使用Pygame制作“吃豆人”游戏

本篇博客展示如何使用 Python + Pygame 编写一个简易版的“吃豆人(Pac-Man)” 风格游戏。这里我们暂且命名为 Py-Man。玩家需要控制主角在一个网格地图里移动、吃掉散布在各处的豆子,并躲避在地图中巡逻的幽灵。此示例可帮助你理解网格地图角色移动敌人随机移动碰撞与得分等常见2D游戏开发技巧。


1. 前言

吃豆人(Pac-Man) 自 1980 年诞生以来,一直是街机游戏史上的里程碑作品。它的核心玩法是:

  • 玩家控制吃豆人(Pac-Man),在迷宫状的地图里收集豆子;
  • 幽灵(Ghost)会在地图中巡逻,玩家要设法规避幽灵追击;
  • 当豆子吃光或玩家被幽灵抓到,游戏结束。

本篇中,我们编写一个缩小且精简的“吃豆人”原型示例,重点演示:

  • 地图使用网格表示:不同数字代表墙壁、豆子、幽灵、可行走区域等;
  • 玩家(Pac-Man)的移动:基于方向键上下左右移动,每次移动一格;
  • 幽灵的随机行动:每帧可能随机选择一个方向前进(如遇墙则停留或换方向);
  • 碰撞和得分:吃完全部豆子即胜利,若与幽灵坐标重叠则游戏失败。

2. 开发环境

  1. Python 3.x
  2. Pygame 库:若未安装可通过
    pip install pygame
    
  3. 支持图形界面的操作系统:Windows、macOS 或绝大多数 Linux 均可。

在确保 import pygame 没有报错后,即可开始项目的开发。


3. 游戏设计思路

  1. 网格地图(MAP)

    • 使用一个二维列表来表示地图;
    • 为简化,我们定义:
      • 1 表示墙壁,不可通过;
      • 2 表示幽灵初始位置;
      • 3 表示豆子,需要被吃掉;
      • 4 表示玩家初始位置;
      • 0 表示空地,可以行走。
    • 当然,实际 Pac-Man 会有更复杂的地图布局,这里只做示例。
  2. 玩家

    • 存储玩家的网格坐标 (row, col)
    • 通过键盘上下左右控制每次移动一格;
    • 若目标位置是墙壁则保持不动,否则进入该格;
    • 如果在有豆子的格子上,就吃掉该豆子并加分。
  3. 幽灵

    • 地图可含多个幽灵;
    • 每帧随机选择一个方向移动,如果该方向是墙壁或出界,则不移动或重新选择;
    • 当玩家和幽灵坐标重叠时,游戏失败。
  4. 得分与胜利

    • 每当玩家吃掉一颗豆子(3),得分增加 1,并将该格子改为 0;
    • 如果全部豆子都被吃光,游戏胜利。
  5. 游戏循环

    • 处理键盘事件与幽灵的随机移动;
    • 更新玩家与幽灵位置;
    • 检测玩家是否吃到豆子、是否与幽灵碰撞;
    • 如果游戏结束或胜利,则跳转到结束场景。

4. 完整示例代码

将以下代码保存为 py_man.py 并执行。你可根据需要自定义地图大小、关卡布局、移动速度、幽灵 AI 等来拓展本示例。

import pygame
import sys
import random

# 初始化 Pygame
pygame.init()

# -----------------------
# 全局配置
# -----------------------
TILE_SIZE = 40     # 每个格子的像素大小
FPS = 8            # 帧率(适当降低 以便看清幽灵移动)
WHITE = (255, 255, 255)
BLACK = (0,   0,   0)
BLUE  = (0,   0, 255)
GREEN = (0, 200,   0)
RED   = (255,   0,  0)
YELLOW= (255, 255, 0)
GRAY  = (100, 100, 100)

# 地图定义:1-墙,2-幽灵,3-豆子,4-玩家位置,0-空地
GAME_MAP = [
    [1,1,1,1,1,1,1,1,1,1],
    [1,4,3,3,0,3,3,3,3,1],
    [1,3,1,1,3,1,1,1,0,1],
    [1,3,1,2,3,1,2,3,3,1],
    [1,3,3,3,3,3,3,1,3,1],
    [1,0,1,3,1,3,1,3,3,1],
    [1,3,1,3,1,3,1,3,3,1],
    [1,3,3,3,3,3,3,3,3,1],
    [1,3,3,1,3,0,3,3,3,1],
    [1,1,1,1,1,1,1,1,1,1],
]

ROWS = len(GAME_MAP)
COLS = len(GAME_MAP[0])

