이제 남은 두 개의 함수의 구현체를 만들도록 하겠다. 제일 먼저, 충돌검사(chek_collision) 함수에 대한 부분이다. 여기서 말하는 충돌이란, 주인공이 쏜 총알에 적 비행기가 맞았을 때, 또는 반대로 적 비행기의 총알에 주인공이 맞았을 때, 마지막으로 적 비행기와 주인공이 직접 충돌했을 때 이렇게 총 3가지가 있을 수 있겠다.
각각의 충돌에 대한 코드를 살펴보자. 적의 총알은 단 1개가 아니므로 총알객체의 모음인 리스트 형태로 존재하고, 따라서, 리스트 안에 하나하나의 총알들과 개별 충돌검사를 for 구문을 통해 하게되고, 충돌이 발견될 경우, 총알 자체도 없에고(8번 라인에서 메모리에서 해제), 적 비행기도 없에면(15번 라인) 되는데 단순하게 그냥 사라지면 흥미가 떨어지니 게임성을 위해 폭발장면을 애니메이션화 하였다. 참고로 적 비행기를 없에기 위한 방법으로 del 명령어 문법이 새롭게 등장했는데, 이는 객체를 제거(메모리에서 해제)하는 한 방법 중에 하나이다. 우리는 enemies 리스트에 enemy 객체들이 담겨있다는 것을 알고, enemy_index를 통해 그 안에 지우고자 하는 객체의 위치를 특정해 지우는 것이다. 그밖에 리스트 안에 특정 값을 제거하는 방법으로 pop과 remove 내장메소드를 사용해봤었는데, 이 메소드들을 사용해서 지울 수 있지만, 편리한 새로운 문법을 배우는 기회로 삼으면 되겠다.
폭발 장면 2장의 이미지를 객체화한 후 에니매이션 처리를 하였는데 주의깊게 봐야할 부분은 폭발장면 애니메이션을 시간지연을 주면서 충분히 보여주는 처리가 필요하였다(13번, 21~23번 라인). 이 부분이 왜 필요한지는 이 부분의 코드를 제거하고 실행해보면 금방 이해가 되는데, 이전 절에서 update 콜백함수의 빠른 호출에 의한 동일한 사유로 애니메이션을 충분히 보여주기도 전에 폭발객체 자체를 삭제해버리는 것을 방지하기 위한 목적이다.
그밖에 score와 game_over의 두 글로벌 변수에 대해서는 적 비행기를 제거해 점수가 증가되는 상황(16번 라인)과 주인공이 죽어 게임이 종료되는 상황(27번 라인)에 대해 변수값 변경을 적용하고 있다.
def check_collision():
global score, game_over
# 적이 총알에 맞았을 때
for bullet in bullets:
enemy_index = bullet.collidelist(enemies)
if enemy_index != -1:
bullets.remove(bullet)
explosion = Actor("explosion1")
explosion.pos = enemies[enemy_index].pos
explosion.images = ["explosion1", "explosion2"]
explosion.fps = 8
explosion.duration = 15
explosions.append(explosion)
del enemies[enemy_index]
score += 1
# 적 비행기 폭발 애니메이션
for explosion in explosions:
explosion.animate()
explosion.duration -= 1
if explosion.duration == 0:
explosions.remove(explosion)
# 적 총알에 주인공이 맞거나, 적과 직접 충돌했을 때
if player.collidelist(enemy_bullets) != -1 or player.collidelist(enemies) != -1:
game_over = True
이제 마지막으로 남은 화면에 정보표시용 draw_text 함수를 구현하는 것으로 모든 사용자 함수의 구현은 끝이나게 된다. draw_text 함수는 간단한데, 아래와 같이 점수와 게임종료 상황에 대해 단순 화면 표시이다.
정말 마지막으로 처리해야 할 것이 하나 남았는데 그것은 게임이 종료되었음에도 불구하고 계속 게임을 할 수 있는 상황을 막아놓는 것이다. 크게 어려운 것은 아니고, 아래와 같이 update 콜백함수에서 게임종료가 아닐 때만, 주인공의 조작이 가능하게 조건문을 넣으면 되는 것이다(8번 라인).
def update():
for background in backgrounds:
background.y += 3
if background.top > HEIGHT:
background.y = (HEIGHT / 2) - HEIGHT
create_enemies()
if game_over == False:
move_player()
shoot_bullets()
check_collision()
이것으로 이번 트윈비 게임의 모든 구현이 완료되었다. 마지막으로 전체 소스코드를 첨부하는 것으로 이 절을 마무리 하겠다.
from pgzhelper import *
import random
WIDTH = 800
HEIGHT = 600
backgrounds = []
background1 = Actor("background1", (WIDTH / 2, HEIGHT / 2))
backgrounds.append(background1)
background2 = Actor("background2", (WIDTH / 2, (HEIGHT / 2) - HEIGHT))
backgrounds.append(background2)
player = Actor("player", (400, 500))
MAX_BULLETS = 3
bullets = []
enemies = []
enemy_bullets = []
explosions = []
score = 0
game_over = False
music.play('main_theme')
def move_player():
if keyboard.right:
player.x += 5
if keyboard.left:
player.x -= 5
if keyboard.up:
player.y -= 5
if keyboard.down:
player.y += 5
if player.right > WIDTH:
player.right = WIDTH
if player.left < 0:
player.left = 0
if player.bottom > HEIGHT:
player.bottom = HEIGHT
if player.top < 0:
player.top = 0
def shoot_bullets():
if keyboard.space and len(bullets) < MAX_BULLETS:
sounds.sfx_sounds_interaction25.play()
bullet_delay = 5
bullet = Actor("player_bullet")
bullet.pos = player.pos
bullet.angle = 90
bullets.append(bullet)
for bullet in bullets:
bullet.move_forward(15)
if bullet.y < 0:
bullets.remove(bullet)
def create_enemies():
if random.randint(0, 1000) > 980:
enemy = Actor("enemy1_1")
enemy.images = ["enemy1_1", "enemy1_2"]
enemy.fps = 5
enemy.y = -50
enemy.x = random.randint(100, WIDTH - 100)
enemy.direction = random.randint(-100, -80)
enemies.append(enemy)
for enemy in enemies:
enemy.move_in_direction(4)
enemy.animate()
if enemy.top > HEIGHT:
enemies.remove(enemy)
if random.randint(0, 1000) > 990:
bullet = Actor("enemy_bullet")
bullet.pos = enemy.pos
bullet.angle = random.randint(0, 359)
enemy_bullets.append(bullet)
for bullet in enemy_bullets:
bullet.move_forward(5)
if bullet.x < 0 or bullet.x > WIDTH or bullet.y < 0 or bullet.y > HEIGHT:
enemy_bullets.remove(bullet)
def check_collision():
global score, game_over
for bullet in bullets:
enemy_index = bullet.collidelist(enemies)
if enemy_index != -1:
bullets.remove(bullet)
explosion = Actor("explosion1")
explosion.pos = enemies[enemy_index].pos
explosion.images = ["explosion1", "explosion2"]
explosion.fps = 8
explosion.duration = 15
explosions.append(explosion)
del enemies[enemy_index]
score += 1
for explosion in explosions:
explosion.animate()
explosion.duration -= 1
if explosion.duration == 0:
explosions.remove(explosion)
if player.collidelist(enemy_bullets) != -1 or player.collidelist(enemies) != -1:
game_over = True
def draw_text():
screen.draw.text("Score " + str(score), (50, 0), color="black", fontsize=30)
if game_over:
screen.draw.text(
"Game over", midbottom=(WIDTH / 2, HEIGHT / 2), color="blue", fontsize=100
)
def draw():
for background in backgrounds:
background.draw()
player.draw()
for enemy in enemies:
enemy.draw()
for bullet in bullets:
bullet.draw()
for explosion in explosions:
explosion.draw()
for bullet in enemy_bullets:
bullet.draw()
draw_text()
def update():
for background in backgrounds:
background.y += 3
if background.top > HEIGHT:
background.y = (HEIGHT / 2) - HEIGHT
create_enemies()
if game_over == False:
move_player()
shoot_bullets()
check_collision()