728x90
반응형

일단 유튜브 영상을 그대로 따라해보면서 시작한다

https://www.youtube.com/watch?v=FfWpgLFMI7w 

https://github.com/attreyabhatt/Space-Invaders-Pygame

 

GitHub - attreyabhatt/Space-Invaders-Pygame: Creating the game Space Invaders using Pygame Module in Python

Creating the game Space Invaders using Pygame Module in Python - GitHub - attreyabhatt/Space-Invaders-Pygame: Creating the game Space Invaders using Pygame Module in Python

github.com

코드 및 활용되는 이미지, 음악 등 다운로드

 

1. Create Game Window

import pygame

#Initialize the pygame
pygame.init()

#Create the screen
screen = pygame.display.set_mode((800,600))

#Game Loop
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

pygame.init() - pygame 시작

pygame.display.set_mode((x,y)) - screen의 크기 지정

 

event 중 pygame.QUIT가 있으면 running을 False로 만들어서 Game Loop 정지

pygame.QUIT는 x버튼이나 Alt+F4같이 창 닫기 event인 것 같다

 

2. Title, Logo, and Background Color

2-1. Title and Icon

https://www.flaticon.com/

 

Free Icons and Stickers - Millions of resources to download

Download Free Icons and Stickers for your projects. Resources made by and for designers. PNG, SVG, EPS, PSD and CSS formats

www.flaticon.com

위 사이트에서 적당한 png 파일을 32px로 다운로드한다

이미지 클릭 시 링크로 이동

 

#Title and Icon
pygame.display.set_caption("Space Invader")

icon = pygame.image.load("logo.png")
pygame.display.set_icon(icon)

pyagme.display.set_caption(string) - Title를 설정

pyagme.image.load(string) - 이미지 불러오기

pygame.display.set_icon(image) - 아이콘을 image로 설정

 

2-2. Background color

while True:
	...
    #Background color (R,G,B)
    screen.fill((255,0,0))
    pygame.display.update()

screen.fill((R,G,B)) - 배경 색상 지정

pyagme.display.update() - display 관련 변경사항 적용

 

3. Player Image

이제 플레이어가 될 Image를 추가해보자

아까 그 사이트에서 적당한 Image를 64px로 다운로드한다 (화면 크기(800x600)를 고려한 px)

이미지 클릭 시 링크로 이동

기본적으로 컴퓨터 화면에서의 좌표는 왼쪽위가 (0,0)이고 오른쪽으로 갈수록, 아래로 갈수록 x,y가 증가한다.

#Player
playerImg = pygame.image.load("player.png")
playerX = 370
playerY = 480

def player():
	screen.blit(playerImg, (playerX, playerY))
    
...

while running:
	...
    player()
    pygame.screen.update()

screen.blit(image, (x,y)) - image를 (x,y)에 나타냄

 

다만 image의 중심이 왼쪽위이기 때문에 정중앙에 나타나지는 않는다.

 

4. Movement

일단 Space Invader에서는 Player는 좌우로만, 즉 x좌표만 변화한다

먼저 방금 만든 player()를 수정한다

...
def player(x,y):
	screen.blit(playerImg, (x,y))

...    

while running:
	...
    playerX += 0.1
    PlayerY -= 0.1
    player(playerX, playerY)
    pygame.display.update()

위 코드는 playerX와 playerY를 프레임당 0.1씩 변화시킨다 (우측 위로 이동)

 

5. Keyboard Inputs&events

먼저 아까 추가했던 playerX += 0.1, playerY -= 0.1를 삭제

