[Pygame] 2D게임 만들기 - Sprites, 전체 동작

ParkJuneWoo·2019년 12월 17일
1

Pygame

목록 보기
3/3

Sprites.py

⚡ 게임 내에 등장하는 모든 객체들을 정의하는 부분이다
⚡ 객체들은 어느 정도는 같은 구조(struct)를 갖고있다
⚡ 반드시 구조를 따라야 되는건 아니다

객체의 움직임(update)과 관련된 코드들이 있다보니 어느면에서는 물리적 계산관 관련이 있지만 쉽게 이해할 수 있는정도이다. (코드를 다시보는데 이해가 안된다... 😅)

  • 일반적으로 맵(map)에 등장하는 객체가 여러개인 경우는 group 으로 사용한다
  • 단 하나(플레이어 같은)만 존재하는 객체의 경우는 단일 객체로 사용한다

그럼 코드를 살펴보겠다

사실 오랜만에 보니 이해가 잘 안되는 요소들이 있어 설명이 부족할 수 있는데 양해 바란다... 😟

initialize


import

import os
import pygame as pg
import random
from time import sleep
from settings import *
vec = pg.math.Vector2

vec = pg.math.Vector2 : pygame 라이브러리의 Vector2 객체를 전역변수로 정의. 이후 방향, 속도가 필요한 변수에 사용된다

___init___ (Player)

class Player(pg.sprite.Sprite):
  
    def __init__(self, game):
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game

Sprite 객체 생성시 공통적으로 사용되는 부분이다

  • Class 생성 시에는 pygame.sprite.Sprite 을 상속받는다
  • init parameter 로 받는 gameStart.py 에 있는 변수를 사용하기 위함이다
  • init 에서는 다른 클래스 사용과 같이 변수들을 초기화시키면 된다

✨ 여기서 Player 는 단일 객체기때문에 self.groupgame.all_spirtes 밖에 없지만 Multi(group) 객체의 경우 다음과 같다.

class Platfomrs(pg.sprite.Spirte):
  
  def __init__(self, game):
    self.groups = game.all_sprites, game.platfomrs
    pg.sprite.Sprite.__init__(self, self.groups)
    ...

game 객체에서 game.platforms 를 그룹으로 묶은다음에, 상속받은 Sprite 초기화에 parameter로 넘긴다

여기서 game.platformsStart.pynew 부분에서 정의한다

#Start..py
class Game(Ojbect):
  
  def __init__(self):
    ....
    
  def new(self):
    ...
    # Group 객체
    self.platforms = pg.sprite.Group()
    self.bullets = pg.spirtes.Group()
    
    # 단일 객체
    self.player = Player(self)
	...

이렇게 new 에서 각 group을 만들어주고 이후 필요시마다. all_spirtegroupadd 하여 사용한다.

Ex

# 총알을 발사하는 event

#player 객체의 위치정보를 받아 bullet 객체 생성
bullet = Bullet(self, self.player.rect.centerx, self.player.rect.top) 
self.all_sprites.add(bullet)
self.bullets.add(bullet)
...

쉽게 상황으로 설명을 하면
1. 발사키를 눌르면 event가 발생
2. event 가 발생하면 총알 발사(객체 생성)
3. 생성된 개체는 게임 안의 sprite groupadd
4. bullet group 안에도 들어감
5. 이후 총알이 적과 충돌하면
6. 충돌한 총알 객체를 group에서 제거

총알을 예로들어 구현방법을 설명했는데, 다른 spirte 객체들도 이와 비슷한 방식으로 구현하면 된다

Method


load_image(image initialize)

    #시트에 동작별 프레임 단위로 나눠진 이미지 값들을 불러오는 함수
    def load_images(self):
        self.standing_frames = [self.game.stand.get_image(2, 0, 28, 40),
                                self.game.stand.get_image(32, 0, 29, 40),
                                self.game.stand.get_image(62, 0, 28, 40)]
        for frame in self.standing_frames:
            frame.set_colorkey(WHITE)
        self.walk_frame_l = [self.game.move.get_image(0, 0, 30, 40),
                            self.game.move.get_image(30, 0, 30, 40)]
        self.walk_frame_r = []
        for frame in self.walk_frame_l:
            frame.set_colorkey(WHITE)

        for frame in self.walk_frame_l:
            self.walk_frame_r.append(pg.transform.flip(frame, True, False))
        self.jump_frame = self.game.jump.get_image(0, 0, 30, 40)
  • load_image사용될 이미지를 갖고와서 프레임단위로 저장한다
  • 굳이 메소드로 사용할 필요는 없다
  • frame은 일정크기로 화면을 나누어 저장한다

