class Game:
def __init__(self):
# initialize game window, etc
pg.init()
pg.mixer.init()
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption(TITLE)
self.clock = pg.time.Clock()
self.running = True #게임 실행 Boolean 값
...
...
pg.init()
: pygame initializer 입니다. pygame 라이브러리를 사용하기 위해 가장먼저 사용
pg.mixer.init()
: music 사용을 위한 initializer
self.screen
: display.set_mode() 를 사용하여 게임 창의 크기를 지정
(WIDTH, HEIGHT 값은 setting.py 에 정의되어 있다)
pg.display.set_caption(TITLE)
: 게임 창의 제목표시줄에 쓰여질 문구
self.clock
: time.Clock() 객체를 사용하여 이후 초당 프레임 설정시 사용한다
self.running
: 게임이 끝난지 아닌지를 판단하기 위한 변수입니다(이름은 상관없다)
def run(self):
#game loop
pg.mixer.music.play(loops=-1) #배경음 플레이 (loops 값 false = 반복, true = 한번)
self.playing = True
while self.playing:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
pg.mixer.music.fadeout(500) #배경음이 갑자기 꺼지지 않고 점점 꺼지게 함
정말 간단하다 😅
self.playing
: 값이 True 이면 현재 게임중 임을 나타낸다
self.clock.tick(FPS)
: 초당 그려질 프레임을 설정합니다
FPS값이 커질수록 부드러워지나 CPU에 부하가 많이 간다
20~30이 적당하다
events
: 주로 버튼클릭에 대한 정의가 있다 (총 쏘기, 점프, Exit, ... 등)
update
: 매 프레임마다 update 될 항목들 이다 (충돌, 적 생성, 게임 오버,.. 등)
(중요한 부분이므로 따로 자세하게 다루겠다)
draw
: 게임을 프레임단위로 계속 그려주는 부분이다
(사실 게임은 매 프레임마다 새로운 화면을 그려주는 것의 반복이다)
def new(self):
# start a new game
self.score = 0
self.head_count = 0
self.enemy_level = 0
self.speed_x = 4
self.speed_y = 5
self.speed_x_min = -2
self.speed_y_min = 2
## sprite group ##
self.all_sprites = pg.sprite.Group()
self.explo = pg.sprite.Group()
self.enemys = pg.sprite.Group() #적 sprite 그룹 생성
self.bullets = pg.sprite.Group() # 총알 sprite 그룹 생성
self.platforms = pg.sprite.Group() #platforms(블록) sprites 그룹 생성
self.items = pg.sprite.Group()
self.heads = pg.sprite.Group()
self.player = Player(self) #self.player, Player객체 생성
self.start_tick = pg.time.get_ticks()
#PLATFORM_LIST에서 각 value값을 받아와 객체 생성
for plat in PLATFORM_LIST:
Platform(self, *plat)
pg.mixer.music.load(os.path.join(self.snd_dir, 'old city theme.mp3')) #배경음 로드
with open(os.path.join(self.dir, SCORE), 'r') as f:
try :
self.highscore = int(f.read())
except:
self.highscore = 0
self.run()
초기화라면 init에다가 하면 되지 않을까?
초기화라고 해서 init 과 혼란이 될 수 있지만 간단하게 생각하면 쉽다
쉽게 말하면
💥 게임을 클릭하여 실행하는 행위 는 init 이고
💥 게임 시작화면에서 start 를 눌러 게임을 플레이 하는 행위는 new 이다
이 점을 잘 생각해서 코딩을 할 수 있도록 하자
def update(self):
#game loop - update
self.all_sprites.update()
self.second = ((pg.time.get_ticks() - self.start_tick)/1000)
if self.player.vel.y > 0:
#hits -> spritecollide 메서드를 이용(x,y, default boolean)충돌 체크
hits = pg.sprite.spritecollide(self.player, self.platforms, False)
if hits:
lowest = hits[0]
for hit in hits:
if hit.rect.bottom > lowest.rect.bottom:
lowest = hit
if self.player.pos.x < lowest.rect.right + 15 and \
self.player.pos.x > lowest.rect.left - 15:
if self.player.pos.y < lowest.rect.bottom:
self.player.pos.y = lowest.rect.top+1 #충돌시 player의 Y축 위치값이 충돌한 블록의 TOP값으로
#즉, 블록위에 있는 것처럼 보이게함
self.player.vel.y = 0
self.player.jumping = False
#Game Level 처리, 최대 3번 난이도가 증가.
if self.score == 1000:
self.score += 10
self.level_up.play()
self.leveup_text()
sleep(0.4)
self.enemy_level += 1
self.levelup(self.enemy_level)
elif self.score == 2500:
self.score += 10
self.level_up.play()
self.leveup_text()
sleep(0.4)
self.levelup(self.enemy_level)
elif self.score == 4000:
self.score += 10
self.level_up.play()
self.leveup_text()
sleep(0.4)
self.levelup(self.enemy_level)
#게임 클리어 조건
if self.head_count == 15:
self.clear_text()
self.ending = True
self.playing = False
self.head_count = 0
sleep(1)
```
- 사실상 여기에 있는 값들도 본인밖에 모를거라 생각한다..
- 전부다 이해하지 말고 큰 그림으로 어떠한 로직인지만 이해하자
- 필요한 부분만 캐치하자!
`self.all_sprites.update()` : 모든 sprite(객체) 들을 update한다. 모든 객체들은 **오버라이드 된 update()메서드**를 갖는다
_(sprite 부분에서 따로 설명하겠다)_
`self.score` : 게임에서 스코어 값을 이용하여 대한 난이도 조절을 한다
`self.head_count` : 게임 클리어 조건을 만족하는지 확인한다
🌟다음은 충돌처리 부분인데, 모든 게임에서 매우 중요한 부분이기 떄문에 따로 설명하겠다
## 충돌 처리
```python
if self.player.vel.y > 0:
#hits -> spritecollide 메서드를 이용(x,y, default boolean)충돌 체크
hits = pg.sprite.spritecollide(self.player, self.platforms, False)
if hits:
lowest = hits[0]
for hit in hits:
if hit.rect.bottom > lowest.rect.bottom:
lowest = hit
if self.player.pos.x < lowest.rect.right + 15 and \
self.player.pos.x > lowest.rect.left - 15:
if self.player.pos.y < lowest.rect.bottom:
self.player.pos.y = lowest.rect.top+1 #충돌시 player의 Y축 위치값이 충돌한 블록의 TOP값으로
#즉, 블록위에 있는 것처럼 보이게함
self.player.vel.y = 0
self.player.jumping = False
# pos 는 플레이어의 position(위치)를 의미한다
# vel 은 플레이어의 이동방향에 대한 속도를 의미한다
# jumping 은 플레이어가 현재 점프 중인지 아닌지를 나타낸다
```
> 충돌 감지가 필요한 부분은 게임마다 다르므로 여기서 어떤 경우 충돌을 체크하는지 보겠다
` self.player.vel.y` : 이 변수는 `player`객체가 `vel.y > 0` y 값이 0보다 커진 경우, 즉 점프한 경우를 말한다
`hits = pg.sprite.spritecollide(self.player, self.platforms, False)`
: pygame에서 지원하는 충돌체크 메서드인 spritecollide()를 사용한다. 이 메서드는
- 단일 객체와 충돌한 **group spirte 내의 객체 리스트를 반환**한다
- parameter **첫 번째로 single sprite** 객체를 받는다
- parameter **두 번째로 group sprite** 객체를 받는다
- 세 번째 parameter 는 True/False 이며 값을 True로하면 충돌즉시 group에서 충돌한 객제를 삭제한다
코드를 쉽게 설명하면,
1. 가장먼저 플레이어와 벽돌(platforms)이 충돌했는지 확인한다
2. 충돌한 벽돌이 여러개라면 **가장 밑에 있는 것을 타겟**으로 잡는다
```python
lowest = hits[0]
for hit in hits:
if hit.rect.bottom > lowest.rect.bottom:
lowest = hit
# 플레이어의 위치가 벽돌의 왼쪽/오른쪽 끝부분 안에 있고
if self.player.pos.x < lowest.rect.right + 15 and \
self.player.pos.x > lowest.rect.left - 15:
# 플레이어의 윗 부분 위치(머리부분)가 벽돌의 밑부분에 닿으면
# 벽돌 위로 올린다
if self.player.pos.y < lowest.rect.bottom:
self.player.pos.y = lowest.rect.top+1
플레이어가 점프 했을 때 올라갈 수 있는 벽돌에 닿았을때의 충돌처리를 구현한 부분이다. 다소 복잡해 보일 수 있지만
순서대로 일어나는 행위 전체를 본다면 이해가 쉬울 것이다. 😅
충돌 부분은 실제로 직접 구현하면 어렵지만 pygame 에서 제공하는 메서드를 이용하면 보다 쉽게 구현할 수 있다
여기 까지 start.py 에서 중요한 부분들을 살펴봤으며, 다음에는 게임에 등장하는 객체들(sprite)을 설명하겠다
마지막으로 게임을 실행하는 부분 을 보여주지만 이해는 각자 해보도록 하자 (귀찮아서가 아님)
g = Game()
while g.start:
while g.running:
g.new()
if g.ending == True:
g.ending_screen() # ending
else:
g.show_over_screen() # game over
pg.quit()