while running:
    #Background color (R,G,B)
    screen.fill((0,0,0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                print("Left arrow")
            if event.key == pygame.K_RIGHT:
                print("Right arrow")
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
                print("Key UP")

    player(playerX, playerY)
    pygame.display.update()

이제 진짜로 움직이게 해보자

...
playerX_change = 0
...
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                #print("Left arrow")
                playerX_change = -0.3
            if event.key == pygame.K_RIGHT:
                #print("Right arrow")
                playerX_change = 0.3
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
                #print("Key UP")
                playerX_change = 0

    playerX += playerX_change
    player(playerX, playerY)
    pygame.display.update()

Left Arrow를 누르면 playerX_change를 -0.3으로

Right Arrow를 누르면 playerX_change를 0.3으로

키를 떼면 playerX_change를 0으로

 

매 프레임마다 playerX에 playerX_change를 더해준다

 

 

6. Boundaries

확인해보면 알겠지만 화면은 800x600이지만 좌표는 이론상 무한이기 때문에 이미지가 밖으로 나가버린다

경계를 추가해서 나가지 않도록 하자

 

...
    if playerX <= 0:
        playerX = 0
    if playerX >= 736:
        playerX = 736
...

플레이어의 이미지 크기가 64px이기때문에 오른쪽 경계는 화면크기-이미지크기 = 736이다.

 

7. Add Enemy

영상에서 사용되는 이미지는 찾지 못했기에 대체 이미지 사용

이미지 클릭 시 링크로 이동

위 이미지가 검은색이라 background color를 검은 색이 아니라 다른 색으로 바꿔야 보인다.

이것 때문에 좀 고생했다

 

#Enmey
enemyImg = pygame.image.load("enemy.png")
enemyX = random.randint(0,800)
enemyY = random.randint(50,150)
enemyX_change = 0

...

def enemy(x,y):
    screen.blit(enemyImg, (x, y))
    
...
while running:
	...
    enemy(enemyX, enemyY)
    ...

enemy는 처음 등장 시 랜덤한 x,y 값을 갖는다.

 

8. Enemy Movement

Space Invader에서 Enemy는 양쪽으로 왔다갔다 하면서 조금씩 내려온다

...
enemyX_change = 0.3
enemyY_change = 40

...

while running:
	...
    enemyX += enemyX_change

    if enemyX <= 0:
        enemyX_change = 0.3
        enemyY += enemyY_change
    if enemyX >= 736:
        enemyX_change = -0.3
        enemyY += enemyY_change
    ...

경계에 도달하면 enemyY를 증가시키고 enemyX_change의 방향을 반대로 한다.

 

9. Background Image

...
#Background Image
background = pygame.image.load("background.png")

...

while running:
	screen.blit(background, (0,0))
    ...

background는 800x600과 같이 display 크기와 같도록 해야한다

https://lektion-von-erfolglosigkeit.tistory.com/200

 

이미지 크기(픽셀) 조절

1. 그림판을 이용한 야매 방법 그림판을 켜고 원하는 이미지를 불러온다 크기조정 원하는 값 입력 저장하면 끝 2, 고급지게 온라인으로 하기 https://ezgif.com/resize Animated GIF resizer (free online tool..

lektion-von-erfolglosigkeit.tistory.com

 

그리고 배경이 어두워서 그런지 위에서 만들었던 enemy 이미지가 보이지 않아서 따로 다시 구해서 만들었다

정사각형으로 만들고(ezgif.com) 크기조절하고(그림판) 배경제거하고(remove.bg)...

 

영상에서는 매 프레임 screen.blit를 실행해서 그런지 속도를 조절했지만 나는 그런 현상이 없기에 조절하지 않았다

 

10. Create Bullet

이제 총알을 추가해보자

마찬가지로 적당한 이미지를 32px로 다운로드

 

#Bullet
#Ready - You can't see the bullet on the screen
#Fire - The bullet is currently moving
bulletImg = pygame.image.load("bullet.png")
bulletX = 0
bulletY = 400
bulletX_change = 0
bulletY_change = 1
bullet_state = "ready"

bullet 관련 변수들 추가해주고

 

def fire_bullet(x,y):
    global bullet_state
    bullet_state = "fire"
    screen.blit(bulletImg, (x + 16,y + 10))

bullet 생성 함수 만들고

 

            if event.key == pygame.K_SPACE:
                fire_bullet(playerX, bulletY)

Space를 누르면 생성하고

 

    #Bullet Movement
    if bullet_state is "fire":
        fire_bullet(playerX, bulletY)
        bulletY -= bulletY_change

fire 되었으면 이동시킨다

 

코드를 실행해보면 총알이 나가긴 하는데 한번만 나가고 심지어 playerX에 따라 움직인다.

 

영상에 따르면 나중에 고친다고 한다...

 

11. Shooting Multiple Bullets

먼저 총알이 한번만 발사되는 경우를 보자

이 문제의 원인은 fire_bullet의 y를 bulletY로 설정하기 때문이다.

한번 발사된 후 bulletY가 초기화 되지 않아 실행해도 화면 밖에서 생성되는 것이다.

 

    #Bullet Movement
    if bulletY <= 0:
        bulletY = 480
        bullet_state = "ready"

총알이 화면 끝에 도달하면 bulletY를 초기화하고 state를 "ready"로 초기화

 

bullet이 player를 쫓아다니는 건 bulletX를 설정해주면 된다

            if event.key == pygame.K_SPACE:
                if bullet_state is "ready":
                    # Get the current x cordinate of the spaceship
                    bulletX = playerX
                    fire_bullet(playerX, bulletY)
                    
...

    if bullet_state is "fire":
        fire_bullet(bulletX, bulletY)
        bulletY -= bulletY_change

처음 생성할 때의 playerX를 받아서 buleltX로 설정해주면 된다

 

12. Collision Detection

이제 총알과 적을 충돌을 판별한다

예상대로면 아마 좌표로 그냥 계산할 것 같다

 

def isCollision(enemyX, enemyY, bulletX, bulletY):
    distance = math.sqrt(math.pow(enemyX-bulletX,2) + math.pow(enemyY-bulletY,2))
    if distance < 27:
        return True
    else:
        return False

각 좌표를 받아서 거리를 계산한 뒤 적당한 값 이하면 충돌판정으로 처리한다.

 

    collision = isCollision(enemyX, enemyY, bulletX, bulletY)
    if collision:
        bulletY = 480
        bullet_state="ready"
        score += 1
        enemyX = random.randint(0,735)
        enemyY = random.randint(50,150)

충돌했다고 판정이 나면 bulletY와 state를 초기화하고 score 1 증가, 그리고 enemy의 위치를 다시 임의로 옮긴다.

 

13. Multiple Enemies

대체 어떻게 이렇게 구현한 게임에 여러 Enemy를 추가하는지 궁금했는데 그냥 배열로 해버렸다...ㅋ

 

#Enmey
enemyImg = []
enemyX = []
enemyY = []
enemyX_change = []
enemyY_change = []

num_of_enemies = 6

for i in range(num_of_enemies):
    enemyImg.append(pygame.image.load("enemy.png"))
    enemyX.append(random.randint(0,735))
    enemyY.append(random.randint(50,150))
    enemyX_change.append(0.1)
    enemyY_change.append(40)

Enemy 관련 변수를 모두 배열로 전환

 

def enemy(x,y,i):
    screen.blit(enemyImg[i], (x, y))

생성도 배열로 

 

    #Enemy Movement
    for i in range(num_of_enemies):
        enemyX[i] += enemyX_change[i]
        if enemyX[i] <= 0:
            enemyX_change[i] = 0.1
            enemyY[i] += enemyY_change[i]
        if enemyX[i] >= 736:
            enemyX_change[i] = -0.1
            enemyY[i] += enemyY_change[i]

        #Collision
        collision = isCollision(enemyX[i], enemyY[i], bulletX, bulletY)
        if collision:
            bulletY = 480
            bullet_state="ready"
            score += 1
            enemyX[i] = random.randint(0,735)
            enemyY[i] = random.randint(50,150)

        enemy(enemyX[i], enemyY[i], i)

충돌 판별, 이동, 생성 모두 배열로 처리

 

이쯤에서 전체 코드를 보면 이렇다

더보기
import pygame
import random, math

#Initialize the pygame
pygame.init()

#Create the screen
screen = pygame.display.set_mode((800,600))

#Title and Icon
pygame.display.set_caption("Space Invader")

icon = pygame.image.load("ufo.png")
pygame.display.set_icon(icon)

#Background Image
background = pygame.image.load("background.png")

#Player
playerImg = pygame.image.load("player.png")
playerX = 370
playerY = 480
playerX_change = 0

#Enmey
enemyImg = []
enemyX = []
enemyY = []
enemyX_change = []
enemyY_change = []

num_of_enemies = 6

for i in range(num_of_enemies):
    enemyImg.append(pygame.image.load("enemy.png"))
    enemyX.append(random.randint(0,735))
    enemyY.append(random.randint(50,150))
    enemyX_change.append(0.1)
    enemyY_change.append(40)

#Bullet
#Ready - You can't see the bullet on the screen
#Fire - The bullet is currently moving
bulletImg = pygame.image.load("bullet.png")
bulletX = 0
bulletY = 400
bulletX_change = 0
bulletY_change = 1
bullet_state = "ready"

score = 0

def player(x,y):
    screen.blit(playerImg, (x, y))

def enemy(x,y,i):
    screen.blit(enemyImg[i], (x, y))

def fire_bullet(x,y):
    global bullet_state
    bullet_state = "fire"
    screen.blit(bulletImg, (x + 16,y + 10))

def isCollision(enemyX, enemyY, bulletX, bulletY):
    distance = math.sqrt(math.pow(enemyX-bulletX,2) + math.pow(enemyY-bulletY,2))
    if distance < 27:
        return True
    else:
        return False

#Game Loop
running = True
while running:
    #Background color (R,G,B)
    screen.fill((255,255,255))
    screen.blit(background, (0,0))

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                #print("Left arrow")
                playerX_change = -0.2
            if event.key == pygame.K_RIGHT:
                #print("Right arrow")
                playerX_change = 0.2
            if event.key == pygame.K_SPACE:
                if bullet_state is "ready":
                    # Get the current x cordinate of the spaceship
                    bulletX = playerX
                    fire_bullet(playerX, bulletY)

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
                #print("Key UP")
                playerX_change = 0

    playerX += playerX_change

    if playerX <= 0:
        playerX = 0
    if playerX >= 736:
        playerX = 736

    #Enemy Movement
    for i in range(num_of_enemies):
        enemyX[i] += enemyX_change[i]
        if enemyX[i] <= 0:
            enemyX_change[i] = 0.1
            enemyY[i] += enemyY_change[i]
        if enemyX[i] >= 736:
            enemyX_change[i] = -0.1
            enemyY[i] += enemyY_change[i]

        #Collision
        collision = isCollision(enemyX[i], enemyY[i], bulletX, bulletY)
        if collision:
            bulletY = 480
            bullet_state="ready"
            score += 1
            enemyX[i] = random.randint(0,735)
            enemyY[i] = random.randint(50,150)

        enemy(enemyX[i], enemyY[i], i)

    #Bullet Movement
    if bulletY <= 0:
        bulletY = 480
        bullet_state = "ready"
         
    if bullet_state is "fire":
        fire_bullet(bulletX, bulletY)
        bulletY -= bulletY_change

    player(playerX, playerY)

    pygame.display.update()

 

14. Score Text

Text도 이미지와 비슷하게 생성할 수 있다

#Score
score_value = 0
font = pygame.font.Font('freesansbold.ttf', 32)
textX = 10
textY = 10

freesansbold.ttf는 pygame에서 제공하는 무료 폰트중 하나라고 한다

 

def show_score(x,y):
    score = font.render("Score: " + str(score_value), True, (255,255,255))
    screen.blit(score,(x,y))

player(x,y)처럼 show_score(x,y)로 생성한다

다만 img가 아닌 font.render로 만든다

 

실행해보면 좌측 상단에 Score가 나오면서 Enemy를 맞추면 Score가 올라가는 것을 볼 수 있다.

 

15. Sound and Background Music

드디어 효과음과 배경음악

근데 이제야 github 링크에서 받으라고 한다

미리 알려줬으면 이미지 때문에 고생 안했을텐데...

background.wav
4.62MB
explosion.wav
0.32MB
laser.wav
0.03MB

pygame의 mixer를 사용한다

 

사용하기 쉽게 따로 import를 해준다.

from pygame import mixer

 

Background Music는 무한반복을 시킨다

#Backgroun Music
mixer.music.load("background.wav")
mixer.music.play(-1)

 

총알 발사와 폭발은 한번만 재생한다

            if event.key == pygame.K_SPACE:
                if bullet_state is "ready":
                    bullet_Sound = mixer.Sound("laser.wav")
                    bullet_Sound.play()
                    # Get the current x cordinate of the spaceship
                    bulletX = playerX
                    fire_bullet(playerX, bulletY)
        #Collision
        collision = isCollision(enemyX[i], enemyY[i], bulletX, bulletY)
        if collision:
            explosion_Sound = mixer.Sound("explosion.wav")
            explosion_Sound.play()
            bulletY = 480
            bullet_state="ready"
            score_value += 1
            enemyX[i] = random.randint(0,735)
            enemyY[i] = random.randint(50,150)

 

mixer.music과 mixer.sound의 차이가 보인다.

 

16. Game Over

Text는 Score와 똑같다

#Game Over Test
over_font = pygame.font.Font('freesansbold.ttf', 64)

font 생성해주고

 

def game_over_text():
    over_text = over_font.render("GAME OVER", True, (255,255,255))
    screen.blit(over_text, (200, 250))

gamer_over_text 함수 만들어서 생성할수 있도록

 

        #Game Over
        if enemyY[i] > 400:
            for j in range(num_of_enemies):
                enemyY[j] = 2000
            game_over_text()
            mixer.music.stop()
            break

만약 선(y=400)을 넘으면 모든 enemy의 y를 2000으로 만들어서 화면에 안보이게 한뒤

game_over_text함수를 실행한 뒤 배경음악을 끄고 break

break하면 enemy loop에서 나가기 때문에 더이상 enemy가 생성되지 않는다 (더 정확히는 다시 화면에 보이지 않는다)

 

유튜브 영상에 있던건 끝났고 다음엔 살짝 개조나 해보자

'작업일지 > Pygame' 카테고리의 다른 글

Pygame #3 - Tetris  (0) 2022.06.14
Pygame #1 - Installation  (0) 2022.05.02