animate

    #이미지 프레임별 애니메이션이 적용되는 함수
    def animate(self):
        now = pg.time.get_ticks() #millisecond 단위로 현재 게임시간을 갖고옴

        #움직임 값(Bollean)에 대한 명세
        if self.vel.x != 0:
            self.walking = True
        else:
            self.walking = False

        #player가 움직이고 있을 떄(True) 애니메이션
        if self.walking:
            if now - self.last_update > 120: #프레임 속도를 위한 값
                self.last_update = now
                #현재 프레임 값은 계속 1씩 증가하면서 이미지의 프레임 길이만큼 나눠짐
                #즉, 프레임 길이에 따라서 current_frame값이 바뀜
                self.current_frame = (self.current_frame + 1) % len(self.walk_frame_l)
                if self.vel.x > 0: #바뀌는 current_frame 값이 이미지의 프레임을 순회.
                    self.image = self.walk_frame_r[self.current_frame]
                else:
                    self.image = self.walk_frame_l[self.current_frame]

        if self.vel.y != 0:
            self.jumping = True

        #제자리 점프에 대한 애니메이션
        if self.jumping and not self.walking:
            if now - self.last_update > 150:
                self.last_update = now
                self.current_frame = (self.current_frame + 1) % 1
                self.image = self.jump_frame
                self.image.set_colorkey(GREEN)

        self.mask = pg.mask.from_surface(self.image) #플레이어 mask

        #가만히 있을때 애니메이션
        if not self.jumping and not self.walking:
            if now - self.last_update > 350:
                self.last_update = now
                self.current_frame = (self.current_frame + 1) % len(self.standing_frames)
                self.image = self.standing_frames[self.current_frame]

animate 메서드는 프레임 단위로 동작들에 대한 애니메이션(이미지)에 대한 정의 이다. 즉, 가만히 있거나, 점프 중이거나, 이동중이나 등.. 어느 한 행동에 대한 프레임단위별 애니메이션을 그려주는 역활을 한다.

update

	def update(self):
        self.animate()
        self.acc = vec(0, PLAYER_GRAV)#가속값 -> X는 0이고 Y는 중력이 적용
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT]:
            self.acc.x = -PLAYER_ACC
        if keys[pg.K_RIGHT]:
            self.acc.x = PLAYER_ACC

        # apply friction
        self.acc.x += self.vel.x * PLAYER_FRICTION #X에만 마찰력이 적용됨 (Y는 중력 적용)

        #equations of motion
        self.vel += self.acc
        if abs(self.vel.x) < 0.1:
            self.vel.x = 0
        self.pos += self.vel + 0.5 * self.acc

        #wrap around the sides of the screen
        if self.pos.x > WIDTH + self.rect.width / 2:
            self.pos.x = 0 - self.rect.width / 2
        if self.pos.x < 0 - self.rect.width / 2:
            self.pos.x = WIDTH + self.rect.width / 2

        self.rect.midbottom = self.pos

update는 어떻게보면 sprite에서 제일 중요한 역활을 한다. 이전에 게임의 루프는 event->update->draw 라고 설명한적이 있다.
update 메서드는 바로 update->draw 를 처리하는 역활을한다. 그래서 주로 매 프레임마다 event를 체크하고 event에 대한 값과, 애니메이션을 update 한다.

여기 까지가 일반적인 sprite의 구현이다. 다른 sprite라고 해서 큰 틀을 벗어나지 않으며 최대한 같은 구조를 유지하여 개발하는게 이후 디버깅에서 편리하다.

💥 참고로 소스코드로 설명하는 메소드들의 이름(name)은 주관적이며 다르게해도 상관없다

Run


이제 이전에 설명안하고 남겼던 run 코드를 보자.

	def run(self):
        #game loop
        self.playing = True
        while self.playing:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()

이제 어느정도 이해가 되는가? 😅
결국 게임의 흐름은 loop(while)를 돌면서
1. 키 이벤트를 체크하고 ( event )
2. 매 프레임마다 변경 사항들을 반영하고 ( update )
3. 이를 화면에 그려준다 ( draw )

run게임의 전체를 의미한다. 즉, event, update, draw 는 게임의 전체에 대한 loop이다. 이 말은 무엇이냐면.

  • event 는 게임 전체에 대한 이벤트를 감지( ex) 키 이벤트)

  • update 또한 게임 전체에 대한 변경 사항들을 반영

    update 안에서 모든 sprite 들의 update&draw 를 하나로 묶어 update 하고, 게임 전체에 대한 update를 수행(점수 변경, 객체 간의 충돌...)

  • drawupdate 에서 변경된 sprite의 위치에 맞게 다시 그려준다

사실 설명하면서도 내가 무슨 말을 하는건지 이해가 안되는부분이 많았는데... 너무 어렵고 복잡하다고 좌절하지말자.
본인도 유튜브와 래퍼런스 문서를 계속 보고 따라하면서 게임을 완성할 수 있었다.

pygame으로 게임 개발에 도전하고 싶다면 한번쯤은 해볼만한 프로젝트라고 생각한다. (기왕이면 더 쉬운 tool을 쓰자.. )

이상으로 허접한 설명을 마치겠다 더 많은 정보를 원한다면 아래의 링크들을 참고바란다. 👍

profile
안녕하세요.

0개의 댓글