使用 Pyxel 开发 2D 游戏入门(第 10 部分):实现碰撞检测
发布: (2026年1月13日 GMT+8 23:00)
5 min read
原文: Dev.to
Source: Dev.to
在本章节中,我们将实现子弹与小行星之间的碰撞检测。
完整代码在最后提供。
1. 实现碰撞检测逻辑
在 sprite.py 中的 BaseSprite 类添加一个新的 intersects() 方法。
该方法检查精灵是否与作为参数传入的另一个精灵重叠,如果碰撞则返回 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()
结果
运行游戏会产生以下结果:
下一章
感谢阅读!
在下一章中,我们将实现 game‑over detection。
敬请期待!!
