Pyxel을 사용한 2D 게임 시작하기 (Part 8): 소행성 생성
Source: Dev.to
1. 소행성 스프라이트 만들기
먼저, 소행성을 위한 스프라이트 클래스를 준비합니다. **sprite.py**에 새로운 AsteroidSprite 클래스를 추가합니다. 생성자에서는 무작위 값을 사용하여 표시할 소행성 이미지를 선택합니다.
# sprite.py
# add this class
class AsteroidSprite(BaseSprite):
def __init__(self, x, y):
"""Constructor"""
super().__init__(x, y)
self.index = random.randint(2, 7) # Asteroid image index
def draw(self):
"""Draw"""
pyxel.blt(
self.x, self.y, 0,
self.w * self.index, 0,
self.w, self.h, 0
) # Asteroid
이 설정으로, 소행성은 생성될 때마다 서로 다른 외형을 갖게 됩니다.
2. 상수와 변수 준비
다음으로, 소행성 관련 상수를 **main.py**에 추가합니다. 이러한 값을 상수로 묶어두면 나중에 게임 난이도를 조정하기가 훨씬 쉬워집니다.
# main.py
# add constants
ASTEROID_INTERVAL = 20 # Spawn interval (frames)
ASTEROID_LIMIT = 30 # Maximum number of asteroids
ASTEROID_SPD_MIN = 1.0 # Minimum speed
ASTEROID_SPD_MAX = 2.0 # Maximum speed
ASTEROID_DEG_MIN = 30 # Minimum angle
ASTEROID_DEG_MAX = 150 # Maximum angle
Game 클래스의 생성자에 소행성을 관리하기 위한 변수를 추가합니다.
# main.py
# add to Game.__init__()
# Asteroids
self.asteroid_time = 0 # Spawn interval counter
self.asteroids = [] # List of asteroids
3. 소행성 생성 로직
Game 클래스에 check_interval() 메서드를 추가합니다. 이 메서드는 고정된 간격(매 ASTEROID_INTERVAL 프레임)마다 소행성을 생성합니다. 소행성의 총 개수는 ASTEROID_LIMIT으로 제한됩니다. 생성 위치는 화면 상단 가장자리에서 무작위로 선택되며, 속도와 각도도 미리 정의된 범위 내에서 무작위로 선택됩니다.
# main.py
# add to Game class
def check_interval(self):
# Asteroid spawn interval
self.asteroid_time += 1
if self.asteroid_time = ASTEROID_LIMIT:
return
# Add a new asteroid
x = random.random() * W
y = 0
spd = random.uniform(ASTEROID_SPD_MIN, ASTEROID_SPD_MAX)
deg = random.uniform(ASTEROID_DEG_MIN, ASTEROID_DEG_MAX)
asteroid = sprite.AsteroidSprite(x, y)
asteroid.move(spd, deg)
self.asteroids.append(asteroid)
4. 소행성 업데이트 및 그리기
업데이트
Game 클래스의 update() 메서드에서 소행성 생성 및 업데이트를 처리합니다.
# main.py
# add to Game.update()
self.check_interval() # Spawn asteroids
# Update asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
그리기
draw() 메서드에서 모든 소행성을 그립니다.
# main.py
# add to Game.draw()
# Draw asteroids
for asteroid in self.asteroids:
asteroid.draw()
이제 여러 개의 소행성이 화면을 가로질러 흐르게 됩니다.
전체 코드
아래는 이 장에서 다루는 모든 기능을 구현한 전체 코드입니다.
sprite.py
import pyxel
import math
import random
class BaseSprite:
def __init__(self, x, y, w=8, h=8):
"""Constructor"""
self.x = x
self.y = y
self.w = w
self.h = h
self.vx = 0
self.vy = 0
def update(self):
"""Update"""
self.x += self.vx
self.y += self.vy
def draw(self):
"""Draw (implemented in subclasses)"""
pass
def move(self, spd, deg):
"""Move"""
rad = deg * math.pi / 180
self.vx = spd * math.cos(rad) # x‑axis velocity
self.vy = spd * math.sin(rad) # y‑axis velocity
def flip_x(self):
"""Flip movement in the x direction"""
self.vx *= -1
class ShipSprite(BaseSprite):
def __init__(self, x, y):
"""Constructor"""
super().__init__(x, y)
def draw(self):
"""Draw"""
pyxel.blt(
self.x, self.y, 0,
0, 0,
self.w, self.h, 0
) # Ship
class AsteroidSprite(BaseSprite):
def __init__(self, x, y):
"""Constructor"""
super().__init__(x, y)
self.index = random.randint(2, 7) # Asteroid image index
def draw(self):
"""Draw"""
pyxel.blt(
self.x, self.y, 0,
self.w * self.index, 0,
self.w, self.h, 0
) # Asteroid
main.py
import pyxel
import math
import random
import sprite
W, H = 160, 120
SHIP_SPD = 1.4
ASTEROID_INTERVAL = 20 # Spawn interval
ASTEROID_LIMIT = 30 # Maximum number of asteroids
ASTEROID_SPD_MIN = 1.0 # Minimum speed
ASTEROID_SPD_MAX = 2.0 # Maximum speed
ASTEROID_DEG_MIN = 30 # Minimum angle
ASTEROID_DEG_MAX = 150 # Maximum angle
class Game:
def __init__(self):
pyxel.init(W, H, caption="Asteroid Shooter")
self.ship = sprite.ShipSprite(W // 2, H - 20)
# Asteroids
self.asteroid_time = 0
self.asteroids = []
def check_interval(self):
# Asteroid spawn interval
self.asteroid_time += 1
if self.asteroid_time = ASTEROID_LIMIT:
return
# Add a new asteroid
x = random.random() * W
y = 0
spd = random.uniform(ASTEROID_SPD_MIN, ASTEROID_SPD_MAX)
deg = random.uniform(ASTEROID_DEG_MIN, ASTEROID_DEG_MAX)
asteroid = sprite.AsteroidSprite(x, y)
asteroid.move(spd, deg)
self.asteroids.append(asteroid)
def overlap_spr(self, asteroid):
# Placeholder for collision detection logic
pass
def update(self):
# Ship movement (example)
if pyxel.btn(pyxel.KEY_LEFT):
self.ship.x -= SHIP_SPD
if pyxel.btn(pyxel.KEY_RIGHT):
self.ship.x += SHIP_SPD
self.check_interval() # Spawn asteroids
# Update asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
def draw(self):
pyxel.cls(0)
# Draw ship
self.ship.draw()
# Draw asteroids
for asteroid in self.asteroids:
asteroid.draw()
Game()
pyxel.run(Game().update, Game().draw)
Source:
최대 각도
# Game
class Game:
def __init__(self):
"""Constructor"""
# Initialize score
self.score = 0
# Initialize player
self.ship = sprite.ShipSprite(W / 2, H - 40)
deg = 0 if random.random() < 0.5 else 180
self.ship.move(SHIP_SPD, deg)
# Asteroids
self.asteroid_time = 0 # Spawn interval counter
self.asteroids = [] # Asteroid list
# Start Pyxel
pyxel.init(W, H, title="Hello, Pyxel!!")
pyxel.load("shooter.pyxres")
pyxel.run(self.update, self.draw)
def update(self):
"""Update"""
# Update player
self.ship.update()
self.control_ship()
self.overlap_spr(self.ship)
self.check_interval() # Spawn asteroids
# Update asteroids
for asteroid in self.asteroids:
asteroid.update()
self.overlap_spr(asteroid)
def draw(self):
"""Draw"""
pyxel.cls(0)
# Draw score
pyxel.text(
10, 10,
"SCORE:{:04}".format(self.score), 12
)
# Draw player
self.ship.draw()
# Draw asteroids
for asteroid in self.asteroids:
asteroid.draw()
def control_ship(self):
"""Action"""
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
def overlap_spr(self, spr):
"""Move to the opposite side when leaving the screen"""
if spr.x < -spr.w:
spr.x = W
return
if W < spr.x:
spr.x = -spr.w
return
if spr.y < -spr.h:
spr.y = H
return
if H < spr.y:
spr.y = -spr.h
return
def check_interval(self):
# Asteroid spawn interval
self.asteroid_time += 1
if self.asteroid_time < ASTEROID_INTERVAL:
return
self.asteroid_time = 0
# Do not exceed the maximum number of asteroids
if ASTEROID_LIMIT < len(self.asteroids):
return
# Add a new asteroid
x = random.random() * W
y = 0
spd = random.uniform(ASTEROID_SPD_MIN, ASTEROID_SPD_MAX)
deg = random.uniform(ASTEROID_DEG_MIN, ASTEROID_DEG_MAX)
asteroid = sprite.AsteroidSprite(x, y)
asteroid.move(spd, deg)
self.asteroids.append(asteroid)
def main():
"""Main"""
Game()
if __name__ == "__main__":
main()
전체 화면 모드로 전환
전체 화면 모드 종료
게임을 실행하면 다음과 같이 보입니다:
Next Time…
Thank you for reading!
In the next chapter, we’ll cover “Let’s Fire Bullets.”
Stay tuned!
