Pyxel을 이용한 2D 게임 시작하기 (Part 10): 충돌 감지 구현
Source: Dev.to
이번 장에서는 총알과 소행성 간의 충돌 감지를 구현합니다.
전체 코드는 마지막에 제공됩니다.
1. 충돌 감지 로직 구현
새로운 intersects() 메서드를 sprite.py의 BaseSprite 클래스에 추가합니다.
이 메서드는 인자로 전달된 다른 스프라이트와 겹치는지 확인하고, 충돌하면 True, 그렇지 않으면 False를 반환합니다. (구현은 AABB 개념을 사용하지만, 지금은 자세히 알 필요 없습니다.)
# sprite.py (added to BaseSprite)
def intersects(self, other):
"""Rectangle‑based collision detection (AABB)"""
if other.x + other.w < self.x: return False
if self.x + self.w < other.x: return False
if other.y + other.h < self.y: return False
if self.y + self.h < other.y: return False
return True
2. 총알과 소행성 충돌
총알 업데이트 단계에서 총알과 소행성 간의 충돌을 검사합니다.
리스트를 순회하면서 요소를 제거하기 때문에 인덱스 문제를 피하기 위해 역순으로 리스트를 순회합니다.
로직을 단순하게 유지하기 위해 충돌이 감지되면 즉시 update() 메서드에서 return 합니다. 충돌이 발생하면 점수가 1점 증가합니다.
# main.py (added to the Game.update() method)
# Update bullets (reverse order)
for bullet in self.bullets[::-1]:
bullet.update()
# Remove bullets that leave the screen
if bullet.y < 0:
self.bullets.remove(bullet)
continue
# Collision detection (bullet × asteroid)
for asteroid in self.asteroids[::-1]:
if asteroid.intersects(bullet):
self.score += 1 # Increase score
self.bullets.remove(bullet)
self.asteroids.remove(asteroid)
return # Simplified handling
전체 코드
아래는 지금까지 구현된 모든 기능이 포함된 전체 코드입니다.
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 position"""
self.x += self.vx
self.y += self.vy
def draw(self):
"""Draw (implemented in subclasses)"""
pass
def move(self, spd, deg):
"""Move in a direction"""
rad = deg * math.pi / 180
self.vx = spd * math.cos(rad) # X velocity
self.vy = spd * math.sin(rad) # Y velocity
def flip_x(self):
"""Flip X direction"""
self.vx *= -1
def intersects(self, other):
"""Rectangle‑based collision detection (AABB)"""
if other.x + other.w < self.x: return False
if self.x + self.w < other.x: return False
if other.y + other.h < self.y: return False
if self.y + self.h < other.y: return False
return True
class ShipSprite(BaseSprite):
def __init__(self, x, y):
"""Constructor"""
super().__init__(x, y)
def draw(self):
"""Draw ship"""
pyxel.blt(self.x, self.y, 0, 0, 0, self.w, self.h, 0)
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 asteroid"""
pyxel.blt(self.x, self.y, 0,
self.w * self.index, 0,
self.w, self.h, 0)
class BulletSprite(BaseSprite):
def __init__(self, x, y):
"""Constructor"""
super().__init__(x, y)
self.x += self.w / 2 - 1 # Center alignment
def draw(self):
"""Draw bullet"""
pyxel.rect(self.x, self.y, 2, 2, 12)
main.py
import pyxel
import math
import random
import sprite
W, H = 160, 120
SHIP_SPD = 1.4
ASTEROID_INTERVAL = 20
ASTEROID_LIMIT = 30
ASTEROID_SPD_MIN = 1.0
ASTEROID_SPD_MAX = 2.0
ASTEROID_DEG_MIN = 30
ASTEROID_DEG_MAX = 150
BULLET_SPD = 3
# 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
self.asteroids = []
# Bullets
self.bullets = []
# 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)
# Update bullets (reverse order)
for bullet in self.bullets[::-1]:
bullet.update()
# Remove bullets that leave the screen
if bullet.y < 0:
self.bullets.remove(bullet)
continue
# Collision detection (bullet × asteroid)
for asteroid in self.asteroids[::-1]:
if asteroid.intersects(bullet):
self.score += 1
self.bullets.remove(bullet)
self.asteroids.remove(asteroid)
return # Simplified handling
# ... (other methods such as draw, control_ship, overlap_spr, check_interval, etc.)
다른 모든 헬퍼 메서드(draw, control_ship, overlap_spr, check_interval 등)는 변경되지 않았습니다.
게임 루프 – 충돌, 그리기 및 컨트롤
for asteroid in self.asteroids[::-1]:
if asteroid.intersects(bullet):
self.score += 1
self.bullets.remove(bullet)
self.asteroids.remove(asteroid)
return
def draw(self):
"""Draw everything on the screen."""
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()
# Draw bullets
for bullet in self.bullets:
bullet.draw()
def control_ship(self):
"""Handle player actions."""
if pyxel.btnp(pyxel.KEY_SPACE):
self.ship.flip_x() # Reverse movement
# Fire bullet
bullet = sprite.BulletSprite(self.ship.x, self.ship.y)
bullet.move(BULLET_SPD, 270)
self.bullets.append(bullet)
def overlap_spr(self, spr):
"""Wrap a sprite around the screen edges."""
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):
"""Spawn asteroids at regular intervals."""
# Asteroid spawn interval
self.asteroid_time += 1
if self.asteroid_time < ASTEROID_INTERVAL:
return
self.asteroid_time = 0
# Limit asteroid count
if ASTEROID_LIMIT < len(self.asteroids):
return
# Spawn 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 entry point."""
Game()
if __name__ == "__main__":
main()
결과
게임을 실행하면 다음과 같은 결과가 나타납니다:
다음 장
읽어 주셔서 감사합니다!
다음 장에서는 게임 오버 감지를 구현할 것입니다.
계속 기대해 주세요!!
