UnityMCP:让 AI 通过 MCP 控制 Unity Editor
UnityMCP 由 Python MCP 服务和 Unity Editor C# Bridge 包组成。AI 根据工具定义选择操作,Python 校验参数并发出桥接命令,Unity 在编辑器主线程执行,再把状态、日志、截图或断言结果交还给 AI。
**公开脱敏适配模板:**文档、示例和源码中的
PROJECT_*/<PROJECT_*>均为通用代称,不对应任何真实项目。高级功能的项目接口必须由使用者在自己的私有环境中接入;公开版不包含真实绑定关系。本地实际运行版本与公开模板分离。
**兼容性先读:**编辑器连接、场景、播放模式、日志、截图等能力使用 Unity API;UI、Lua、登录、实体、相机和特效等高级能力包含 项目 UI / Lua 运行时适配接口,不是任意 Unity 项目安装后都能使用。反射消除了编译时依赖,但没有消除运行时依赖。
当前源码状态
| 项目 | 当前值 / 说明 |
|---|---|
| Python 包 | unity-mcp-py,元数据版本 0.10.0 |
| Unity UPM 包 | com.local.unitymcp-bridge,元数据版本 0.8.0 |
| 桥接协议 | 1.10.0,以桥包内 protocol.schema.json 为准 |
| 默认工具数 | 41,由实际 FastMCP 注册列表核实 |
| 可选工具 | profile_performance,开启后共 42 个 |
| Python | 3.11+;此次测试环境为 Windows / Python 3.14.3 |
| MCP Python SDK | mcp>=1.0.0,<2;使用 v1 FastMCP API,不能直接升级到 v2 |
| Unity | 包声明最低 2022.3;具体项目仍需 EditMode 与实机验证 |
| 版本性质 | 公开脱敏模板;项目绑定需自行适配,不能直接覆盖私有运行版本 |
此次整理保留当前多实例绑定、认证、恢复功能,并整合此前性能分支的两个改动:握手响应超时设为 5 秒;重复截图默认省略相同帧的图像数据。没有把旧分支整个覆盖到新工作区。
**不要直接使用旧 dist/ 中同名压缩包。**修改 Python 或 C# 后必须重新构建两端;协议相同也不意味着旧包已包含最新实现。具体测试与发布记录见 PUBLICATION_REPORT.md。
目录导航
快速安装
1. 安装 Python 服务
下载仓库源码后进入根目录,创建专用虚拟环境,不建议把开发依赖装入全局 Python:
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
macOS/Linux 使用 .venv/bin/python。Unix 用户必须先完成下文的认证目录权限配置,再启动 Bridge。
2. 安装 Unity Bridge
在 Unity Package Manager 中选择 Add package from disk...,打开本仓库的:
unity-bridge/com.local.unitymcp-bridge/package.json
或先执行 python scripts/pack_unity_bridge.py,再用 Add package from tarball... 导入新生成的 tgz。桥包依赖 Newtonsoft JSON、Editor Coroutines、Input System;版本以 package.json 为准。
需要打开真实 Unity 项目并等待脚本编译完成。ensure_ready 不会替你启动 Unity。
3. 配置 MCP 客户端
<MCP_ROOT> 表示本工具源码根目录,<PROJECT_ROOT> 表示使用者自己的 Unity 工程,<UNITY_EDITOR_PATH> 表示编辑器可执行文件。占位符必须替换后才能执行。
以下是通用 MCP stdio JSON 配置示意,路径替换为本机实际路径。不同客户端的配置文件位置不同。
{
"mcpServers": {
"unity-mcp-py": {
"command": "<MCP_ROOT>/.venv/Scripts/python.exe",
"args": ["-m", "unity_mcp_py.server"],
"env": {
"UNITY_MCP_PROJECT_PATH": "<PROJECT_ROOT>",
"UNITY_MCP_BRIDGE_HOST": "127.0.0.1",
"UNITY_MCP_BRIDGE_PORT": "17873",
"UNITY_MCP_CLIENT_ID": "mygame-editor-agent"
}
}
}
}
Codex 的等价项目配置采用 TOML:
[mcp_servers.unity-mcp-py]
command = '<MCP_ROOT>/.venv/Scripts/python.exe'
args = ['-m', 'unity_mcp_py.server']
[mcp_servers.unity-mcp-py.env]
UNITY_MCP_PROJECT_PATH = '<PROJECT_ROOT>'
UNITY_MCP_BRIDGE_HOST = '127.0.0.1'
UNITY_MCP_BRIDGE_PORT = '17873'
UNITY_MCP_CLIENT_ID = 'mygame-editor-agent'
一个稳定且独立的 UNITY_MCP_CLIENT_ID 对应一个逻辑客户端;多个客户端不要复用相同身份。未配置时会回退到会话变量或 PID,PID 改变后不能保证自动恢复原绑定。启动后先让 AI 调 select_bridge(scan=true)、核对项目,再绑定目标,而不是盲目接管第一个端口。
4. 多 Unity 实例与端口
- Bridge 优先监听本机
127.0.0.1:17873。 - 未显式指定端口且端口占用时,Unity listener 可回退至
17874..17883。 - Python 默认扫描覆盖
17873..17882;17883 需要明确指定,不要把扫描范围与回退范围混为一谈。 - 优先使用
bind_bridge持久绑定;只想切换当前连接时使用select_bridge。 - 使用
expected_bridge_id核对身份,不仅核对端口;不应自动设置force_takeover=true抢占其他工作会话。 - Python 当前只接受字面量 loopback IP,不接受 DNS 主机名或公网地址;旧文档中的远程开关不适用。
AI 控制的完整链路
用户自然语言
↓
外部模型 + MCP 客户端(选择工具、生成结构化参数)
↓ MCP / stdio
Python FastMCP:参数 schema → telemetry → 可选 JSONL recorder
↓ tools/*.py → bridge_call / BridgeSession
线程转发 + 异步锁串行化 + 身份握手 / HMAC
↓ TCP,换行分隔的自定义 JSON request/response
UnityMCPServer:接收请求 → 主线程队列
↓ EditorApplication.update
UnityMCPCommands.Dispatch:命令处理器 / 跨帧协程
↓
Unity Editor API、反射调用、项目 Lua VM
↓
结构化结果 / 日志 / PNG / 断言 → 模型决定下一步
**Bridge 中没有大模型,也不会自己理解自然语言。**控制来自模型选择的工具调用,执行来自可审查的 Python/C# 实现。MCP 协议与 Unity TCP 桥接协议是两层不同协议;不要把后者称为完整 MCP JSON-RPC 服务。
实现入口:
src/unity_mcp_py/server.py:工具注册、服务端指导语、记录/遥测中间件。src/unity_mcp_py/tools/:每个工具的参数处理及业务编排。src/unity_mcp_py/bridge/session.py:连接、串行调用、切换及取消清理。src/unity_mcp_py/_vendor/bridge_client.py:连接认证、握手、请求 ID、响应匹配、通知缓冲及超时。unity-bridge/com.local.unitymcp-bridge/Editor/UnityMCPServer.cs:本机 TCP listener、认证和主线程调度。- 同目录
UnityMCPCommands.cs:命令分派及编辑器/Lua 操作。 - 同目录
UnityMCPUIInspector.cs、UnityMCPWorldObserver.cs:项目 UI 和世界状态适配。
Unity 主线程每帧使用有限请求数量/时间预算;长时间等待通过协程跨帧完成。Python 异步并不代表 Unity 可以在任意后台线程安全执行 API。
重要工具如何实现
以下 Python 入口均为 src/unity_mcp_py/tools/<工具名>.py。表中的命令由 UnityMCPCommands.cs 分派,除非标注为 Python 本地操作。参数完整 schema 以客户端 tools/list 为准;客户端显示的 mcp__...__ 前缀不是服务器原始工具名。
A. 连接、身份与恢复(4 个)
| 工具 | AI 如何控制 / 实现 | 使用边界 |
|---|---|---|
ensure_ready |
BridgeSession.connect() 完成认证和 Handshake,调用 GetPlayState;可选进入 PlayMode |
engine_binary_path 当前不启动程序;项目路径参数不等于严格的目标身份验证。重复连接返回的握手信息可能是缓存状态。 |
select_bridge |
管理通道查询各端口状态,不占用业务槽;准备候选连接,握手成功后切换活动 session | 指定端口、预期 bridge ID;失败不能误把另一项目当成目标。 |
bind_bridge |
在选择逻辑之外持久化 client→bridge 绑定;文件锁、revision/CAS、失败补偿控制并发 | auto 遇多实例返回候选,需选择;restore 依赖稳定客户端身份;unbind 解除当前绑定。 |
bridge_admin |
run_bridge_admin 分派 pair/status/owners/diagnose/clear/repair;使用带认证的控制协议 |
clear 区分 binding/accepted/listener;破坏性动作核对实例与连接代次,不杀进程。不是公网远程管理工具。 |
B. 编辑器、场景与编译(8 个)
| 工具 | AI 如何控制 / 实现 | 使用边界 |
|---|---|---|
enter_playmode |
写操作调用 EnterPlaymode,设置编辑器播放状态 |
不证明游戏初始化、资源加载或登录完成,需继续观察。 |
exit_playmode |
调用 ExitPlaymode 返回编辑模式 |
会停止运行态,应先保存需要的证据。 |
open_scene |
Python 校验场景路径,发送 OpenScene,C# 使用 EditorSceneManager |
MCP 参数是 scene_path;不能假设桥接协议额外模式均被 MCP 暴露。操作会改变当前场景。 |
query_scene |
GetSceneState 枚举场景根对象/组件概要,并附带播放状态、日志位置 |
不是完整深度对象图,也不是游戏业务实体列表。 |
run_method |
RunEditMethod 查找已加载程序集的类型,通过反射调用 public static 方法,收集返回值和日志 |
不是 C# 编译器;有危险类型/命名限制,可用 Unity 进程环境变量 UNITY_MCP_RUN_METHOD_ALLOW 收窄类型前缀,但不是沙箱。 |
assert_no_compile_errors |
查询编译状态,发现编译错误时阻止当前检查通过 | 编译通过不等于运行逻辑正确。 |
get_console_logs |
TailEditorLog 读取内存日志窗口,Python 按游标、级别、正则和数量过滤 |
日志缓冲有上限;不能证明未保留的历史没有错误。 |
clear_console |
查询最新日志位置并推进 Python recorder 的虚拟日志游标,返回 previous_cursor/new_cursor | 不删除 Unity Console 日志,不产生写操作 token;它是逻辑游标重置,不是编辑器清屏。 |
C. 截图、证据和断言(5 个)
capture_screenshot
- 查询验证会话;根据
session.code_dirty决定是否需要人工批准截图。 - 调用 Unity
CaptureScreenshot,等待帧处理后取得 PNG。 - Python 输出
full(Base64 图像)、metadata(摘要/大小)、file_ref(本地 PNG 路径)。 - 默认
skip_if_unchanged=true:仅当 full 模式与该 Python 进程上次实际交付的 full 帧相同时才省略图片,返回unchanged。metadata/file_ref 不更新该交付记录;file_ref 每次返回有效文件路径。已交付并不保证客户端真的展示或模型已经查看;最终验收应设skip_if_unchanged=false并实际查看图片。 file_ref需要客户端另行读取本地图片;后续截图会清理之前跟踪的缓存文件,需留档时自行复制。
代码重载会置 dirty 标记,下一次截图可等待用户点击“继续”。这不是始终无人干预的截图服务。遇到 screenshot_available=false 或空图片不能当作已完成视觉验证。图片摘要当前按 Base64 文本计算,不能直接当作 PNG 文件原始字节哈希。
observe_evidence
顺序取得日志增量、场景根对象、播放状态和可选截图,再消费指定 write_op_token。默认不截图,默认精简日志和场景字段;verbose=true 可保留更完整信息。该工具提供证据,不替模型理解画面。可选截图失败可能表现为缺少图像,因此必须检查实际输出。
verify_assertions
Python 服务端执行显式断言并返回 passed/failures/results,避免模型逐条猜测:
expect_no_errors:保留的日志窗口中无错误。log_matches:匹配或排除某正则。play_state:播放状态符合预期。node_exists/node_visible:匹配指定 UI 节点。lua_expr:执行表达式并比较字符串化结果,必须注明 goal。
只证明所写断言,不证明画面正确。lua_expr 走真实 Lua 执行,并非强制只读沙箱。有效完成的断言检查会重新补充 Lua runway;断言失败时应看原因,而不是自动宣称验证成功。
list_pending_writes
读取 Python WriteOpGuard 中尚未消费的操作标记,帮助 AI 找出缺失的观察步骤。标记是进程内状态,不随重启永久保存。
escalate_via_click
同一目标验证反复失败时,执行调用方提供的 undo_lua,再用指定 UI 入口 simulate_click,可选重新执行断言。用于避免“内部函数把面板打开了,但绕过业务数据初始化”的假成功。undo 不是自动生成的事务回滚;点击也仍然是项目模拟接口。
D. Lua、热重载与开发登录(4 个)
| 工具 | 实现路径 | 前提和风险 |
|---|---|---|
lua_eval |
LuaEval → DefaultLuaDoString() → 反射获得 <PROJECT_LUA_VM> → xLua DoString() |
需要项目 VM 与 PlayMode;script 长度受限、goal 必填。runway 要求定期观察现实,但不是权限隔离;客户端超时不能保证终止 Unity 主线程死循环。 |
lua_reload_module |
校验模块列表后调用 <PROJECT_RELOAD_MODULE> |
不是所有 Lua 框架通用重载;检查 payload 中的 ok 与 traceback。传输成功不代表脚本成功;成功路径标记代码变更。 |
csharp_reload_scripts |
CsharpReloadScripts → CompilationPipeline.RequestScriptCompilation;force 路径处理播放模式切换 |
域重载可能影响连接和运行状态,不承诺任意状态无损热替换。之后重新检查编译并观察。 |
auto_login |
向 <PROJECT_LOGIN_SERVICE> 提交测试登录参数,轮询登录 token 是否存在 |
项目特定开发登录入口,不是通用账号密码表单。绕过 SDK/UI 的部分逻辑;被服务器接受不代表角色创建或进场完成。仅对授权测试环境使用。 |
auto_login 的默认参数来自 UNITY_MCP_LOGIN_USERNAME、UNITY_MCP_LOGIN_SERVER_ID、UNITY_MCP_LOGIN_SCENE_ID。不要把真实账号写进仓库。Lua runway 的配置为 UNITY_MCP_LUA_RUNWAY、UNITY_MCP_LUA_SOFT_LIMIT、UNITY_MCP_LUA_RUNAWAY_MAX;这是工作流约束,不是恶意脚本隔离。
E. UI、本地化与输入(6 个)
| 工具 | AI 调用后真实执行 | 不代表什么 |
|---|---|---|
list_ui_nodes |
UnityMCPUIInspector.ListNodes() 遍历项目 UI layer 下的 PROJECT_UI 节点,提取 instanceId、panelUid、name/path、文字、textId、可见性、交互属性和屏幕矩形 |
不是通用 UGUI/UI Toolkit 检查器;节点数量上限会截断结果。 |
resolve_localization |
根据 ID 和语言反射调用项目 PROJECT_LOCALIZATION_LOOKUP(int, PROJECT_LANGUAGE) |
不是自动翻译,也不会替你生成缺失的词条。 |
open_panel |
PROJECT_RUNTIME.global.ui:open(uid, info) 后用跨帧轮询检查是否打开/可见 |
内部开面板不一定走真实用户入口,不能证明业务数据链正确;info 仅支持平面基本类型。 |
simulate_click |
用 panel UID + refKey/path 找 PROJECT_UI 节点;raycast 模式调 PROJECT_CLICK_WITH_HIT_TEST(false),force 模式调 PROJECT_CLICK_DIRECT(false) |
不是操作系统鼠标注入;force 绕过命中检查。必须检查 clicked/nodeFound,不是看见 token 就算点中。 |
simulate_key |
PROJECT_RUNTIME.global.PROJECT_INPUT_SERVICE:PROJECT_TRIGGER_ACTION 依次触发 Performed 和 Canceled |
不是物理键盘/手柄输入;可能绕过焦点等输入 gate,仅覆盖真实 InputActionAsset,虚拟热键可能不触发。 |
set_input_text |
找到输入节点,设置 text、激活输入框,再调用 PROJECT_COMMIT_TEXT | 不是逐键输入,不验证 IME、每键事件或硬件输入路径。 |
路径基准不同:list_ui_nodes.path 相对于 UI layer root,包含 panel root 名称;simulate_click / set_input_text.path 相对于指定 panel root。调用前需转换路径,不能原样照抄。ref_key 来自项目 PROJECT_NODE_REFERENCE 配置,不能用节点 name 或 instanceId 代替。
这些工具适合让 AI 复现应用层交互路径。涉及硬件手柄、输入法、焦点或系统级鼠标行为时,需要用户或真正设备级 E2E 测试补充。
F. 世界状态、相机与特效(7 个)
| 工具 | 实现 | AI 应如何解读 |
|---|---|---|
observe_camera |
UnityMCPWorldObserver 查询项目 camera manager,合并反射字段,可计算目标投影 |
观察项目相机状态,不是任意 Camera 的全量管理工具。 |
list_entities |
构造 Lua 查询 PROJECT_RUNTIME.global.PROJECT_ENTITY_SERVICE,过滤 actor/class/template 并裁剪数量 |
返回逻辑实体目录,不等于 Hierarchy GameObject 列表。 |
observe_entity |
按 entity/actor/template 定位,采集位置、HP、生命/战斗/动画等状态 | 读取已有实体,不负责生成或修改它。 |
list_effects |
反射项目 effect manager 列出特效生成器/实例 | 是当前时刻快照,瞬态特效可能已结束。 |
observe_effect |
Lua 查询实例状态/时长,结合子 ParticleSystem 粒子计数 | 未看到不等于从未播放,应必要时改用时间窗口。 |
observe_window |
跨帧重复计算 Lua 表达式,返回采样值、everTrue、首次/末次命中等 | 可捕捉短暂状态,但采样有间隔;语法限制不是完全纯函数或安全证明。 |
operate_camera |
白名单 action 映射 PROJECT_RUNTIME.game.camera 方法,比较 before/after |
applied 依赖无错误且可观测变化;settle_ms 当前保留但未实现延迟等待,不承诺动画已稳定。 |
G. 人机协作(3 个)
start_verification_session:在 Unity 显示验证提示,监听人类接管输入,配置暂停/中止和截图 gate。提示窗不进入 Game View 截图。显式启动默认 gate 与截图工具按 dirty 自动调整是两条不同路径。end_verification_session:结束指定 session,清理验证状态。应传对应 session ID,避免误结束后来者。human_collab_gate:第一次保存截图/日志基线,返回待用户操作状态与 opaque ID;第二次接收真实用户的 Done/Skipped/Failed,再比较证据。基线约一小时过期。AI 不能自行伪造用户已完成的结论。
H. 录制和回放(4 个)
record_start 开启工具调用 JSONL 记录;record_stop 停止并返回摘要;list_sessions 列出已有记录;replay 从注册表查找同名工具重新调用,并比较结果。
录制不是视频。回放不是只读浏览。replay 默认 dry_run=false,可能再次进入播放、点击、改场景或执行代码;先设 dry_run=true 检查计划,再选择 skip_tools 和 stop_on_error。录制参数/结果没有完整敏感字段脱敏,不能直接公开。
I. 可选性能工具(1 个,默认关闭)
在 Python MCP 进程启动前设置 UNITY_MCP_ENABLE_PROFILER=1,重新启动后注册 profile_performance:
ProfilerCapabilities确认可用性;不可分析时返回原因,不编造指标。- 按 scenarios、cpu/memory/both、sample_frames 构造矩阵。
- Unity 用
ProfilerRecorder/ 相关采样命令跨帧收集指标,可临时改变质量、目标帧率和分辨率,结束时尝试恢复。 - 第一次成功采样建立 Python 内存 baseline,后续按场景 label 比较并报告回归。
- baseline 可跨 Bridge 断线保留,但不跨 Python 进程重启;
reset=true清空。
它不是自动性能修复器,也不自动证明根因;强退等情况不保证配置恢复。不要拿不同项目或不同工作负载的数据作无条件比较。
验证闭环与调用示例
写操作标记的真实含义
_write_op_helper.write_op() 先取日志游标,执行命令,成功返回后登记 write_op_token、requires_observation 和 line_number_before。AI 再把 token 传给 observe_evidence 关联后续观察。
**这不是强制安全事务。**未消费 token 不会统一拦截所有后续操作,payload 里的业务结果也可能失败。服务端 SERVER_INSTRUCTIONS 的最终截图规则是模型行为指导,不是不可绕过的“完成证明”。
示例流程(工具调用示意,不是 Python 导入调用)
select_bridge(scan=true)
bind_bridge(port=17873, expected_bridge_id="从扫描结果取得的真实 ID")
ensure_ready(project_path="<PROJECT_ROOT>")
assert_no_compile_errors()
enter_playmode()
observe_evidence(consume_tokens=["上一调用返回的 token"])
# 仅在对应项目 UI 适配存在时:
list_ui_nodes(panel_uid=实际面板编号)
simulate_click(panel_uid=实际面板编号, ref_key="真实节点引用", mode="raycast")
observe_evidence(consume_tokens=["点击返回的 token"])
verify_assertions(assertions=[{"type":"expect_no_errors"}])
capture_screenshot(mode="full", skip_if_unchanged=false)
工具接受 JSON 类型参数,示例中的中文占位值需换成运行时查到的数据。多屏流程逐步取证;最后实际查看图像。普通没有高级适配的 Unity 项目可先验证编辑器工具,不应直接复制项目 Lua 示例。
项目适配边界
公开文档只使用下列接口角色代称,不列出真实项目代码:
- Lua VM:
<PROJECT_LUA_VM>,表示项目的 Lua 执行入口。 - UI:
<PROJECT_UI_SYSTEM>,表示节点枚举、定位、点击和输入适配接口。 - 本地化:
<PROJECT_LOCALIZATION_SERVICE>,表示按词条 ID 和语言查询文本的接口。 - 运行时观察:
<PROJECT_CAMERA_SERVICE>、<PROJECT_ENTITY_SERVICE>、<PROJECT_EFFECT_SERVICE>。 - 重载:
<PROJECT_RELOAD_MODULE>,表示项目自己实现的模块重载能力。 - 登录:
<PROJECT_LOGIN_SERVICE>,表示授权测试环境的登录适配接口。
源码里使用合法示例标识符(不带尖括号);它们仍然是占位契约,不是任何真实项目的类名或调用链。通用 Unity/MCP API、协议字段和 MCP 工具名保持原名。
迁移时应修改 UnityMCPCommands 中 Lua 获取/调用入口、UnityMCPUIInspector 和 UnityMCPWorldObserver 的适配,再为目标项目添加 EditMode/PlayMode 测试。MCP 工具的 schema、错误语义和协议字段应保持同步。未适配时如实返回不可用,而不是返回空结果伪装成功。
安全与数据边界
- 仅供可信本机开发环境。只监听 loopback 不意味着同一 OS 用户下的恶意进程不可访问。
- 当前认证采用 nonce-bound HMAC,核对 bridge/client/instance/generation;不是 TLS,也不是远程托管服务设计。
lua_eval、run_method、业务重放拥有实际执行/修改能力。白名单、runway、token 不能替代 OS 隔离与备份。- 账号、认证文件、绑定文件、真实环境变量、截图、JSONL、Console 日志都可能含私人数据;不要提交到版本库或直接发给无关模型服务。
- 变更工具可能触发游戏后端请求。使用专门测试账号和环境,避免生产交易、付费资源或不可逆业务操作。
- 请求超时不等于 Unity 中的操作已经撤销;先查状态再决定是否重试。
macOS/Linux 必须完成的权限缓解
当前 C# Bridge 自动创建认证文件时尚未自动收紧 Unix 权限。在默认 umask/可遍历 home 下存在其他本机用户读取 token 的条件性风险。以下是部署要求,不是声称代码已经修复。
关闭 Unity/MCP,确认目录/文件属于当前用户、无额外 ACL 授权且不是共享/同步目录,然后执行:
umask 077
test ! -L "$HOME/.unity-mcp" || exit 1
mkdir -p "$HOME/.unity-mcp"
chmod 700 "$HOME/.unity-mcp"
if test -e "$HOME/.unity-mcp/auth-token"; then
test ! -L "$HOME/.unity-mcp/auth-token" || exit 1
chmod 600 "$HOME/.unity-mcp/auth-token"
fi
保持目录 0700,并在首次创建后确认 token 为 0600。仅改 umask 不能修复旧文件,也不能保证 Unity Hub 启动的进程继承设置。已泄露的 token 需由用户另行安排轮换;此操作不会打印或覆盖现有 token。Windows 应确保用户配置目录 ACL 私有。
开发测试和打包
python -m pip install -e ".[dev]"
python -m pip install pytest-cov pip-audit
python -m pytest -q --cov=unity_mcp_py --cov-report=term-missing
python -m pip_audit
python scripts/build_release.py
构建脚本默认先运行测试,再构建 Python wheel、Unity tgz 与部署 bundle。输出写入 dist/,不提交旧二进制。不要在测试失败时用 --skip-tests 对外声称已验证。
测试分层:Python 单元/MockBridge 集成测试;桥包 Tests.EditMode;真实 Unity 项目的手工/自动 PlayMode 验证。Python 测试通过不等于 C# 已在所有 Unity/OS 组合编译,更不等于项目 UI/登录流程已实测。本次没有执行真实游戏交互。
常见故障
| 现象 | 检查方法 |
|---|---|
| 找不到 bridge | Unity 是否打开、包是否导入成功、编译是否干净、实际监听端口、禁用标记 |
| 端口忙 / 多实例 | select_bridge(scan=true) 和 bridge_admin(action="status", port=...),核对 ID 后再绑定 |
| 握手超时 | 新实现握手响应 5 秒超时;Unity 主线程编译/阻塞时稍后重试,不直接抢占或杀进程 |
| 协议不一致 | 同步更新 Python 与 Unity 包;检查 schema 版本,不只看 wheel/tgz 文件名 |
| Lua/UI 类型找不到 | 项目尚未接入 UI / Lua 占位契约,不能靠换端口解决 |
| 点击无效 | 检查 nodeFound/blocked/ clicked 等实际字段、路径和遮挡;force 与真实用户操作并不等价 |
| 截图等待 | 代码 dirty 后可能需要用户点击继续;不要擅自代替人工确认 |
| 截图没有像素 | 检查 mode、unchanged、screenshot_available;最终视觉验证强制 full 且不跳过 |
| 绑定重启丢失 | 设置稳定独立 client ID,核对绑定存储和预期 bridge ID |
许可证
本仓库附 MIT LICENSE。该许可证不自动授予第三方游戏项目、素材、SDK 或内部业务接口的使用权;迁移适配前应确认相关授权。