이전의 포스트에서 슈팅게임의 뼈대를 만들어 두었다.
이제 그 위에 그럴듯한 옷을 입힐 차례다.
아래와 같은 게임에 스프라이트를 입히면
아래 처럼 변한다. 그래픽을 입히니까 그럴듯해 보인다
이런 게임 아트를 game asset 이라고 한다. 게임을 멋있게 만들기 위해서는 프로그래밍 하는 시간보다 자료 찾는 시간이 오래걸린다. 저작권이 없는(public) 자료도 있지만 많은 경우 크리에이티브 커먼즈 저작권이 걸려있다. 저작권 침해를 방지하기 위해 사용전에 어떤 조건인지 꼭 확인하도록 한다.
www.flaticon.com/ 벡터 아이콘이 많은 곳이다
freemusicarchive.org/home 게임음악 스러운 Free Music
원하는 game asset 을 모으다 보면 상당한 시간이 흘러갈 것이다. 이전 포스팅에 도형으로 뼈대를 만든 것은 그 때문이었다. game asset 을 찾는 것은 천천히 찾고 우선 뼈대를 만들어 진행을 시키는 것이다. 그래픽이야 언제든지 더 나은 asset 을 찾으면 바꿀 수 있다.
지난 포스팅의 연장이므로 전체 소스 보다는 부분적인 설명이 좋을 것이다.
전체 소스파일은 파일로 첨부한다.
게임루프를 보면 윗쪽 부분에 이미지와 사운드 파일을 로드한다. 배경으로 사용할 파일과 캐릭터 이미지 등 도형에 입힐 스프라이트 파일을 로드한다. 이미지를 리스트로 만드는 것은 적을 랜덤하게 생성하기 위해서이다.
사운드의 경우도 마찬가지이다. 사용할 것들을 미리 로드해놓는 것이다. 배경음악은 바로 틀어준다.
def game_loop(surface):
# ----- load game graphics -----
background = pygame.image.load(os.path.join(image_dir, 'milkyway.png')).convert()
bg_rect = background.get_rect()
player_image = pygame.image.load(os.path.join(image_dir, 'spikedship1.png')).convert()
bullet_image = pygame.image.load(os.path.join(image_dir, 'blasterbolt.png')).convert()
asteroid_image = []
asteroid_list = ['asteroid1.png', 'asteroid2.png', 'asteroid3.png',
'asteroid4.png', 'asteroid5.png']
for img in asteroid_list:
asteroid_image.append(pygame.image.load(os.path.join(image_dir, img)).convert())
shoot_sound = pygame.mixer.Sound(os.path.join(sound_dir, 'sfx3.wav'))
shoot_sound.set_volume(0.1)
explosion_sound = pygame.mixer.Sound(os.path.join(sound_dir, 'sfx4.wav'))
explosion_sound.set_volume(0.1)
pygame.mixer.music.load(os.path.join(sound_dir, 'stage1.mp3'))
pygame.mixer.music.set_volume(0.1)
pygame.mixer.music.play(loops=-1)
clock = pygame.time.Clock()
sprite_group = pygame.sprite.Group()
mobs = pygame.sprite.Group()
bullets = pygame.sprite.Group()
player = PlayerShip(player_image)
global player_health
player_health= 100
global score
score = 0
sprite_group.add(player)
for i in range(7):
enemy = Mob(asteroid_image)
sprite_group.add(enemy)
mobs.add(enemy)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q:
running = False
if event.key == pygame.K_SPACE:
player.shoot(sprite_group, bullets, bullet_image, shoot_sound)
if event.type == pygame.MOUSEBUTTONDOWN:
player.shoot(sprite_group, bullets, bullet_image, shoot_sound)
sprite_group.update()
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for hit in hits:
explosion_sound.play()
mob = Mob(asteroid_image)
sprite_group.add(mobs)
mobs.add(mob)
score += 10
hits = pygame.sprite.spritecollide(player, mobs, False, pygame.sprite.collide_circle)
if hits:
print('a mob hits player!')
player_health -= 1
if player_health < 0:
gameover(surface)
close_game()
restart()
surface.fill(LIGHT_PINK1)
surface.blit(background, bg_rect)
sprite_group.draw(surface)
score_update(surface)
pygame.display.flip()
clock.tick(FPS)
pygame.quit()
print('game played: ',playtime)
다음은 플레이어쉽이다.
여기서 self.image = pygame.Surface((40,30)) 의 도형을 주석처리하고 대신 위에서 로드한 image를 사용한다. transform.scale 을 사용하면 사이즈를 맞출 수 있다. 이미지를 조작하는 방법은 이전 포스트를 참고한다.
set_colorkey를 하는 이유는 png 파일의 여백부분을 없애주기 때문이다. 그 외에 self.radius 를 사용하면 충돌감지기능을 향상시킬 수 있다. (메인루프의 collide 에서 나온다)
def shoot 에서 아까 로드한 sound들을 play() 하는 것을 볼 수 있다. 버튼이 눌러질 때 마다 무기가 발사되면서 소리가 난다.
class PlayerShip(pygame.sprite.Sprite):
def __init__(self, image):
pygame.sprite.Sprite.__init__(self)
# self.image = pygame.Surface((40,30))
self.image = pygame.transform.scale(image, (75, 40))
self.image.set_colorkey(BLACK)
self.rect = self.image.get_rect()
self.radius = int(self.rect.width * .9/2)
# pygame.draw.circle(self.image, RED, self.rect.center, self.radius, 1)
# self.image.fill(RED)
self.rect.centerx = int(SCREEN_WIDTH / 2)
self.rect.centery = SCREEN_HEIGHT - 20
self.speedx = 0
self.speedy = 0
def update(self):
self.speedx = 0
self.speedy = 0
keystate = pygame.key.get_pressed()
if keystate[pygame.K_a]:
self.speedx = -10
if keystate[pygame.K_d]:
self.speedx = 10
if keystate[pygame.K_w]:
self.speedy = -10
if keystate[pygame.K_s]:
self.speedy = 10
self.rect.x += self.speedx
self.rect.y += self.speedy
if self.rect.right > SCREEN_WIDTH:
self.rect.right = SCREEN_WIDTH
if self.rect.left < 0:
self.rect.left = 0
if self.rect.bottom > SCREEN_HEIGHT:
self.rect.bottom = SCREEN_HEIGHT
if self.rect.top < 0:
self.rect.top = 0
def shoot(self, all_sprites,bullets, image, sound):
bullet = Bullet(self.rect.centerx, self.rect.top, image)
all_sprites.add(bullet)
bullets.add(bullet)
sound.play()
다음은 몹과 총알이다. 이것도 같은 방식으로 image 를 로드한다.
몹을 만들 때 리스트로 파일들을 로드하고 random 함수로 선택한다. rotozoom 함수로 이미지를 원하는 형태로 바꾼다.
중간에 rotate 메서드를 추가했는데 이것은 날아오는 적들에게 움직임을 주기 위해서이다.
이런 기능들은 파이게임을 사용하지 않았다면 구현하기 쉽지 않은 것들이다. 비록 아마추어 수준의 게임이지만 파이게임은 상당히 장점이 많다. 파이썬과 프로그래밍을 학습하기에 상당히 좋은 내용이다.
class Mob(pygame.sprite.Sprite):
def __init__(self, image):
pygame.sprite.Sprite.__init__(self)
self.image_origin = random.choice(image)
self.image_origin = pygame.transform.rotozoom(random.choice(image), 0, 0.7)
self.image = self.image_origin
self.image.set_colorkey(WHITE)
# self.image = pygame.Surface((30,30))
# self.color = random.choice([RED, BLUE, RED, GREEN1, YELLOW])
# self.image.fill(self.color)
self.rect = self.image.get_rect()
self.radius = int(self.rect.width * .9 / 2)
# pygame.draw.circle(self.image, RED, self.rect.center, self.radius, 1)
self.rect.x = random.randrange(SCREEN_WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange( 1, 8)
self.speedx = random.randrange(-3, 3)
self.rotation = 0
self.rotation_speed = random.randrange(-10, 10)
# self.rotation_speed = 7
self.last_update = pygame.time.get_ticks()
def rotate(self):
now = pygame.time.get_ticks()
if now - self.last_update > 50:
self.last_update = now
self.rotation = (self.rotation + self.rotation_speed) % 360
new_image = pygame.transform.rotate(self.image_origin, self.rotation)
old_center = self.rect.center
self.image = new_image
self.rect = self.image.get_rect()
self.rect.center = old_center
def update(self):
self.rotate()
self.rect.x += self.speedx
self.rect.y += self.speedy
if self.rect.top > SCREEN_HEIGHT + 10 or self.rect.left < -25 or self.rect.right > SCREEN_WIDTH + 20:
self.rect.x = random.randrange(SCREEN_WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.speedy = random.randrange(3, 8)
class Bullet(pygame.sprite.Sprite):
def __init__(self, player_x, player_y, image):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(image, (40, 70))
self.image.set_colorkey(WHITE)
# self.image = pygame.Surface((10,20))
# self.image.fill(GREEN1)
self.rect = self.image.get_rect()
# pygame.draw.ellipse(self.image, RED, self.rect,1)
self.rect.bottom = player_y
self.rect.centerx = player_x
self.speedy = - 10
def update(self):
self.rect.y += self.speedy
if self.rect.bottom < 0:
self.kill()
이번 코드는 지난번 도형 슈팅게임 보다 좀 더 그래픽적으로 발전하고 소리와 배경음도 추가해봤다. 파이게임이 이런 복잡한 과정을 다 도와주기 때문에 손쉽게 할 수 있었다.
파이썬 게임에 관한 주제는 다음 포스팅에서도 다뤄보도록 하겠다.
*참고영상 유튜브 채널