☰
用MCP和Claude Code从零搭建Unity游戏:AI辅助开发完整工作流
2026/10/12 14:51:42 网站建设 项目流程

1. 开局:一个空文件夹,Claude Code 和 MCP 到底能干什么

先交代一下背景。最近我给自己安排了一个小实验:手里只有一台装好了常见开发工具的电脑,桌面上新建了一个空文件夹,名字就叫 Game01,里面什么都没有。目标很明确——在不手动写业务代码的前提下,用 Claude Code 配合 MCP(Model Context Protocol,模型上下文协议)把 Unity 工程从零搭起来,最后得到一个能打开、能操作、能通关的 3D 收集小游戏。

这个实验的动机其实来自日常的挫败感。平时用大模型写代码,最典型的问题是"答案看着对,但脚本就是跑不起来""代码块复制过去了,工程里一片红色报错"。原因很简单,模型再聪明,它也看不见我硬盘上的文件结构,不知道 Unity 编译到哪一步了,更不知道运行时场景里到底发生了什么。MCP 就是用来补上这层"手"和"眼"的协议——它让 AI 不仅能聊天,还能直接读写本地文件、执行命令、查询工具状态。

Claude Code 在这条链路里的角色更像一个"会写代码的施工队长",MCP 则是它伸向外部世界的一组工具接口。配合 Unity 场景的话,我可以让 AI 反复做这样一件事:写脚本 → 触发编译 → 读错误日志 → 改代码 → 再编译,直到编辑器日志里没有报错。整个过程我只负责确认方向、设定验收标准,偶尔介入一下它改不动的地方。

说句实话,"空文件夹"这个卖点确实抓眼球,但真正让我兴奋的是这套工作流沉淀下来之后对原型期开发的效率提升。今天这篇不聊什么宏大架构,就把这个实验的完整过程拆开:环境怎么配、三个 MCP 工具怎么分工、AI 建工程的核心工作流长什么样、以及哪些坑我替你先踩过了。

提示:我会假设你了解 Unity 的基本操作——新建项目、拖拽物体、给对象挂脚本。如果你连 C# 和 Unity 都不太熟,也不影响你理解核心思路,只是代码部分需要多看两眼。

2. 环境准备:三个 MCP 工具,把 AI 变成"看得见、动得了手"的施工队

2.1 Unity 版本选择与工程初始化

我先装了 Unity Hub,在 Hub 里创建了一个 3D Core 空模板项目,选的版本是 Unity 6 LTS 对应的稳定版。没选最新预览版的原因很实际:预览版的 C# API 和资源管理系统经常变,AI 训练语料里的答案容易过时。LTS 版本虽然老一点,但绝大多数社区资料和文档片段都能直接对上,这对 AI 生成代码的命中率是决定性的——它写出来的 API 必须跟当前引擎版本一致才行。

这里有一个绕开大坑的经验:不要让 AI 去创建整个 Unity 工程,而是先用 Unity Hub 正常创建一次空工程,之后让 AI 只折腾 Assets 和 Packages 这两个目录。为什么?因为一个完整的 Unity 工程不只是 Assets 和 ProjectSettings,还有庞大的 Library 目录(资源导入缓存)、Logs 目录和 Temp 目录。Library 里的很多二进制索引是 Unity 自己管理的,AI 手写不出来,硬拼只会制造一堆不可逆的错误。正确姿势是:Hub 创建一个干净工程,AI 负责在合法边界内折腾。

2.2 MCP 配置:三个核心工具的分工与 JSON 写法

Claude Code 的 MCP 配置维护在一个 JSON 文件里,通常放在项目根目录的.mcp.json。我这次用的是项目级配置,好处是换机器、换项目时不会污染其他工程。JSON 结构大致是这样:

{ "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "D:/Game01"] }, "executor": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-command", "--allowed-dir", "D:/Game01"] }, "unity-bridge": { "command": "node", "args": ["D:/Game01/Tools/unity-mcp-server/index.js"] } } }

我配了三类工具,分工如下:

