使用 Pyxel 开始 2D 游戏(第15部分):隧道躲避游戏 2(示例)

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

Source: Dev.to

资产

使用 Pyxel Editor 创建背景资产并下载资源文件:

Download – 点击页面右上角的 Download 按钮获取文件。

完整示例代码

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 processing"""
        self.x += self.vx
        self.y += self.vy

    def draw(self):
        """Draw processing (implemented in subclasses)"""
        pass

    def move(self, spd, deg):
        """Move by speed and angle"""
        rad = deg * math.pi / 180
        self.vx = spd * math.cos(rad)   # Velocity on X axis
        self.vy = spd * math.sin(rad)   # Velocity on Y axis

    def intersects(self, other):
        """Rectangle 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 PlayerSprite(BaseSprite):
    def __init__(self, x, y):
        """Constructor"""
        super().__init__(x, y)
        self.gravity = 0.4      # Gravity
        self.jump_x = 1.0       # Jump X velocity
        self.jump_y = -3.4      # Jump Y velocity

    def update(self):
        """Update processing"""
        super().update()
        self.vy += self.gravity

    def draw(self):
        """Draw processing"""
        pyxel.blt(self.x, self.y, 0, 0, 16, self.w, self.h, 0)
        # Debug
        # pyxel.rectb(self.x, self.y, self.w, self.h, 3)

    def jump(self):
        """Jump"""
        self.vx = self.jump_x
        self.vy = self.jump_y

class TunnelSprite(BaseSprite):
    def __init__(self, x, y, length, top_flg=False):
        """Constructor"""
        super().__init__(x, y, 16, length * 8)
        self.length = length          # Tunnel length
        if top_flg:
            self.y -= self.h           # Upper tunnel

    def draw(self):
        """Draw processing"""
        # Top
        pyxel.blt(self.x, self.y, 0, 16, 16, self.w, 8, 0)

        # Middle
        for i in range(self.length - 2):
            y = self.y + (i + 1) * 8
            pyxel.blt(self.x, y, 0, 16, 24, self.w, 8, 0)

        # Bottom
        y = self.y + (self.length - 1) * 8
        pyxel.blt(self.x, y, 0, 16, 32, self.w, 8, 0)

        # Debug
        # pyxel.rectb(self.x, self.y, self.w, self.h, 3)

main.py

import pyxel
import math
import random
import sprite

# ----------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------
W, H = 160, 120

START_X = W / 2 - 48
START_Y = H / 2 - 12

MODE_TITLE = "title"
MODE_PLAY = "play"
MODE_GAME_OVER = "game_over"

TUNNEL_TOTAL = 48

class Game:
    def __init__(self):
        """Constructor"""

        # Initialize score
        self.score = 0

        # Game mode
        self.game_mode = MODE_TITLE

        # Initialize player
        self.player = sprite.PlayerSprite(START_X, START_Y)

        # Initialize stage
        self.reset()

        # Start Pyxel
        pyxel.init(W, H, title="Hello, Pyxel!!")
        pyxel.load("flappy.pyxres")
        pyxel.run(self.update, self.draw)

    # ------------------------------------------------------------------
    # Core loop
    # ------------------------------------------------------------------
    def update(self):
        """Update processing"""

        # Update score
        self.score = int(self.player.x - START_X)

        # Control input
        self.control()

        # Skip the rest if we are not in play mode
        if self.game_mode != MODE_PLAY:
            return

        # Update player
        self.player.update()

        # Update tunnels
        for tunnel in self.tunnels:
            tunnel.update()
            if tunnel.intersects(self.player):
                self```

```python
.game_mode = MODE_GAME_OVER
                break

        # 下落检查
        if H < self.player.y:
            self.game_mode = MODE_GAME_OVER

    def draw(self):
        """绘制处理"""
        pyxel.cls(6)

        # 瓦片地图背景
        pyxel.bltm(0, 0, 0, 0, 0, 192, 128, 0)

        # 相机(设置)
        pyxel.camera(self.player.x - START_X, 0)

        # 绘制玩家
        self.player.draw()

        # 绘制隧道
        for tunnel in self.tunnels:
            tunnel.draw()

        # 相机(重置)
        pyxel.camera()

        # 消息
        if self.game_mode == MODE_TITLE:
            msg = "按空格开始"
            pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 1)
        elif self.game_mode == MODE_GAME_OVER:
            msg = "游戏结束"
            pyxel.text(W / 2 - len(msg) * 2, H / 2, msg, 1)

        # 绘制得分
        pyxel.text(10, 10, f"得分:{self.score:04}", 1)

    # ------------------------------------------------------------------
    # 辅助方法
    # ------------------------------------------------------------------
    def reset(self):
        """初始化关卡"""

        # 玩家位置
        self.player.x = START_X
        self.player.y = START_Y

        # 隧道
        self.tunnels = []
        for i in range(TUNNEL_TOTAL):
            pad_x = 42
            pad_y = random.randint(2, 3) * 8
            x = START_X + pad_x * i + 32
            y = H / 2 + random.randint(-2, 2) * 8

            # 上方隧道
            t_top = sprite.TunnelSprite(x, y - pad_y, 10, True)
            self.tunnels.append(t_top)

            # 下方隧道
            t_bottom = sprite.TunnelSprite(x, y + pad_y, 10)
            self.tunnels.append(t_bottom)

    def control(self):
        """控制输入"""
        if not pyxel.btnp(pyxel.KEY_SPACE):
            return

        # 标题 → 开始
        if self.game_mode == MODE_TITLE:
            self.game_mode = MODE_PLAY

        # 游戏结束 → 标题
        if self.game_mode == MODE_GAME_OVER:
            self.game_mode = MODE_TITLE
            self.reset()

        # 跳跃(仅在游戏模式)
        if self.game_mode == MODE_PLAY:
            self.player.jump()

def main():
    Game()

if __name__ == "__main__":
    main()

主流程

""" Main process """
Game()

if __name__ == "__main__":
    main()

运行时游戏将呈现如下。

非常感谢您阅读本文。
希望本系列能帮助您入门游戏开发。

(如果您喜欢本篇,点赞 👍 将不胜感激!)

Back to Blog

相关文章

阅读更多 »