使用 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.)

所有其他辅助方法(drawcontrol_shipoverlap_sprcheck_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 screenshot

下一章

感谢阅读!

在下一章中,我们将实现 game‑over detection

敬请期待!!

Back to Blog

相关文章

阅读更多 »