使用 Pyxel 入门 2D 游戏(第 8 部分):生成小行星

发布: (2026年1月11日 GMT+8 23:00)
6 min read
原文: Dev.to

I’m happy to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you already provided) here? Once I have the article’s body, I’ll translate it into Simplified Chinese while preserving the original formatting, markdown syntax, and technical terms.

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)

最大角度

# 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()

进入全屏模式

退出全屏模式

运行游戏时,它的效果如下:

游戏截图

下次…

感谢阅读!

在下一章中,我们将介绍 “Let’s Fire Bullets.”

敬请期待!

Back to Blog

相关文章

阅读更多 »