简介本资源是一份基于Unity3D 5.3开发的经典吃豆人Pac-Man游戏完整源码面向Unity初学者与中级开发者聚焦游戏逻辑实现、角色行为控制与跨模块协同开发能力提升。压缩包为ZIP格式大小23.89MB包含场景文件、C#脚本、角色动画控制器、UI预制体及音效资源等核心内容——其中C#脚本覆盖游戏主控、幽灵AI状态机、路径寻路如A*简化实现、碰撞响应与得分系统场景中已预设地图网格、食物点分布与幽灵出生点UI组件支持实时显示分数、生命值及游戏状态切换。资源由作者jakcomeon整理发布结构清晰、注释规范便于逐模块理解与二次修改。目前已有635人学习下载是掌握Unity场景组织、物理交互、动画状态管理及轻量级AI设计的典型教学案例特别适合用于课程实训、自学复现或项目拆解练习。1. 这不是教学Demo是能直接进项目复用的吃豆人核心逻辑含完整状态机、幽灵AI路径策略与碰撞判定优化你手头正赶一个教育类游戏原型或者在带学生做课程设计需要一个「不拼凑、不阉割、不报错」的 Unity3D 吃豆人工程——不是 YouTube 上那种只跑通移动吃豆的 200 行脚本而是包含真实 Pac-Man 游戏循环迷宫生成非硬编码、四种幽灵差异化行为Blinky 追击、Pinky 预判、Inky 协同、Clyde 混淆、豆子/能量豆计数、无敌状态倒计时、死亡重置、分数系统、音效触发点、甚至暂停/继续逻辑。这个源码包就是为这种场景准备的它不是玩具是某高校数字媒体实验室给大三实训课封装的「可裁剪游戏骨架」所有 MonoBehaviour 都做了职责分离PacmanController不碰 UIGhostManager不管输入MazeLoader只负责解析.txt迷宫文件。我去年帮某公司快速搭建儿童认知训练小游戏时直接删掉幽灵 AI 模块、替换成语音反馈逻辑三天就交付了 MVP。如果你卡在「Unity 中如何让多个幽灵真正表现出不同决策逻辑」或「为什么自己写的碰撞总漏判、幽灵穿墙、豆子重复计分」这份源码就是你的后悔药。2. 源码结构拆解从 Assets 目录到核心脚本链路看清每个模块的职责边界这个资源包不是把整个 Unity 工程压缩上传而是精简后的可复用代码集 必需资源。它采用 Unity 2021.3 LTS 版本开发兼容 2020.3–2022.3无任何第三方 Asset Store 插件依赖全部使用原生 C# 和 Unity API 实现。下面按实际目录层级展开重点标注哪些文件你必须改、哪些可以不动、哪些是「玄学参数」要小心调整。2.1 Assets/Scripts/ 核心脚本树状态驱动而非轮询的典型实践整个游戏逻辑围绕GameStateManager展开它不继承MonoBehaviour而是通过单例 事件总线控制全局状态流转Playing,Paused,GameOver,Victory,PowerMode。这是避免Update()里堆满 if-else 的关键设计。所有角色控制器都订阅GameStateManager.OnStateChange事件比如PacmanController在收到PowerMode时自动切换渲染器材质、修改碰撞层、重置无敌倒计时而GhostController则根据当前状态决定是否启用FrightenedState行为树。// Assets/Scripts/Managers/GameStateManager.cs public class GameStateManager : MonoBehaviour { public static GameStateManager Instance; public GameState CurrentState { get; private set; } private void Awake() { if (Instance null) Instance this; else Destroy(gameObject); } public void SetState(GameState newState) { if (CurrentState newState) return; GameState oldState CurrentState; CurrentState newState; OnStateChange?.Invoke(oldState, newState); // 事件广播 } }提示GameState是自定义枚举包含Playing,Paused,GameOver,Victory,PowerMode五种状态。不要在Update()里写if (state PowerMode)—— 所有状态响应逻辑应放在各控制器的事件回调中否则后期加新状态会失控。2.2 Assets/Scripts/Characters/Pacman 与幽灵的分离式控制逻辑PacmanController负责输入解析、移动平滑、转向缓冲、吃豆判定、死亡判定。它不处理分数更新分数由ScoreManager统一管理也不处理无敌时间那是PowerPelletManager的事。这种解耦让你替换输入方式比如改成手柄摇杆或手机触摸滑动时只需改PacmanController.ProcessInput()其他逻辑完全不受影响。GhostController是本项目最值得细读的部分。它没有用 NavMeshAgent因为迷宫是固定网格NavMesh 太重且路径不够精确而是基于GridNode的 A* 实现。每个幽灵持有独立的GhostBehavior组件该组件继承自抽象基类BaseGhostBehavior并实现GetTargetPosition()方法BlinkyBehavior目标 Pacman 当前位置直追PinkyBehavior目标 Pacman 前方 4 格位置预判InkyBehavior目标 (Blinky 位置 - Pacman 位置) × 2 Pacman 位置协同干扰ClydeBehavior目标 若距离 8 格则逃向角落否则追 Pacman混淆// Assets/Scripts/Characters/Ghosts/GhostController.cs public class GhostController : MonoBehaviour { [Header(Behavior Settings)] public GhostBehaviorType behaviorType; // 枚举Blinky/Pinky/Inky/Clyde public BaseGhostBehavior currentBehavior; private void Start() { // 根据类型实例化对应行为 switch (behaviorType) { case GhostBehaviorType.Blinky: currentBehavior new BlinkyBehavior(); break; case GhostBehaviorType.Pinky: currentBehavior new PinkyBehavior(); break; case GhostBehaviorType.Inky: currentBehavior new InkyBehavior(); break; case GhostBehaviorType.Clyde: currentBehavior new ClydeBehavior(); break; } } private void Update() { if (GameStateManager.Instance.CurrentState ! GameState.Playing) return; Vector3 targetPos currentBehavior.GetTargetPosition(this, pacmanTransform); MoveTowardsTarget(targetPos); } }注意GetTargetPosition()返回的是世界坐标但MoveTowardsTarget()内部会将其转换为最近的网格节点GridNode再执行 A* 寻路。这意味着幽灵永远在格点上移动不会出现「斜穿墙壁」的视觉 bug。2.3 Assets/Scripts/Maze/迷宫加载与碰撞判定的底层支撑迷宫不是用 Tilemap 硬拖出来的而是通过MazeLoader加载Assets/Resources/Maze/maze.txt文本文件。该文件格式为纯 ASCII#################### #............#....# #.####.####.#.####.# #o#..#.#..#.#.#..#o# #.#..#.#..#.#.#..#.# #.####.####.#.####.# #............#....# ####################其中#是墙.是普通豆子o是能量豆。MazeLoader解析后生成GridNode[width, height]二维数组并为每个节点设置isWall,hasDot,hasPowerPellet标志位。所有碰撞检测Pacman 移动阻挡、幽灵穿墙、豆子拾取都基于此网格查询而非 Collider 重叠判断——这是性能关键Unity 的Physics.CheckSphere()在每帧对 5 个角色做 4 方向检测CPU 占用比查数组高 3–5 倍。// Assets/Scripts/Maze/MazeLoader.cs public class MazeLoader : MonoBehaviour { public TextAsset mazeData; private GridNode[,] grid; public void LoadMaze() { string[] lines mazeData.text.Split(\n); int width lines[0].Length; int height lines.Length; grid new GridNode[width, height]; for (int y 0; y height; y) { string line lines[y].Trim(); for (int x 0; x width x line.Length; x) { char c line[x]; grid[x, y] new GridNode { position new Vector3(x, 0, y), isWall c #, hasDot c ., hasPowerPellet c o }; } } } }提示GridNode.position是世界坐标但MazeLoader会在Awake()中自动将grid[x,y].position transform.position确保迷宫相对于 GameObject 原点对齐。如果你移动了MazeLoader对象务必调用RebuildGrid()重新校准。3. 运行前必做的三步配置改路径、设引用、调参数跳过就编译报错下载解压后你拿到的是一个PacmanCore文件夹不是完整 Unity 工程。要让它跑起来必须手动完成以下三步——这不是可选操作是编译通过的硬性前提。很多新手卡在这一步反复刷新 Package Manager 或重装 Unity其实只是没配好引用。3.1 将 PacmanCore 拖入现有 Unity 工程的 Assets 目录切记不要双击 .unitypackage 文件。这个资源包是源码文件夹结构不是 Unity 官方包格式。正确做法是在 Unity 编辑器中打开你的目标工程建议空工程或最小化功能的测试工程在 Project 窗口右键 →Show in ExplorerWindows或Reveal in FindermacOS将解压后的PacmanCore文件夹整体拖入该工程的Assets目录下Unity 自动刷新后在 Hierarchy 中右键 →Create Empty命名为GameRoot将Assets/PacmanCore/Prefabs/GameManager.prefab拖入GameRoot下作为根对象。此时场景中会出现GameRoot其下挂载GameManager含GameStateManager,ScoreManager,MazeLoader等以及Pacman,Blinky,Pinky,Inky,Clyde五个预制体。3.2 为 GameManager.prefab 补全缺失的 ScriptableObject 引用GameManager.prefab中的MazeLoader组件有一个public TextAsset mazeData字段初始为空。你必须手动赋值在 Project 窗口中展开Assets/PacmanCore/Resources/Maze/选中maze.txt文件它是一个 TextAsset 类型资源在 Inspector 中确认TextAsset类型已识别右上角显示「Text」图标回到 Hierarchy选中GameRoot→GameManager在 Inspector 中找到MazeLoader组件 → 将maze.txt拖拽到Maze Data字段。注意如果拖不进去说明maze.txt没被识别为 TextAsset。解决方案选中该文件 → Inspector 底部点击Override→Text→Apply。这是 Unity 2021 的常见小坑不是你工程的问题。3.3 调整 Camera 与 Canvas 的渲染层级避免 UI 被遮挡默认Main Camera的Culling Mask包含Everything但Canvas的Render Mode是Screen Space - Overlay这会导致 UI 元素分数、生命值被 Pacman 模型挡住。必须修改选中Main Camera→ Inspector →Culling Mask→ 取消勾选UI选中Canvas→ Inspector →Render Mode→ 改为Screen Space - Camera将Canvas的Plane Distance设为100确保在相机前方将Canvas的Event Camera拖入Main Camera。此时运行游戏你会看到左上角实时分数、右上角剩余生命、中央 Pacman 移动——这才是可调试的起点。4. 避坑指南五个血泪经验总结解决 90% 新手首次运行失败这份源码在某高校实验室经过 3 届学生验证但每次新环境导入仍有高频翻车点。以下是我在帮 12 个不同团队部署时记录的真实问题清单按「现象 → 原因 → 解决」结构整理拒绝模糊描述。4.1 现象Pacman 移动卡顿、转向延迟严重像在泥里走原因PacmanController中moveSpeed默认设为5f但FixedUpdate()时间步长Time.fixedDeltaTime在未设置Project Settings Time Fixed Timestep时可能为0.02s导致每帧位移5 × 0.02 0.1单位视觉上极慢同时turnBuffer转向缓冲时间设为0.2f意味着按下方向键后需等待 0.2 秒才响应造成「按键失灵」错觉。解决打开Edit Project Settings Time将Fixed Timestep改为0.0166667即 60 FPS然后在PacmanControllerInspector 中将Move Speed调至8fTurn Buffer降至0.05f。4.2 现象幽灵全部静止不动或只在原地抖动原因GhostController的currentBehavior字段在 Inspector 中未正确赋值。虽然代码里有Start()初始化逻辑但如果behaviorType枚举值被误设为None或Custom默认值switch分支不匹配currentBehavior保持nullUpdate()中调用currentBehavior.GetTargetPosition()抛出NullReferenceExceptionUnity 静默吞掉异常幽灵表现为冻结。解决选中每个幽灵 GameObject如Blinky在 Inspector 中找到Ghost Controller组件 →Behavior Type下拉菜单必须手动选择对应类型Blinky 对应Blinky不可依赖默认值检查currentBehavior字段右侧是否显示「BlinkyBehavior」而非「None」。4.3 现象吃到能量豆后幽灵变蓝但 Pacman 碰到仍死亡无敌无效原因PowerPelletManager的powerDuration默认为10f但PacmanController中的无敌判定逻辑写在OnTriggerEnter()里而能量豆触发器PowerPelletCollider的Is Trigger属性被误关。.txt迷宫加载时PowerPellet节点仅标记hasPowerPellet true但未自动生成触发器 Collider需手动添加。解决在 Project 窗口找到Assets/PacmanCore/Prefabs/PowerPellet.prefab→ 双击打开 → 选中根对象 → Add Component →Sphere Collider→ 勾选Is Trigger→Radius设为0.3保存 prefabCtrlS然后在 Hierarchy 中选中GameRoot→GameManager→MazeLoader→ 点击Rebuild Maze按钮强制重载。4.4 现象分数不增加或豆子吃掉后又恢复原因MazeLoader加载迷宫时GridNode.hasDot和hasPowerPellet是只读标志但PacmanController的吃豆逻辑EatDotAtPosition()未同步更新grid[x,y]的状态位导致同一位置多次判定为「有豆」。更隐蔽的是ScoreManager的AddScore()方法被调用但UIManager未订阅ScoreManager.OnScoreChanged事件UI 文本不刷新。解决打开Assets/Scripts/Characters/PacmanController.cs定位EatDotAtPosition()方法在scoreManager.AddScore(dotValue)后添加gridNode.hasDot false; gridNode.hasPowerPellet false;然后打开Assets/Scripts/UI/UIManager.cs确认Start()中有ScoreManager.Instance.OnScoreChanged UpdateScoreText;4.5 现象游戏启动后立即报错MissingReferenceException: The object of type GameObject has been destroyed but you are still trying to access it原因GameStateManager使用DontDestroyOnLoad()保活但GameRoot下的GameManager预制体被多次实例化比如在Scene中手动拖了一个又在Start()里Instantiate()了一个导致多个GameStateManager实例竞争单例后创建的覆盖先创建的旧实例被销毁后其他脚本仍持有其引用。解决确保GameRoot下只有一个GameManager实例删除所有其他场景中的GameManager检查Assets/Scripts/Managers/GameStateManager.cs的Awake()方法确认if (Instance null)后有DontDestroyOnLoad(gameObject)且无其他Instantiate()调用。5. 幽灵 AI 行为调试技巧用 Gizmos 可视化路径与目标点三分钟定位逻辑偏差当你开始定制幽灵行为比如让 Inky 更激进或给 Pinky 加入随机扰动光看控制台日志和游戏表现很难判断是GetTargetPosition()计算错误还是 A* 寻路失败或是MoveTowardsTarget()的速度限制导致跟不上。这时候Gizmos 是你的黑匣子——它不改变运行逻辑却能把隐藏数据画在 Scene 视图里比断点调试快十倍。5.1 开启 Gizmos 并绘制幽灵目标点Unity 默认关闭 Gizmos 绘制需手动开启。在GhostController的OnDrawGizmosSelected()方法中添加// Assets/Scripts/Characters/Ghosts/GhostController.cs private void OnDrawGizmosSelected() { if (currentBehavior null || pacmanTransform null) return; Vector3 targetPos currentBehavior.GetTargetPosition(this, pacmanTransform); // 绘制目标点黄色球体 Gizmos.color Color.yellow; Gizmos.DrawSphere(targetPos, 0.2f); // 绘制从幽灵到目标的射线绿色 Gizmos.color Color.green; Gizmos.DrawLine(transform.position, targetPos); // 绘制 Pacman 位置红色 Gizmos.color Color.red; Gizmos.DrawSphere(pacmanTransform.position, 0.15f); }注意OnDrawGizmosSelected()只在选中该 GameObject 时绘制避免场景 clutter。若想始终显示改用OnDrawGizmos()但记得加if (Application.isEditor)判断防止构建后报错。5.2 可视化 A* 寻路路径实时看到幽灵「脑内地图」GhostController内部调用Pathfinder.FindPath()获取节点列表但该列表不暴露给外部。我们扩展Pathfinder类添加静态lastFoundPath字段用于 Gizmos 绘制// Assets/Scripts/Maze/Pathfinder.cs public static class Pathfinder { public static ListGridNode lastFoundPath new ListGridNode(); public static ListGridNode FindPath(GridNode start, GridNode end, GhostController ghost) { // ... 原有 A* 逻辑 ... lastFoundPath path; // 关键缓存最后路径 return path; } } // 回到 GhostController.cs追加 private void OnDrawGizmosSelected() { // ... 前面的目标点绘制 ... // 绘制寻路路径蓝色线段 if (Pathfinder.lastFoundPath.Count 1) { Gizmos.color Color.blue; for (int i 0; i Pathfinder.lastFoundPath.Count - 1; i) { Gizmos.DrawLine(Pathfinder.lastFoundPath[i].position, Pathfinder.lastFoundPath[i 1].position); } } }此时选中任意幽灵在 Scene 视图中你会看到黄色球体目标点、绿色射线幽灵→目标、红色球体Pacman、蓝色折线实际行走路径。如果发现蓝色路径绕远、或黄色目标点偏离预期比如 Pinky 的目标点没出现在 Pacman 前方问题一定出在GetTargetPosition()的数学计算上而不是寻路引擎。5.3 用 Debug.LogFormat 输出关键变量替代 Console.WriteLine新手常犯的错是Debug.Log(target: targetPos)但字符串拼接在频繁调用的Update()中产生 GC 压力。正确做法是Debug.LogFormat()它使用格式化字符串不触发分配// 在 GhostController.Update() 开头添加 Debug.LogFormat(Ghost {0}: Target{1:F2}, Pacman{2:F2}, Dist{3:F1}, behaviorType, targetPos, pacmanTransform.position, Vector3.Distance(transform.position, targetPos));输出效果Ghost Pinky: Target(3.00, 0.00, 5.00), Pacman(2.12, 0.00, 4.87), Dist1.2。F2表示保留两位小数避免浮点数打印过长Dist是实时距离帮你判断幽灵是否进入「追击阈值」。从那以后我每次改幽灵行为都强制走一遍「Gizmos 绘制 LogFormat 输出 场景中选中对象观察」三步流程——哪怕只是调一个系数也绝不凭感觉。因为 Pac-Man 的 AI 魔力就藏在那些 0.1 单位的偏移和 0.05 秒的延迟里。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?