MCP 服务职责为什么要它
filesystem读写工程内的文件、目录、JSONAI 写文件的基本操作原语
executor/command执行 shell 命令触发 Unity 批处理编译、读取日志、跑 git
unity-bridge与打开中的 Unity 编辑器通信查询场景物体、触发 Play 模式、取回编辑器日志

第三个 unity-bridge 严格来说不是官方标配,而是社区里很多人做的"桥梁"服务。原理是在 Unity 编辑器里装一个本地 Socket 插件,PC 端的 MCP 服务把 AI 的意图转成 JSON 请求发给编辑器,编辑器执行完再回传结果。我在这个实验里自己写了个小号版本,只实现了三个接口:查询当前场景的物体列表、触发 Play 模式、读取 Editor.log 末尾 200 行。就这三个接口,已经能让 AI 从"盲写代码"变成"看着场景改代码"。

提示:.mcp.json里的路径建议用绝对路径,尽量避开带空格的中文目录。MCP 子进程的启动目录问题很折磨人,我后面在踩坑部分会专门讲。

2.3 为什么是"三个工具"而不是一个万能工具

可能有人觉得把 filesystem、executor、unity-bridge 分开很啰嗦,不如搞一个"Unity 全功能 MCP"一步到位。我一开始也这么想,后来发现术业有专攻:filesystem 读小文件快、不容易出错;executor 适合跑 Unity 批处理和读日志;unity-bridge 适合跟运行中的编辑器实时交互。把它们拆开,任何一环出问题,AI 能明确说出是读写失败还是执行超时,而不是糊成一团报一个"连接失败"。

分包还有一个隐藏好处,就是权限控制。filesystem 我只给了它 D:/Game01 一个目录,executor 也只在工程目录内执行命令,这样即使 AI 产生幻觉想把整块磁盘删了,它也没那个权限。做原型可以适当放开,但底线不能丢。

3. 让 AI 自己建工程骨架:从目录结构到核心脚本

3.1 开工第一件事:让 AI 先交一份"施工方案"

环境配好后,我在 Claude Code 里发的第一条指令不是"做一个游戏",而是"检查 D:/Game01 工程,给出当前 Assets 目录结构,然后规划一个小型 3D 收集游戏的完整文件清单"。为什么要先要清单?因为 AI 如果不知道 Unity 工程里哪些文件是必需、哪些是可选的,很容易漏掉 Packages/manifest.json 或 ProjectSettings 里的关键配置。

它给出来的清单长这样:

Assets/ Scenes/Main.unity Scripts/PlayerController.cs Scripts/CameraFollow.cs Scripts/Gem.cs Scripts/GameManager.cs Scripts/Enemy.cs Editor/LevelBuilder.cs Packages/manifest.json ProjectSettings/ProjectVersion.txt

这个清单其实暴露了 AI 的一个习惯——它倾向于把可配置数据全写死在 MonoBehaviour 里。对原型来说没问题,但如果你要做正经项目,需要提前在 prompt 里加约束,比如"把数值配置抽到 ScriptableObject"。这个后面再展开。

3.2 PlayerController 的第一版与"手感改造"

这一节是整个实验的重头戏。AI 生成的第一版 PlayerController.cs 长这样:

using UnityEngine; public class PlayerController : MonoBehaviour { public float moveSpeed = 8f; public float jumpForce = 5f; private Rigidbody rb; private bool isGrounded = true; void Start() { rb = GetComponent<Rigidbody>(); } void Update() { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); Vector3 move = new Vector3(h, 0f, v).normalized; rb.velocity = new Vector3(move.x * moveSpeed, rb.velocity.y, move.z * moveSpeed); if (Input.GetButtonDown("Jump") && isGrounded) { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); isGrounded = false; } } void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Ground")) { isGrounded = true; } } }

乍一看挺正常,但往细了说有三个影响"能玩"的问题。第一,它用Update处理物理移动,规范做法是用FixedUpdate,不然角色高速移动时刚体穿透和碰撞判断都会出问题。第二,isGrounded只在碰撞进入时置 true,如果角色从平台边缘走出去,会保留"在地面上"的错误状态,导致空中可以无限连跳。第三,直接改rb.velocity会跟外力互相覆盖,出斜坡时速度会突然乱飙。