SCREEN_WIDTH = COLS * TILE_SIZE
SCREEN_HEIGHT = ROWS * TILE_SIZE

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Py-Man 简易吃豆人")
clock = pygame.time.Clock()
font = pygame.font.SysFont("arial", 32)

# -----------------------
# 玩家类
# -----------------------
class Player:
    def __init__(self, row, col):
        self.row = row
        self.col = col
        self.score = 0

    def move(self, dr, dc):
        new_row = self.row + dr
        new_col = self.col + dc
        if 0 <= new_row < ROWS and 0 <= new_col < COLS:
            # 如果不是墙(1),则可移动
            if GAME_MAP[new_row][new_col] != 1:
                self.row = new_row
                self.col = new_col

    @property
    def x(self):
        return self.col * TILE_SIZE

    @property
    def y(self):
        return self.row * TILE_SIZE

# -----------------------
# 幽灵类
# -----------------------
class Ghost:
    def __init__(self, row, col):
        self.row = row
        self.col = col

    def update(self):
        # 随机尝试一个方向移动(上下左右)
        directions = [(-1,0), (1,0), (0,-1), (0,1)]
        dr, dc = random.choice(directions)

        new_row = self.row + dr
        new_col = self.col + dc
        # 如果新位置不是墙,且在地图内,则移动
        if 0 <= new_row < ROWS and 0 <= new_col < COLS:
            if GAME_MAP[new_row][new_col] != 1:
                self.row = new_row
                self.col = new_col

    @property
    def x(self):
        return self.col * TILE_SIZE

    @property
    def y(self):
        return self.row * TILE_SIZE

# -----------------------
# 工具函数:初始化游戏对象
# -----------------------
def init_game():
    player = None
    ghosts = []

    for r in range(ROWS):
        for c in range(COLS):
            cell = GAME_MAP[r][c]
            if cell == 4:  # 玩家
                player = Player(r, c)
                # 恢复成空地
                GAME_MAP[r][c] = 0
            elif cell == 2:  # 幽灵
                ghosts.append(Ghost(r, c))
                # 恢复成空地
                GAME_MAP[r][c] = 0

    return player, ghosts

