일단 유튜브 영상을 그대로 따라해보면서 시작한다
https://www.youtube.com/watch?v=FfWpgLFMI7w
https://github.com/attreyabhatt/Space-Invaders-Pygame
코드 및 활용되는 이미지, 음악 등 다운로드
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
위 사이트에서 적당한 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
그리고 배경이 어두워서 그런지 위에서 만들었던 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 링크에서 받으라고 한다
미리 알려줬으면 이미지 때문에 고생 안했을텐데...
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 |