这些如果只是让 AI 输出一段代码,它大概率不会意识到。但我的流程是"写完 → 批处理编译 → 通过 unity-bridge 进入 Play 模式跑 3 秒 → 查日志",闭环跑下来,控制台没报错,可我一上手玩就发现手感稀烂。于是我把测试感受写成自然语言反馈给它:

"角色离地后还能再跳一次;下坡时速度会突然变快;撞到墙有轻微卡顿;希望移动时有加速度而非瞬间满速。"

AI 收到反馈后改成FixedUpdate + 射线检测地面 + 加速度插值的写法,手感立刻不一样了。这一步让我深刻体会到,MCP 闭环的价值不是让 AI 一次写对,而是让 AI 能"看到反馈然后改对"。

3.3 CameraFollow:盯住目标,但别让玩家头晕

第二个脚本是 CameraFollow。这个脚本的坑比想象中多。AI 最初给的是非常流行的"直接把摄像机设成角色的子物体",但这种方式在 3D 物理场景里会抖动,因为刚体更新和摄像机渲染不在同一帧。修正后的版本:

using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target; public float smoothTime = 0.12f; private Vector3 velocity = Vector3.zero; private Vector3 offset; void Start() { offset = transform.position - target.position; } void LateUpdate() { Vector3 targetPos = target.position + offset; transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime); } }

LateUpdate配合SmoothDamp是标准做法:不在物理更新阶段追目标,而在所有物体更新完之后的 LateUpdate 里做平滑追赶,玩家不会觉得镜头在甩头。还有一个细节:offset必须在 Start 里基于初始位置记录,而不是硬编码new Vector3(0, 10, -10)。这样在场景里调整过摄像机初始位置后,不需要改任何代码。AI 如果不被提醒,很容易写出"死坐标",一挪摄像机就全歪。

4. 场景装配:不手写 YAML,用 Editor 脚本让 AI"搭积木"

4.1 手写 .unity 场景文件的失败尝试

到了场景装配这一步,我遇到了整个实验里最诡异的坑。AI 第一次尝试直接生成.unity文件,内容是 YAML 格式的 GameObject、Transform、MeshRenderer 条目。我让它把文件放到 Assets/Scenes 下,然后在 Unity 里打开——编辑器直接报"scene file is corrupt",连场景画面都不给看。

原因其实很经典。Unity 场景文件开头有:

%YAML 1.1 %TAG !u! tag:unity3d.com,2011:

然后每个对象都带m_FileID: &&& FileID: ...这样的引用链。这些 FileID 和 GUID 不是随意生成的,它们必须和对应资源的.meta文件里的 GUID 严格一致。AI 可以仿写 YAML 的样子,但不可能凭空知道某个材质的 GUID,除非它先通过 filesystem 去读工程里所有的.meta文件。就算读了,只要有一处引用写错,Unity 就会把整个场景标记为损坏。这是典型的"文本长得像但语义上不可靠",纯手写场景文件这条路我直接判了死刑。

4.2 用 Editor 脚本绕过 YAML 深渊

既然手写 YAML 不行,那就让 AI 写一个Editor/LevelBuilder.cs——一个通过[MenuItem]暴露在菜单里的工具脚本,在编辑器里执行时自动把整个场景搭出来。完整流程变成:

  1. AI 通过 filesystem 写 LevelBuilder.cs;
  2. 手动(或让 executor 触发)在 Unity 菜单里点一下 "Tools/LevelBuilder/Build Level";
  3. LevelBuilder 用new GameObject()、AddComponent<T>()、EditorSceneManager.SaveScene()创建并保存场景;
  4. 所有 GUID 和引用关系由 Unity 编辑器自动管理,完全绕开手写 YAML。

这个方案的本质,是让 AI 站在官方 C# API 之上搭积木,而不是在文本流里碰运气。下面是一个简化版:

using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; public static class LevelBuilder { [MenuItem("Tools/LevelBuilder/Build Level")] public static void Build() { Scene newScene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single); // 地面 GameObject ground = GameObject.CreatePrimitive(PrimitiveType.Cube); ground.name = "Ground"; ground.transform.position = Vector3.zero; ground.transform.localScale = new Vector3(30f, 1f, 30f); ground.tag = "Ground"; // 玩家 GameObject player = GameObject.CreatePrimitive(PrimitiveType.Capsule); player.name = "Player"; player.transform.position = new Vector3(0f, 1f, 0f); var rb = player.AddComponent<Rigidbody>(); rb.mass = 1f; rb.collisionDetectionMode = CollisionDetectionMode.Continuous; player.AddComponent<PlayerController>(); // 主相机 Camera cam = Camera.main; if (cam != null) { var follow = cam.gameObject.AddComponent<CameraFollow>(); follow.target = player.transform; cam.transform.position = player.transform.position + new Vector3(0f, 8f, -10f); } // 一串宝石 for (int i = 0; i < 8; i++) { GameObject gem = GameObject.CreatePrimitive(PrimitiveType.Sphere); gem.name = "Gem_" + i; gem.transform.position = new Vector3(i * 2f - 7f, 1.5f + Mathf.Sin(i) * 0.5f, 0f); gem.AddComponent<Gem>(); } EditorSceneManager.SaveScene(newScene, "Assets/Scenes/Main.unity"); AssetDatabase.Refresh(); } }

注意 Player 部分,Rigidbody的collisionDetectionMode我特意改成 Continuous。AI 一开始写的是默认值(离散),小球快速下落时会直接穿过地面。这个细节如果靠人来翻文档要花一些时间,但因为我把"能玩"放进了验收标准里,AI 被逼着考虑这种边界情况,才补上了这一行。

4.3 编译、批处理、日志三件套

场景脚本写完就要验证。这时候不能每次都让 AI 打开 Unity 编辑器去点按钮,太慢了。更高效的是 Unity 的命令行批处理模式:

Unity.exe -batchmode -projectPath D:/Game01 -executeMethod LevelBuilder.Build -quit -logFile D:/Game01/Logs/build_level.log

executor MCP 负责跑这条命令,跑完再用 filesystem 去读Logs/build_level.log的末尾。如果编译失败,日志里会有error CS1234:这样的片段,AI 读完后会顺着修代码,然后再次触发编译。这一步是整个 MCP 工作流的"闭环发动机":写代码 → 编译 → 读日志 → 修代码,循环往复。

实际操作中有几个容易翻车的参数细节:

  • -executeMethod后面的类名必须是静态类里的静态方法;
  • -batchmode下不要调用任何需要弹窗的 API,否则编辑器会挂起等待人工确认;
  • -quit要写在-executeMethod之后,否则方法还没执行完编辑器就退了;
  • 日志路径最好用绝对路径,否则默认落到系统临时目录,AI 找不到。

5. 从"能跑"到"能玩":手感、状态管理、反馈三座大山

5.1 手感:AI 最容易忽略的隐藏工作量

编译通过、场景能打开,离"能玩"还有一大段距离。我第一次跑起来的感觉是:角色像在冰面上滑行,跳跃软绵绵,吃掉宝石完全没有成就感。这些都是"能跑"和"能玩"的差距。

我整理了一张参数调优表,让 AI 按这张表逐项改:

参数初始值最终值说明
moveSpeed86太高导致转弯困难
acceleration无12让角色加速而非瞬间满速
jumpForce57.5配合加重力后跳跃更干脆
gravityScale12.2下落更快,落点更可控
smoothTime0.120.18摄像机稍微跟手一点

调参方法是笨但有效的:改一个参数 → 批处理编译 → unity-bridge 触发 Play 模式 1 秒 → 抓帧截图 → 让 AI 看图 → 再改。MCP 可以把 Unity 的 Game 视图截图转成临时 PNG 文件,再让 filesystem 把图片路径交给 AI。这一步让 AI 从"盲调"变成"看图说话",它看到角色在地面上悬空又落下,就会意识到是重力或者地面检测出了问题。能看图这种能力,对调游戏手感是质的飞跃。

5.2 状态管理:GameManager 不能只写一个单例

第三个核心脚本是 GameManager,负责"游戏开始、收集进度、游戏结束"三个状态。AI 一开始写成了经典单例:

using UnityEngine; public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } public int totalGems = 8; public int collectedGems = 0; void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; } public void AddGem() { collectedGems++; if (collectedGems >= totalGems) { Time.timeScale = 0f; } } }