# -----------------------
# 主游戏逻辑
# -----------------------
def main():
    player, ghosts = init_game()
    running = True

    # 统计剩余豆子数
    total_beans = sum(row.count(3) for row in GAME_MAP)

    while running:
        clock.tick(FPS)

        # 1) 事件处理
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # 2) 键盘输入(上下左右)
        keys = pygame.key.get_pressed()
        if keys[pygame.K_UP]:
            player.move(-1, 0)
        elif keys[pygame.K_DOWN]:
            player.move(1, 0)
        elif keys[pygame.K_LEFT]:
            player.move(0, -1)
        elif keys[pygame.K_RIGHT]:
            player.move(0, 1)

        # 玩家若踩到豆子(3),吃掉并得分
        if GAME_MAP[player.row][player.col] == 3:
            GAME_MAP[player.row][player.col] = 0
            player.score += 1
            # 若吃完所有豆子 -> 胜利
            if player.score == total_beans:
                running = False
                game_over(won=True, score=player.score)
                continue

        # 幽灵随机移动
        for g in ghosts:
            g.update()

        # 检测玩家是否与幽灵相碰
        for g in ghosts:
            if g.row == player.row and g.col == player.col:
                # 玩家失败
                running = False
                game_over(won=False, score=player.score)
                break

        # 3) 绘制场景
        screen.fill(BLACK)

        # 绘制地图
        for r in range(ROWS):
            for c in range(COLS):
                tile = GAME_MAP[r][c]
                x = c * TILE_SIZE
                y = r * TILE_SIZE

                if tile == 1:  
                    pygame.draw.rect(screen, BLUE, (x, y, TILE_SIZE, TILE_SIZE))  # 墙
                elif tile == 3:  
                    pygame.draw.circle(screen, YELLOW, (x + TILE_SIZE//2, y + TILE_SIZE//2), TILE_SIZE//6)  # 豆子
                else:
                    # 通路 or 空地
                    pygame.draw.rect(screen, GRAY, (x, y, TILE_SIZE, TILE_SIZE))

        # 绘制玩家 (绿色圆)
        pygame.draw.circle(screen, GREEN, (player.x + TILE_SIZE//2, player.y + TILE_SIZE//2), TILE_SIZE//2 - 4)

        # 绘制幽灵 (红色方块)
        for g in ghosts:
            pygame.draw.rect(screen, RED, (g.x+5, g.y+5, TILE_SIZE-10, TILE_SIZE-10))

        # 分数
        score_surf = font.render(f"Score: {player.score}", True, WHITE)
        screen.blit(score_surf, (10, 10))

        pygame.display.flip()

    pygame.quit()
    sys.exit()

def game_over(won, score):
    screen.fill(BLACK)
    if won:
        msg = f"恭喜,你吃掉所有豆子!得分: {score}"
    else:
        msg = f"被幽灵抓住了!得分: {score}"
    label = font.render(msg, True, WHITE)
    rect = label.get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2))
    screen.blit(label, rect)
    pygame.display.flip()
    pygame.time.wait(3000)  # 等待3秒再退出

if __name__ == "__main__":
    main()

代码说明

  1. 地图 GAME_MAP

    • 用一个二维列表存储地图信息,示例尺寸 10×10:
      • 1(蓝色方块)表示墙壁;
      • 3(黄色小圆)表示豆子;
      • 24 仅用于初始位置记录,随后会被设为 0
    • 你可以自行扩展地图,或使用文件读取的方式加载更大规模的关卡。
  2. 玩家与幽灵

    • 玩家每帧检测上下左右键,若不是墙就移动。并且若所在格子是豆子就得分、把该格子变成空地;
    • 幽灵update() 里随机选择一个方向移动,如果遭遇墙壁则保持原地或尝试别的方向。
    • 玩家坐标幽灵坐标一致,则判定失败。
  3. 得分与胜利

    • 统计地图中豆子的总数 total_beans
    • 如果玩家吃掉的豆子数与 total_beans 相等,则判定胜利;
    • 碰到幽灵则立即失败。
  4. 绘制

    • 墙壁用蓝色矩形,豆子用黄色小圆,空地用灰色背景
    • 玩家用绿色圆,幽灵用红色小方块
    • 你可以替换为更精美的贴图或动画帧,让游戏看起来更加逼真。

5. 运行效果

image.png


6. 总结

通过本篇示例,你已掌握了一个简化“吃豆人” 原型所需的关键实现,你可以在此基础上,结合你对 Pac-Man 的灵感或其他创意,一步步将这个简易示例打磨成更完备、更具乐趣的 2D 游戏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/963803.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springboot使用rabbitmq

使用springboot创建rabbitMQ的链接。 整个项目结构如下&#xff1a; 1.maven依赖 <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>3.4.1</version> </dependency>application.y…

安卓(android)订餐菜单【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握Activity生命周的每个方法。 2.掌握Activity的创建、配置、启动和关闭。 3.掌握Intent和IntentFilter的使用。 4.掌握Activity之间的跳转方式、任务栈和四种启动模式。 5.掌握在Activity中添加…

RabbitMQ快速上手及入门

概念 概念&#xff1a; publisher&#xff1a;生产者&#xff0c;也就是发送消息的一方 consumer&#xff1a;消费者&#xff0c;也就是消费消息的一方 queue&#xff1a;队列&#xff0c;存储消息。生产者投递的消息会暂存在消息队列中&#xff0c;等待消费者处理 exchang…

java命令详解

这里以jdk8为例子&#xff0c;查看默认的垃圾回收器 java -XX:PrintCommandLineFlags -version-XX:UseParallelGC : Parallel Scavenge 和 Parallel Old 组合 -XX:InitialHeapSize268435456 : 初始化堆大小&#xff08;字节&#xff09; -XX:MaxHeapSize4294967296 : 最大堆大…

自主Shell命令行解释器

什么是命令行 我们一直使用的"ls","cd","pwd","mkdir"等命令&#xff0c;都是在命令行上输入的&#xff0c;我们之前对于命令行的理解&#xff1a; 命令行是干啥的&#xff1f;是为我们做命令行解释的。 命令行这个东西实际上是我们…

分析哲学:从 语言解剖到 思想澄清的哲学探险

分析哲学&#xff1a;从 语言解剖 到 思想澄清 的哲学探险 第一节&#xff1a;分析哲学的基本概念与公式解释 【通俗讲解&#xff0c;打比方来讲解&#xff01;】 分析哲学&#xff0c;就像一位 “语言侦探”&#xff0c;专注于 “解剖语言”&#xff0c;揭示我们日常使用的语…

自定义数据集 使用paddlepaddle框架实现逻辑回归

导入必要的库 import numpy as np import paddle import paddle.nn as nn 数据准备&#xff1a; seed1 paddle.seed(seed)# 1.散点输入 定义输入数据 data [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1], [1.5, 75.6…

QtCreator在配置Compilers时,有一个叫ABI的选项,那么什么是ABI?

问题提出 QtCreator在配置Compilers时,有一个叫ABI的选项,那么什么是ABI&#xff1f; ABI&#xff08;Application Binary Interface&#xff09;介绍 ABI&#xff08;Application Binary Interface&#xff0c;应用二进制接口&#xff09;是指应用程序与操作系统或其他程序…

[STM32 标准库]EXTI应用场景 功能框图 寄存器

一、EXTI 外部中断在嵌入式系统中有广泛的应用场景&#xff0c;如按钮开关控制&#xff0c;传感器触发&#xff0c;通信接口中断等。其原理都差不多&#xff0c;STM32会对外部中断引脚的边沿进行检测&#xff0c;若检测到相应的边沿会触发中断&#xff0c;在中断中做出相应的处…

Maven jar 包下载失败问题处理

Maven jar 包下载失败问题处理 1.配置好国内的Maven源2.重新下载3. 其他问题 1.配置好国内的Maven源 打开⾃⼰的 Idea 检测 Maven 的配置是否正确&#xff0c;正确的配置如下图所示&#xff1a; 检查项⼀共有两个&#xff1a; 确认右边的两个勾已经选中&#xff0c;如果没有请…

【JavaScript】Web API事件流、事件委托

目录 1.事件流 1.1 事件流和两个阶段说明 1.2 事件捕获 1.3 事件冒泡 1.4 阻止冒泡 1.5 解绑事件 L0 事件解绑 L2 事件解绑 鼠标经过事件的区别 两种注册事件的区别 2.事件委托 案例 tab栏切换改造 3.其他事件 3.1 页面加载事件 3.2 页面滚动事件 3.2 页面滚…

Spring Cloud工程搭建

目录 工程搭建 搭建父子工程 创建父工程 Spring Cloud版本 创建子项目-订单服务 声明项⽬依赖 和 项⽬构建插件 创建子项目-商品服务 声明项⽬依赖 和 项⽬构建插件 工程搭建 因为拆分成了微服务&#xff0c;所以要拆分出多个项目&#xff0c;但是IDEA只能一个窗口有一…

neo4j入门

文章目录 neo4j版本说明部署安装Mac部署docker部署 neo4j web工具使用数据结构图数据库VS关系数据库 neo4j neo4j官网Neo4j是用ava实现的开源NoSQL图数据库。Neo4作为图数据库中的代表产品&#xff0c;已经在众多的行业项目中进行了应用&#xff0c;如&#xff1a;网络管理&am…

selenium记录Spiderbuf例题C03

防止自己遗忘&#xff0c;故作此为记录。 鸢尾花数据集(Iris Dataset) 这道题牵扯到JS动态加载。 步骤&#xff1a; &#xff08;1&#xff09;进入例题&#xff0c;需要找到按钮规律。 flip_xpath: str r"//li/a[onclickgetIrisData({});]" &#xff08;2&…

自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数

导入必要的库&#xff1a; import numpy as np import torch import torch.nn as nn import torch.optim as optim from sklearn.metrics import precision_score, recall_score, f1_score 准备数据&#xff1a; class1_points np.array([[1.9, 1.2],[1.5, 2.1],[1.9, 0.5]…

如何运行Composer安装PHP包 安装JWT库

1. 使用Composer Composer是PHP的依赖管理工具&#xff0c;它允许你轻松地安装和管理PHP包。对于JWT&#xff0c;你可以使用firebase/php-jwt这个库&#xff0c;这是由Firebase提供的官方库。 安装Composer&#xff08;如果你还没有安装的话&#xff09;&#xff1a; 访问Co…

《Linux服务与安全管理》| 数据库服务器安装和配置

《Linux服务与安全管理》| 数据库服务器安装和配置 目录 《Linux服务与安全管理》| 数据库服务器安装和配置 任务一&#xff1a; 安装PostgreSQL数据库&#xff0c;设置远程登录&#xff0c;客户端可以成功登录并操作数据库。 任务二&#xff1a; 安装MySQL数据库&#xf…

【贪心算法篇】:“贪心”之旅--算法练习题中的智慧与策略(一)

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;贪心算法篇–CSDN博客 文章目录 一.贪心算法1.什么是贪心算法2.贪心算法的特点 二.例题1.柠…

DRM系列七:Drm之CREATE_DUMB

本系列文章基于linux 5.15 DRM驱动的显存由GEM&#xff08;Graphics execution management&#xff09;管理。 一、创建流程 创建buf时&#xff0c;user层提供需要buf的width,height以及bpp(bite per pixel)&#xff0c;然后调用drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &…

Python从0到100(八十七):CNN网络详细介绍及WISDM数据集模型仿真

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…