单例本身没问题,但注意一个隐蔽的坑:如果GameManager放在场景里,而玩家在胜利时切换到另一个场景,这个Instance就没了。更稳的方案是DontDestroyOnLoad或者干脆保持单一场景。原型阶段我选了后者,省掉一堆场景切换的引用问题。另外,用Time.timeScale = 0f暂停游戏虽然方便,但它会把所有跟Time.deltaTime相关的逻辑一起冻住,UI 的飘字动画也会卡住,后来我改成了用状态机的isGameOver变量来控制,而不是冻结时间。

5.3 反馈密度:飘字、音效、粒子,AI 不会主动想到

一个游戏好不好玩,很大程度取决于"反馈密度"。AI 在这方面的弱点是它不会主动想到,你得在 prompt 里按优先级排队:飘字 → 音效 → 粒子 → 屏幕震动。我这次加了两个最简单的反馈:

  • 吃掉宝石时,玩家头顶飘出 "+1" 的 UI Text,0.5 秒后淡出;
  • 敌人碰到玩家时,玩家闪烁红色并被击退。

这两个效果代码都很短,但体感立竿见影。AI 第一次写的飘字是用 Canvas 动态创建 Text,没有处理 Canvas 的渲染模式和缩放,字跑到屏幕外面去了。这一看就是"AI 看着截图调"才修好的问题。没有截图反馈,这种位置类 bug 靠文字描述能来回折腾好几轮。

6. 踩坑实录:这 7 个坑我替你先踩了

6.1 .meta 文件与 GUID 引用断裂

这是 AI 操作 Unity 工程最大的坑,没有之一。Unity 里的每个资源文件都对应一个隐藏的.meta文件,里面记录着 GUID。如果 AI 用 filesystem 复制、移动、删除文件时没同步处理.meta,Unity 刷新后要么找不到引用,要么一堆组件显示 "Missing Script"。

我遇到的具体场景:AI 想重命名 PlayerController.cs,用命令把它改成 PlayerControl.cs,但没动旁边的 PlayerController.cs.meta。Unity 刷新后,场景里所有挂在 PlayerController 上的组件全部变灰。正确修法不是自己改 meta——那个 GUID 冲突了没人能保证全局唯一,而是让 AI 通过编辑器侧完成重命名,比如用 unity-bridge 调用AssetDatabase.RenameAsset。以后凡是涉及 Assets 目录下文件的改名、移动,都走编辑器 API,不要直接动文件系统。

6.2 手写 .unity 场景 YAML 的"伪成功"

前面说了手写 YAML 导致 corrupt,但还有更隐蔽的情况:AI 写的 YAML 文本层面能被 Unity 解析,但物体层级错乱、引用丢失,场景打开后一片空白或者只有一个空节点。这种比直接报错还难查,因为你以为成功了,运行起来全是空引用异常。我的建议是:无论场景复杂还是简单,一律用 Editor 脚本生成。不手写 YAML 是底线,没有例外。

6.3 批处理模式的卡死与会话残留

Unity 的-batchmode有时候会卡在 "Importing Assets" 或 "Reloading Scripts" 阶段,尤其是第一次导入资源时。我遇到一次批处理命令跑了 3 分钟没退出,executor MCP 一直挂着,最后差点超时断连。两个解决办法:一是给 executor MCP 设置命令超时,比如 60 秒;二是在命令里加硬性护栏,比如在批处理前先清理Temp目录。如果上次批处理意外崩溃,残留的Temp和日志文件会影响下一次启动,干脆先rd /s /q D:/Game01/Temp再启动。

6.4 社区 MCP 工具与 Unity 版本的兼容性

社区里的 unity-bridge 工具质量参差不齐。我自己写的最小实现只有三个接口,工作量不大,但有一条必须记住:Socket 传输的 JSON 里不要塞二进制数据。截图这种内容,先写成临时 PNG 文件,再把路径传给 AI,让 filesystem 去读文件。直接在 JSON 里 base64 编码会瞬间撑爆上下文窗口,模型回答质量立刻下降,会话也会变得很迟缓。

6.5 主题窗口:MCP 不是接得越多越好

Claude Code 的上下文窗口是有限的,每次 MCP 工具调用返回的内容都会占用上下文。如果给 filesystem 开放整块磁盘且不做目录过滤,AI 光读 Library 目录下的缓存文件就能把窗口塞满。我只开放了 Assets、Packages、Logs 三个子目录,其余路径全部在配置里加排除规则。另外,我要求 AI 在每次读日志时只取末尾 200 行,不要一次性读整个文件。这些小习惯能显著延长单次会话的有效时长,不然每 10 分钟就得开新会话重新带入项目背景,效率非常受伤。

6.6 中文路径与编码噩梦

.mcp.json里不要用带空格的中文路径,这条我用实际教训换来了。Windows 上如果工程放在D:\游戏项目\下,Unity 批处理经常因为编码问题找不到脚本文件。既然工作流里大量依赖 shell 命令,路径里就尽量不要出现中文和空格。这不是偏见,是帮 AI 省掉一次无意义的报错排查。

6.7 AI 的"自我感觉良好"是头号杀手

最后一个坑是心态层面的。AI 在连续两轮编译通过后,会非常自信地告诉你"功能已经完善"。但实际上它可能只验证了编译,没验证运行逻辑。我培养出的对策是:每一个功能都必须有可观测的数据验收。比如"游戏胜利"不能只看代码逻辑,要让 AI 自己描述——如果玩家吃掉 8 个宝石,界面上哪一行数字变成 8,控制台输出什么。拿不到完整闭环描述,就不算做完。

7. 这套工作流的边界与可复制性

实验做到最后,我的 Game01 文件夹里多了一个约 100MB 的 Unity 工程,里面是一个能跑的 3D 收集游戏:玩家移动跳跃吃宝石,有一个来回巡逻的敌人,碰到会闪红并击退,收集满 8 个宝石后显示胜利界面。整个过程里,我手动做的事情只有三件:安装 Unity Hub、创建空工程、在编辑器菜单里点了一次 LevelBuilder。

但这个结论不能过度夸大。我要诚实地划出几条边界:第一,美术资源没有一个是我让 AI 直接生成的,用的都是 Unity 内置原生几何体和免费基础素材;第二,玩法设计从一开始就很明确,AI 是在我的验收标准下做实现,而不是从零发明玩法;第三,整个工程代码量大概在 400 行左右,属于典型小原型,这个量级 AI 完全可以驾驭,但如果目标是几万行的商业化项目,MCP 工作流只能解决局部模块,解决不了架构治理。

那什么时候这套工作流最划算?我自己的判断是:Game Jam 原型、新玩法验证、工具链脚本、教学 Demo。这些场景的共同点是需求变化快、规模小、允许丑但可玩。如果你在做大型多人项目,MCP 更适合用在"让 AI 辅助写单元测试、生成关卡数据、批量处理资源"这些局部环节,而不是让它接管整个工程。

最后分享一个我实验后沉淀下来的习惯:每次开工前,我会把验收标准写成一页README_ACCEPTANCE.md放到工程根目录里,用列表写清楚"能运行、能操作、能通关、无报错"的具体含义。AI 通过 filesystem 能直接读到这份文件,它会自己对照检查,而不是等我逐字提醒。这个习惯价值很大,相当于给 AI 装了一个"踩坑提醒器",省下来的沟通成本远超写那份文件用的十分钟。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询