Merge remote changes: integrate MCP tools and resource refactor docs into framework
- Resolve directory rename conflict from old 03/06 dirs - Move remote-added 资源加载重构方案.md into modules/frameworks/资源管理/ - Move remote-added 06_工具脚本/MCP工具 into modules/frameworks/编辑器工具/MCP工具/ - Keep Tools/CodexUnityMcp/ as MCP Server implementation - Update README.md, modules/模块索引.md, and parent READMEs - Add 文档关联 sections to new docs and fix old absolute paths Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
- [相机系统使用指南](modules/frameworks/相机系统/使用指南.md)
|
||||
- [资源管理](modules/frameworks/资源管理/README.md)
|
||||
- [资源管理使用指南](modules/frameworks/资源管理/使用指南.md)
|
||||
- [资源加载重构方案](modules/frameworks/资源管理/资源加载重构方案.md)
|
||||
- [场景系统](modules/frameworks/场景系统.md)
|
||||
- [网络系统](modules/frameworks/网络系统.md)
|
||||
- [数据配置框架](modules/frameworks/数据配置框架.md)
|
||||
@@ -59,6 +60,7 @@
|
||||
### 编辑器工具
|
||||
- [编辑器工具](modules/frameworks/编辑器工具/README.md)
|
||||
- [UI 拼接工具](modules/frameworks/编辑器工具/UI拼接工具.md)
|
||||
- [MCP 工具](modules/frameworks/编辑器工具/MCP工具/README.md)
|
||||
|
||||
### 项目规则
|
||||
- [项目规则入口](rules/项目规则入口.md)
|
||||
|
||||
2
Tools/CodexUnityMcp/.gitignore
vendored
Normal file
2
Tools/CodexUnityMcp/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
*.log
|
||||
69
Tools/CodexUnityMcp/README.md
Normal file
69
Tools/CodexUnityMcp/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# CrystalBattle Unity MCP
|
||||
|
||||
This local MCP server exposes selected CrystalBattle Unity Editor commands to Codex.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Codex connects to this MCP server over stdio.
|
||||
- The MCP server calls the Unity Editor bridge at `http://127.0.0.1:17777`.
|
||||
- The Unity Editor bridge executes Unity API work on the editor main thread.
|
||||
|
||||
## Unity Setup
|
||||
|
||||
Open `CrystalBattle_Client` in Unity. The bridge starts automatically after scripts compile.
|
||||
|
||||
Manual controls are available from the bridge panel:
|
||||
|
||||
```text
|
||||
Tools/Codex Unity MCP/Bridge Panel
|
||||
```
|
||||
|
||||
The panel shows the current connection status, endpoint, listener thread state, pending command count, total request count, recent command, and cached log count. It also includes buttons to start, stop, restart, copy the endpoint, and print status to the Unity Console.
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
From this directory:
|
||||
|
||||
```powershell
|
||||
pnpm install
|
||||
```
|
||||
|
||||
If `pnpm` is not on `PATH`, use the Codex bundled runtime:
|
||||
|
||||
```powershell
|
||||
& "C:\Users\admin\.cache\codex-runtimes\codex-primary-runtime\dependencies\bin\pnpm.cmd" install
|
||||
```
|
||||
|
||||
## Tools
|
||||
|
||||
- `unity_health`
|
||||
- `unity_refresh_assets`
|
||||
- `unity_execute_menu_item`
|
||||
- `unity_get_console_logs`
|
||||
- `unity_generate_ui_prefab`
|
||||
- `unity_export_prefab_to_json`
|
||||
|
||||
## Environment
|
||||
|
||||
Optional:
|
||||
|
||||
```text
|
||||
UNITY_MCP_BRIDGE_URL=http://127.0.0.1:17777
|
||||
```
|
||||
|
||||
## Codex Config
|
||||
|
||||
The workspace includes `.codex/config.toml` with a `crystalbattle-unity` MCP server entry.
|
||||
Restart Codex after dependency installation so the new MCP server is discovered.
|
||||
|
||||
---
|
||||
|
||||
## 文档关联
|
||||
|
||||
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
|
||||
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [MCP 工具](../../modules/frameworks/编辑器工具/MCP工具/README.md) | Codex ↔ Unity MCP 工具说明 |
|
||||
| [CrystalBattle Codex ↔ Unity MCP 使用规则](../../modules/frameworks/编辑器工具/MCP工具/CrystalBattle_Codex_Unity_MCP使用规则.md) | 具体使用规则 |
|
||||
| [modules/模块索引.md](../../modules/模块索引.md) | 模块索引是项目知识总入口 |
|
||||
18
Tools/CodexUnityMcp/package.json
Normal file
18
Tools/CodexUnityMcp/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "codex-unity-mcp",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Local MCP server that exposes CrystalBattle Unity Editor commands to Codex.",
|
||||
"main": "src/server.mjs",
|
||||
"scripts": {
|
||||
"start": "node src/server.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
784
Tools/CodexUnityMcp/pnpm-lock.yaml
generated
Normal file
784
Tools/CodexUnityMcp/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,784 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.29.0
|
||||
version: 1.29.0(zod@3.25.76)
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
version: 3.25.76
|
||||
|
||||
packages:
|
||||
|
||||
'@hono/node-server@1.19.14':
|
||||
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
|
||||
engines: {node: '>=18.14.1'}
|
||||
peerDependencies:
|
||||
hono: ^4
|
||||
|
||||
'@modelcontextprotocol/sdk@1.29.0':
|
||||
resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@cfworker/json-schema': ^4.1.1
|
||||
zod: ^3.25 || ^4.0
|
||||
peerDependenciesMeta:
|
||||
'@cfworker/json-schema':
|
||||
optional: true
|
||||
|
||||
accepts@2.0.0:
|
||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
ajv-formats@3.0.1:
|
||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
|
||||
ajv@8.20.0:
|
||||
resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==}
|
||||
|
||||
body-parser@2.3.0:
|
||||
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
call-bound@1.0.4:
|
||||
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
content-disposition@1.1.0:
|
||||
resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
content-type@1.0.5:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
content-type@2.0.0:
|
||||
resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cookie-signature@1.2.2:
|
||||
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
|
||||
engines: {node: '>=6.6.0'}
|
||||
|
||||
cookie@0.7.2:
|
||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
cors@2.8.6:
|
||||
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
encodeurl@2.0.0:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
etag@1.8.1:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
eventsource-parser@3.1.0:
|
||||
resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
eventsource@3.0.7:
|
||||
resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
express-rate-limit@8.5.2:
|
||||
resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
express: '>= 4.11'
|
||||
|
||||
express@5.2.1:
|
||||
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
fast-uri@3.1.3:
|
||||
resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==}
|
||||
|
||||
finalhandler@2.1.1:
|
||||
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
fresh@2.0.0:
|
||||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.4:
|
||||
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hono@4.12.27:
|
||||
resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
http-errors@2.0.1:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ip-address@10.2.0:
|
||||
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
is-promise@4.0.0:
|
||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
jose@6.2.3:
|
||||
resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==}
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
|
||||
json-schema-typed@8.0.2:
|
||||
resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
media-typer@1.1.0:
|
||||
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
merge-descriptors@2.0.0:
|
||||
resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
mime-db@1.54.0:
|
||||
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@3.0.2:
|
||||
resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
negotiator@1.0.0:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
parseurl@1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-to-regexp@8.4.2:
|
||||
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
|
||||
|
||||
pkce-challenge@5.0.1:
|
||||
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||
engines: {node: '>=16.20.0'}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
qs@6.15.3:
|
||||
resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
range-parser@1.3.0:
|
||||
resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
raw-body@3.0.2:
|
||||
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
router@2.2.0:
|
||||
resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
send@1.2.1:
|
||||
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
serve-static@2.2.1:
|
||||
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
side-channel-list@1.0.1:
|
||||
resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel-map@1.0.1:
|
||||
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel-weakmap@1.0.2:
|
||||
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
side-channel@1.1.1:
|
||||
resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
statuses@2.0.2:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
toidentifier@1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
type-is@2.1.0:
|
||||
resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
unpipe@1.0.0:
|
||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vary@1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
zod-to-json-schema@3.25.2:
|
||||
resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
|
||||
peerDependencies:
|
||||
zod: ^3.25.28 || ^4
|
||||
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@hono/node-server@1.19.14(hono@4.12.27)':
|
||||
dependencies:
|
||||
hono: 4.12.27
|
||||
|
||||
'@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@hono/node-server': 1.19.14(hono@4.12.27)
|
||||
ajv: 8.20.0
|
||||
ajv-formats: 3.0.1(ajv@8.20.0)
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.6
|
||||
cross-spawn: 7.0.6
|
||||
eventsource: 3.0.7
|
||||
eventsource-parser: 3.1.0
|
||||
express: 5.2.1
|
||||
express-rate-limit: 8.5.2(express@5.2.1)
|
||||
hono: 4.12.27
|
||||
jose: 6.2.3
|
||||
json-schema-typed: 8.0.2
|
||||
pkce-challenge: 5.0.1
|
||||
raw-body: 3.0.2
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
accepts@2.0.0:
|
||||
dependencies:
|
||||
mime-types: 3.0.2
|
||||
negotiator: 1.0.0
|
||||
|
||||
ajv-formats@3.0.1(ajv@8.20.0):
|
||||
optionalDependencies:
|
||||
ajv: 8.20.0
|
||||
|
||||
ajv@8.20.0:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-uri: 3.1.3
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
body-parser@2.3.0:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
content-type: 2.0.0
|
||||
debug: 4.4.3
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
on-finished: 2.4.1
|
||||
qs: 6.15.3
|
||||
raw-body: 3.0.2
|
||||
type-is: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
bytes@3.1.2: {}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
call-bound@1.0.4:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
get-intrinsic: 1.3.0
|
||||
|
||||
content-disposition@1.1.0: {}
|
||||
|
||||
content-type@1.0.5: {}
|
||||
|
||||
content-type@2.0.0: {}
|
||||
|
||||
cookie-signature@1.2.2: {}
|
||||
|
||||
cookie@0.7.2: {}
|
||||
|
||||
cors@2.8.6:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
encodeurl@2.0.0: {}
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-object-atoms@1.1.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
etag@1.8.1: {}
|
||||
|
||||
eventsource-parser@3.1.0: {}
|
||||
|
||||
eventsource@3.0.7:
|
||||
dependencies:
|
||||
eventsource-parser: 3.1.0
|
||||
|
||||
express-rate-limit@8.5.2(express@5.2.1):
|
||||
dependencies:
|
||||
express: 5.2.1
|
||||
ip-address: 10.2.0
|
||||
|
||||
express@5.2.1:
|
||||
dependencies:
|
||||
accepts: 2.0.0
|
||||
body-parser: 2.3.0
|
||||
content-disposition: 1.1.0
|
||||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
cookie-signature: 1.2.2
|
||||
debug: 4.4.3
|
||||
depd: 2.0.0
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 2.1.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
merge-descriptors: 2.0.0
|
||||
mime-types: 3.0.2
|
||||
on-finished: 2.4.1
|
||||
once: 1.4.0
|
||||
parseurl: 1.3.3
|
||||
proxy-addr: 2.0.7
|
||||
qs: 6.15.3
|
||||
range-parser: 1.3.0
|
||||
router: 2.2.0
|
||||
send: 1.2.1
|
||||
serve-static: 2.2.1
|
||||
statuses: 2.0.2
|
||||
type-is: 2.1.0
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-uri@3.1.3: {}
|
||||
|
||||
finalhandler@2.1.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
on-finished: 2.4.1
|
||||
parseurl: 1.3.3
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fresh@2.0.0: {}
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.2
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.4
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.2
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
hasown@2.0.4:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hono@4.12.27: {}
|
||||
|
||||
http-errors@2.0.1:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ip-address@10.2.0: {}
|
||||
|
||||
ipaddr.js@1.9.1: {}
|
||||
|
||||
is-promise@4.0.0: {}
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
jose@6.2.3: {}
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
|
||||
json-schema-typed@8.0.2: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
media-typer@1.1.0: {}
|
||||
|
||||
merge-descriptors@2.0.0: {}
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
|
||||
mime-types@3.0.2:
|
||||
dependencies:
|
||||
mime-db: 1.54.0
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
on-finished@2.4.1:
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
|
||||
once@1.4.0:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
parseurl@1.3.3: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-to-regexp@8.4.2: {}
|
||||
|
||||
pkce-challenge@5.0.1: {}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
|
||||
qs@6.15.3:
|
||||
dependencies:
|
||||
es-define-property: 1.0.1
|
||||
side-channel: 1.1.1
|
||||
|
||||
range-parser@1.3.0: {}
|
||||
|
||||
raw-body@3.0.2:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.7.2
|
||||
unpipe: 1.0.0
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
router@2.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
depd: 2.0.0
|
||||
is-promise: 4.0.0
|
||||
parseurl: 1.3.3
|
||||
path-to-regexp: 8.4.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
send@1.2.1:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
fresh: 2.0.0
|
||||
http-errors: 2.0.1
|
||||
mime-types: 3.0.2
|
||||
ms: 2.1.3
|
||||
on-finished: 2.4.1
|
||||
range-parser: 1.3.0
|
||||
statuses: 2.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
serve-static@2.2.1:
|
||||
dependencies:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 1.2.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
side-channel-list@1.0.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
|
||||
side-channel-map@1.0.1:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
|
||||
side-channel-weakmap@1.0.2:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
side-channel-map: 1.0.1
|
||||
|
||||
side-channel@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
side-channel-list: 1.0.1
|
||||
side-channel-map: 1.0.1
|
||||
side-channel-weakmap: 1.0.2
|
||||
|
||||
statuses@2.0.2: {}
|
||||
|
||||
toidentifier@1.0.1: {}
|
||||
|
||||
type-is@2.1.0:
|
||||
dependencies:
|
||||
content-type: 2.0.0
|
||||
media-typer: 1.1.0
|
||||
mime-types: 3.0.2
|
||||
|
||||
unpipe@1.0.0: {}
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
zod-to-json-schema@3.25.2(zod@3.25.76):
|
||||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod@3.25.76: {}
|
||||
165
Tools/CodexUnityMcp/src/server.mjs
Normal file
165
Tools/CodexUnityMcp/src/server.mjs
Normal file
@@ -0,0 +1,165 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
|
||||
const DEFAULT_BRIDGE_URL = "http://127.0.0.1:17777";
|
||||
const DEFAULT_TIMEOUT_MS = 30000;
|
||||
const LONG_TIMEOUT_MS = 120000;
|
||||
|
||||
const bridgeUrl = (process.env.UNITY_MCP_BRIDGE_URL || DEFAULT_BRIDGE_URL).replace(/\/+$/, "");
|
||||
|
||||
const server = new McpServer({
|
||||
name: "crystalbattle-unity",
|
||||
version: "0.1.0"
|
||||
});
|
||||
|
||||
server.registerTool(
|
||||
"unity_health",
|
||||
{
|
||||
title: "Unity health",
|
||||
description: "Check whether the CrystalBattle Unity Editor bridge is online.",
|
||||
inputSchema: {}
|
||||
},
|
||||
async () => textResult(await getHealth())
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"unity_refresh_assets",
|
||||
{
|
||||
title: "Refresh Unity assets",
|
||||
description: "Run AssetDatabase.Refresh in the Unity Editor.",
|
||||
inputSchema: {}
|
||||
},
|
||||
async () => textResult(await callUnity("refresh_assets", {}, DEFAULT_TIMEOUT_MS))
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"unity_execute_menu_item",
|
||||
{
|
||||
title: "Execute Unity menu item",
|
||||
description: "Execute a whitelisted Unity Editor menu item.",
|
||||
inputSchema: {
|
||||
menuItem: z.string().min(1).describe("Whitelisted Unity menu item path, for example Assets/Refresh.")
|
||||
}
|
||||
},
|
||||
async ({ menuItem }) => textResult(await callUnity("execute_menu_item", { menuItem }, DEFAULT_TIMEOUT_MS))
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"unity_get_console_logs",
|
||||
{
|
||||
title: "Get Unity console logs",
|
||||
description: "Return recent logs captured by the Unity Editor bridge.",
|
||||
inputSchema: {
|
||||
limit: z.number().int().min(1).max(200).optional().describe("Maximum number of log entries to return.")
|
||||
}
|
||||
},
|
||||
async ({ limit = 50 }) => textResult(await callUnity("get_console_logs", { limit }, DEFAULT_TIMEOUT_MS))
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"unity_generate_ui_prefab",
|
||||
{
|
||||
title: "Generate UI prefab",
|
||||
description: "Generate a Unity UI prefab from a JSON layout file.",
|
||||
inputSchema: {
|
||||
jsonPath: z.string().min(1).describe("JSON layout path. Absolute paths and Unity-project-relative paths are supported."),
|
||||
prefabPath: z.string().min(1).optional().describe("Optional Unity asset path for the generated prefab.")
|
||||
}
|
||||
},
|
||||
async ({ jsonPath, prefabPath }) =>
|
||||
textResult(await callUnity("generate_ui_prefab", { jsonPath, prefabPath }, LONG_TIMEOUT_MS))
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
"unity_export_prefab_to_json",
|
||||
{
|
||||
title: "Export prefab to JSON",
|
||||
description: "Export a Unity UI prefab to a JSON layout file.",
|
||||
inputSchema: {
|
||||
prefabPath: z.string().min(1).describe("Unity asset path of the prefab to export."),
|
||||
jsonPath: z.string().min(1).optional().describe("Optional output JSON path.")
|
||||
}
|
||||
},
|
||||
async ({ prefabPath, jsonPath }) =>
|
||||
textResult(await callUnity("export_prefab_to_json", { prefabPath, jsonPath }, LONG_TIMEOUT_MS))
|
||||
);
|
||||
|
||||
async function getHealth() {
|
||||
const response = await request(`${bridgeUrl}/health`, {
|
||||
method: "GET",
|
||||
timeoutMs: DEFAULT_TIMEOUT_MS
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
async function callUnity(command, args, timeoutMs) {
|
||||
return request(`${bridgeUrl}/command`, {
|
||||
method: "POST",
|
||||
timeoutMs,
|
||||
body: {
|
||||
id: `${command}-${Date.now()}`,
|
||||
command,
|
||||
args: stripUndefined(args)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function request(url, options) {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: options.method,
|
||||
signal: controller.signal,
|
||||
headers: options.body ? { "content-type": "application/json" } : undefined,
|
||||
body: options.body ? JSON.stringify(options.body) : undefined
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
let payload;
|
||||
try {
|
||||
payload = text ? JSON.parse(text) : {};
|
||||
} catch {
|
||||
payload = { ok: false, message: text || "Unity bridge returned a non-JSON response." };
|
||||
}
|
||||
|
||||
if (!response.ok && payload.ok !== false) {
|
||||
payload.ok = false;
|
||||
payload.message = payload.message || `Unity bridge returned HTTP ${response.status}.`;
|
||||
}
|
||||
|
||||
return payload;
|
||||
} catch (error) {
|
||||
const reason = error.name === "AbortError"
|
||||
? `Timed out after ${options.timeoutMs} ms.`
|
||||
: error.message;
|
||||
return {
|
||||
ok: false,
|
||||
message: `Unity Editor bridge is not reachable at ${bridgeUrl}. ${reason}`,
|
||||
data: {},
|
||||
logs: []
|
||||
};
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function stripUndefined(value) {
|
||||
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
||||
}
|
||||
|
||||
function textResult(payload) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(payload, null, 2)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
@@ -0,0 +1,205 @@
|
||||
# CrystalBattle Codex ↔ Unity MCP 使用规则
|
||||
|
||||
## 1. 目标与定位
|
||||
|
||||
MCP(Model Context Protocol)用于打通 Codex 与 Unity Editor。Codex 不直接操作 Unity 进程,而是通过本地 MCP Server 调用 Unity Editor 内的 HTTP Bridge,再由 Bridge 在 Unity 主线程执行 Editor 命令。
|
||||
|
||||
当前首版定位:
|
||||
|
||||
- 只支持 Unity Editor 工具桥,不覆盖 Play Mode 或真机运行时调试。
|
||||
- 只监听本机 `127.0.0.1`,不开放局域网访问。
|
||||
- 优先服务 UI 生成、Prefab 导出、资源刷新、日志读取等 Editor 自动化流程。
|
||||
- Proxima 暂不作为 MCP 主通道,后续如做运行时调试再单独规划。
|
||||
|
||||
## 2. 工程结构
|
||||
|
||||
本文档只说明 Codex 如何对接 Unity MCP,不说明 Unity Editor 内部脚本目录结构,也不在本节维护数据目录。
|
||||
|
||||
Codex 需要知道的对接点:
|
||||
|
||||
```text
|
||||
Unity MCP Server: UnityAI/Tools/CodexUnityMcp/
|
||||
Codex config: .codex/config.toml
|
||||
Unity Bridge URL: http://127.0.0.1:17777
|
||||
```
|
||||
|
||||
## 3. 启动与连接
|
||||
|
||||
1. 确认 `.codex/config.toml` 中存在 `crystalbattle-unity` MCP server 配置。
|
||||
2. 确认 MCP Server 入口指向 `UnityAI/Tools/CodexUnityMcp/src/server.mjs`。
|
||||
3. Codex 侧重启或新开线程后,会重新读取 MCP server 配置。
|
||||
4. 每轮 Unity 自动化前,优先调用 `unity_health` 检查连通性。
|
||||
5. 如果 `unity_health` 不通,只记录错误并停止后续 Unity 操作;Unity 侧如何打开或重启 Bridge 不在本文档说明范围内。
|
||||
|
||||
禁止事项:
|
||||
|
||||
- 不允许把 Bridge 地址改成 `0.0.0.0` 或局域网 IP。
|
||||
- 不允许绕过 MCP 直接让 Codex 执行 Unity Editor 侧业务逻辑。
|
||||
- 不允许在 Unity 后台线程直接调用 Unity API,所有 Unity API 必须回到 Editor 主线程执行。
|
||||
|
||||
## 4. Codex 可用工具
|
||||
|
||||
| 工具名 | 用途 | 规则 |
|
||||
|---|---|---|
|
||||
| `unity_health` | 检查 Unity Bridge 是否在线 | 每轮 Unity 自动化前优先调用 |
|
||||
| `unity_refresh_assets` | 执行 `AssetDatabase.Refresh()` | 生成或导出资源后可调用 |
|
||||
| `unity_execute_menu_item` | 执行白名单菜单项 | 只能执行 Bridge 内白名单 |
|
||||
| `unity_get_console_logs` | 读取最近 Unity Console 日志 | 生成失败或验证结果时必须查看 |
|
||||
| `unity_generate_ui_prefab` | 根据 JSON 生成 UI Prefab | 输入 JSON 必须使用 Unity 工程相对路径或绝对路径 |
|
||||
| `unity_export_prefab_to_json` | 从 UI Prefab 导出 JSON | Prefab 路径必须是 Unity Asset 路径 |
|
||||
|
||||
## 5. MCP 数据目录规则
|
||||
|
||||
所有 MCP 数据必须按功能单独放在 Unity Editor 目录下,并直接按目标预制体名分文件夹;不再增加中间层目录。统一格式:
|
||||
|
||||
```text
|
||||
Assets/Editor/MCP_{功能名}/<PrefabName>/
|
||||
```
|
||||
|
||||
命名要求:
|
||||
|
||||
- `{功能名}` 只表示功能类别,不写具体窗口名、临时任务名或个人名。
|
||||
- `<PrefabName>` 必须沿用目标预制体名称,和最终生成的 `<PrefabName>.prefab` 保持一致。
|
||||
- UI 相关数据统一放在 `Assets/Editor/MCP_UI/<PrefabName>/`。
|
||||
- 其它功能按能力命名,例如 `Assets/Editor/MCP_Config/<PrefabName>/`、`Assets/Editor/MCP_Map/<PrefabName>/`。
|
||||
- 新增、导出、临时验证用 JSON 都必须遵守该目录规则。
|
||||
|
||||
整理旧数据时,应迁移到 `Assets/Editor/MCP_UI/<PrefabName>/` 或对应功能目录。
|
||||
|
||||
## 6. UI 生成规则
|
||||
|
||||
标准流程:
|
||||
|
||||
1. 先读取 `UnityAI/README.md` 中的 UI 相关索引。
|
||||
2. 根据 README 的 UI 相关入口,优先阅读:
|
||||
- `UnityAI/modules/frameworks/UI窗口系统/开发指南.md`
|
||||
- `UnityAI/modules/frameworks/UI窗口系统/界面拼接.md`
|
||||
3. 需要使用 JSON 与 Prefab 双向转换时,再阅读 README 中的 `编辑器工具脚本` 入口:
|
||||
- `UnityAI/modules/frameworks/编辑器工具/UI工具/`
|
||||
4. 按 UI 指南确认窗口命名、层级、资源路径和布局规则后,准备 UI JSON。
|
||||
5. 调用 `unity_generate_ui_prefab`。
|
||||
6. 目标 Prefab 路径优先使用:
|
||||
|
||||
```text
|
||||
Assets/Game/GameHFResource/CN/UIWindows/<WindowName>/<WindowName>.prefab
|
||||
```
|
||||
|
||||
7. 调用 `unity_get_console_logs` 查看是否有 Error 或关键 Warning。
|
||||
8. 如需要验证双向转换,再调用 `unity_export_prefab_to_json`。
|
||||
|
||||
JSON 约束:
|
||||
|
||||
- 根对象使用 `UILayoutData` 结构:`name/type/layer/rectTransform/canvas/children`。
|
||||
- UI 节点使用 `UIElementData` 结构:`name/type/rectTransform/image/button/text/.../children`。
|
||||
- 资源路径使用 Unity Asset 路径,例如 `Assets/...`。
|
||||
- 测试 UI 命名必须带明确测试标识,例如 `CodexMcpTestWindow`。
|
||||
|
||||
当前注意事项:
|
||||
|
||||
- `UnityEngine.JsonUtility` 解析递归 `children` 结构时可能出现 serialization depth warning。
|
||||
- 如果 Prefab 已成功生成且没有 Error,该 warning 暂不阻塞测试。
|
||||
- 后续如大量使用 UI JSON,应考虑把解析器替换为更适合递归结构的 JSON 库。
|
||||
|
||||
## 7. 安全与权限
|
||||
|
||||
MCP 工具可以触发 Unity Editor 行为,因此必须保持最小权限:
|
||||
|
||||
- `execute_menu_item` 必须使用白名单,禁止开放任意菜单执行。
|
||||
- 只允许监听 `127.0.0.1`。
|
||||
- 不在 MCP Server 中直接读写 Unity 资源,资源变更统一交给 Unity Bridge 执行。
|
||||
- 生成测试资源时使用独立测试目录或明确测试命名,避免覆盖正式 UI。
|
||||
- 覆盖已有 Prefab 前必须确认目标路径是否为预期文件。
|
||||
- 不通过 MCP 执行批量删除、批量移动、构建发布、Git 操作等高风险动作。
|
||||
|
||||
## 8. 排错流程
|
||||
|
||||
连接失败:
|
||||
|
||||
1. 调用 `unity_health` 获取错误信息。
|
||||
2. 用浏览器或 PowerShell 检查:
|
||||
|
||||
```powershell
|
||||
Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:17777/health"
|
||||
```
|
||||
|
||||
3. 如果连接仍失败,停止后续 Unity 操作并记录错误;Unity 侧启动、重启、面板操作不在本文档说明范围内。
|
||||
|
||||
MCP 工具不可见:
|
||||
|
||||
1. 确认 `UnityAI/Tools/CodexUnityMcp/node_modules` 已安装。
|
||||
2. 确认 `.codex/config.toml` 中 `command` 和 `args` 路径有效。
|
||||
3. 重启 Codex,让 MCP server 配置重新加载。
|
||||
|
||||
UI 生成失败:
|
||||
|
||||
1. 调用 `unity_get_console_logs` 查看 Unity Console。
|
||||
2. 检查 JSON 文件是否存在。
|
||||
3. 检查 JSON 是否位于 `Assets/Editor/MCP_UI/<PrefabName>/`。
|
||||
4. 检查 `rectTransform` 数组字段是否齐全。
|
||||
5. 检查 Sprite、Font、Material 等资源路径是否存在。
|
||||
6. 确认 Prefab 目标目录可写且未被外部程序锁定。
|
||||
|
||||
## 9. 测试基准
|
||||
|
||||
每次修改 MCP 或 Unity Bridge 后,至少验证:
|
||||
|
||||
- `unity_health` 返回 `ok=true`。
|
||||
- `unity_get_console_logs` 能返回日志。
|
||||
- `unity_generate_ui_prefab` 能生成一个最小测试 Prefab。
|
||||
- 生成的 Prefab 文件存在,并包含预期节点名。
|
||||
- `unity_export_prefab_to_json` 能把测试 Prefab 导出 JSON。
|
||||
- Unity Console 无新增 Error。
|
||||
|
||||
UI 测试样例推荐路径:
|
||||
|
||||
```text
|
||||
Assets/Editor/MCP_UI/CodexMcpTestWindow/CodexMcpTestWindow.json
|
||||
Assets/Game/GameHFResource/CN/UIWindows/CodexMcpTestWindow/CodexMcpTestWindow.prefab
|
||||
Assets/Editor/MCP_UI/CodexMcpTestWindow/CodexMcpTestWindow.exported.json
|
||||
```
|
||||
|
||||
## 10. 维护规则
|
||||
|
||||
新增 MCP tool 时必须同步更新:
|
||||
|
||||
- `UnityAI/Tools/CodexUnityMcp/src/server.mjs`
|
||||
- Unity Bridge 侧对应命令代码
|
||||
- 本文档的工具列表与测试基准
|
||||
|
||||
新增 Unity 命令时必须满足:
|
||||
|
||||
- HTTP 请求只负责收包和回包。
|
||||
- Unity API 在 `EditorApplication.update` 主线程队列执行。
|
||||
- 返回值使用统一结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"message": "done",
|
||||
"data": {},
|
||||
"logs": []
|
||||
}
|
||||
```
|
||||
|
||||
文档版本:
|
||||
|
||||
- v1.4
|
||||
- 生效日期:2026-07-03
|
||||
- 适用项目:CrystalBattle_Client
|
||||
|
||||
---
|
||||
|
||||
## 文档关联
|
||||
|
||||
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
|
||||
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [MCP 工具入口](README.md) | 本文件是 MCP 工具模块的子文档 |
|
||||
| [UI 开发指南](../../UI窗口系统/开发指南.md) | UI 生成规则参考 |
|
||||
| [UI 界面拼接设计指南](../../UI窗口系统/界面拼接.md) | UI 布局规则参考 |
|
||||
| [UI 拼接工具](../UI拼接工具.md) | JSON/Prefab 双向转换工具说明 |
|
||||
| [Tools/CodexUnityMcp](../../../../Tools/CodexUnityMcp/README.md) | MCP Server 实现 |
|
||||
| [modules/模块索引.md](../../../模块索引.md) | 模块索引是项目知识总入口 |
|
||||
| [rules/10-架构说明.md](../../../../rules/10-架构说明.md) | 编辑器工具层的架构说明 |
|
||||
| [rules/30-模块登记规则.md](../../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
28
modules/frameworks/编辑器工具/MCP工具/README.md
Normal file
28
modules/frameworks/编辑器工具/MCP工具/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# MCP工具
|
||||
|
||||
本目录用于沉淀 Codex 与 Unity Editor 打通相关的规则、工具说明和排错流程。
|
||||
|
||||
当前文档:
|
||||
|
||||
- [CrystalBattle Codex ↔ Unity MCP 使用规则](CrystalBattle_Codex_Unity_MCP使用规则.md)
|
||||
|
||||
当前实现位置:
|
||||
|
||||
- Unity Editor Bridge:`CrystalBattle_Client/Assets/Editor/Command/`
|
||||
- MCP Server:`UnityAI/Tools/CodexUnityMcp/`
|
||||
- Codex 配置:`.codex/config.toml`
|
||||
|
||||
---
|
||||
|
||||
## 文档关联
|
||||
|
||||
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
|
||||
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [编辑器工具入口](../README.md) | 本文件是编辑器工具模块的子文档 |
|
||||
| [CrystalBattle Codex ↔ Unity MCP 使用规则](CrystalBattle_Codex_Unity_MCP使用规则.md) | 具体 MCP 使用规则 |
|
||||
| [Tools/CodexUnityMcp](../../../../Tools/CodexUnityMcp/README.md) | MCP Server 实现 |
|
||||
| [modules/模块索引.md](../../../模块索引.md) | 模块索引是项目知识总入口 |
|
||||
| [rules/10-架构说明.md](../../../../rules/10-架构说明.md) | 编辑器工具层的架构说明 |
|
||||
| [rules/30-模块登记规则.md](../../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
@@ -41,6 +41,11 @@
|
||||
- [通用框架](../通用框架/README.md)
|
||||
- [资源管理](../资源管理/README.md)
|
||||
|
||||
## 子文档
|
||||
|
||||
- [UI 拼接工具](UI拼接工具.md)
|
||||
- [MCP 工具](MCP工具/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 文档关联
|
||||
@@ -50,5 +55,7 @@
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
|
||||
| [modules/frameworks/编辑器工具/UI拼接工具.md](UI拼接工具.md) | UI 预制体 JSON 双向转换工具 |
|
||||
| [modules/frameworks/编辑器工具/MCP工具/README.md](MCP工具/README.md) | Codex ↔ Unity MCP 工具 |
|
||||
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 编辑器工具层的架构说明 |
|
||||
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
|
||||
@@ -120,6 +120,11 @@ SpriteAtlasManager.atlasRequested
|
||||
- [实体系统](../实体系统/README.md)
|
||||
- [场景系统](../场景系统.md)
|
||||
|
||||
## 子文档
|
||||
|
||||
- [资源管理使用指南](使用指南.md)
|
||||
- [资源加载重构方案](资源加载重构方案.md)
|
||||
|
||||
---
|
||||
|
||||
## 文档关联
|
||||
@@ -132,5 +137,6 @@ SpriteAtlasManager.atlasRequested
|
||||
| [modules/frameworks/UI窗口系统/README.md](../UI窗口系统/README.md) | UI 资源加载依赖资源管理 |
|
||||
| [modules/frameworks/实体系统/README.md](../实体系统/README.md) | 实体资源加载依赖资源管理 |
|
||||
| [modules/frameworks/场景系统.md](../场景系统.md) | 场景资源加载依赖资源管理 |
|
||||
| [modules/frameworks/资源管理/资源加载重构方案.md](资源加载重构方案.md) | AssetBundle 加载链路重构方案 |
|
||||
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
|
||||
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
|
||||
380
modules/frameworks/资源管理/资源加载重构方案.md
Normal file
380
modules/frameworks/资源管理/资源加载重构方案.md
Normal file
@@ -0,0 +1,380 @@
|
||||
# ESF_ 资源加载重构方案
|
||||
|
||||
## 一、目标与边界
|
||||
|
||||
本方案用于规划 StrayFog 框架中 AssetBundle 资源加载链路的重构。当前阶段先文档化设计,不直接修改代码。
|
||||
|
||||
重构范围只包含 `Assets/StrayFog/Editor/CopyToX/...` 下的 `ESF_` 框架源头代码。`ESF_` 后续会通过 CopyTo 流程同步到 `GameHFScripte` 目录,并替换为业务运行时使用的 `HF_` 命名。因此:
|
||||
|
||||
- 框架优化只修改 `ESF_` 源头。
|
||||
- 不直接修改 CopyTo 后的 `HF_` 副本代码。
|
||||
- 对外调用接口保持兼容,业务仍通过现有 `HF_` API 使用资源系统。
|
||||
|
||||
需要保持兼容的公开入口:
|
||||
|
||||
- `ESF_ABMgr.LoadInMemory(...)`
|
||||
- `ESF_ABMgr.SetModel(...)`
|
||||
- `ESF_ABMgr.SetDownloadWhilePlaying(...)`
|
||||
- `ESF_ABMgr.SetDownLoadUri(...)`
|
||||
- `ESF_ABMgr.RegisterDownloadMainifestFun(...)`
|
||||
- `ESF_AssetBundlePathAttribute` 现有构造与使用方式
|
||||
|
||||
计划新增的公开入口:
|
||||
|
||||
```csharp
|
||||
public bool TryUnloadPrefabInMemory(string _prefabName);
|
||||
public bool TryUnloadAssetInMemory(string _assetName);
|
||||
public long GetLoadedAssetBundleMemorySize();
|
||||
public void SetCdnSaveRootPath(string _saveRootPath);
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `TryUnloadPrefabInMemory(string)` 供业务按预制体名称主动请求卸载,例如 `TryUnloadPrefabInMemory("GameLogicFightWindow")`。
|
||||
- `TryUnloadAssetInMemory(string)` 供业务按资源名称主动请求卸载,适用于非 prefab 资源。
|
||||
- 业务接口不暴露 `assetBundleKey`、`ESF_AssetBundlePathAttribute` 和 `_unloadAllLoadedObjects`,避免上层误用内部概念。
|
||||
- 内部卸载统一使用 `AssetBundle.Unload(false)`,只释放 Bundle 文件内存,不强制销毁已经实例化出去的对象。
|
||||
- 框架必须先判断资源已经没有框架侧活跃引用;如果仍在加载中、正在被窗口/任务使用、处于 ACTIVE 生命周期,或名称匹配不唯一,则拒绝卸载并返回 `false`。
|
||||
- `GetLoadedAssetBundleMemorySize()` 返回当前 ESF_ABMgr 统计到的已加载 AssetBundle 内存大小,单位为 byte,用于运行时监控面板或调试日志。
|
||||
- `SetCdnSaveRootPath(string)` 供外部按平台传入 CDN 热更新文件落盘根目录;如果未传入或传入空值,默认使用 PersistentData 资源根路径。
|
||||
|
||||
## 二、当前问题
|
||||
|
||||
### 2.1 职责封装需要加强
|
||||
|
||||
`ESF_ABMgr` 当前通过 partial 分布类承载 Manifest 读取、资源路径修正、Loader 管理、依赖查询、加载模式控制、GC 回收等职责。这种设计保留了统一入口,方便后续 CopyTo 和上层调用,是框架既有设计方向。
|
||||
|
||||
后续优化不应拆散 `ESF_ABMgr` 的统一入口,而是需要继续在 `ESF_ABMgr` 内部强化职责封装:把 Manifest 查询、加载来源选择、下载落盘、GC 回收、主动卸载等流程封装成清晰的内部方法或内部服务,减少方法之间直接互相修改状态。`ESF_AssetLoader` 内部也需要把依赖加载、主包加载、下载等待、进度计算和回调分发拆成更明确的状态步骤。
|
||||
|
||||
### 2.2 本地加载内存峰值偏高
|
||||
|
||||
PersistentData 等普通本地文件路径当前存在 `File.ReadAllBytes` + `AssetBundle.LoadFromMemoryAsync` 的加载方式。结合 Unity 官方说明和当前框架实现,这个判断成立,但需要精确理解:
|
||||
|
||||
- `File.ReadAllBytes(path)` 会先把整个 AssetBundle 文件读入托管 `byte[]`,这一步本身就会产生一份完整文件大小的内存占用。
|
||||
- `AssetBundle.LoadFromMemoryAsync(byte[])` 是从内存中的 byte[] 创建 AssetBundle,适合已经下载、解密或只能拿到 byte[] 的场景。
|
||||
- `AssetBundle.LoadFromFileAsync(path)` 是从磁盘文件异步加载 AssetBundle;Unity 官方说明它支持各种压缩类型,并且未压缩和 Chunk 压缩包可以直接从磁盘读取。
|
||||
- 当前框架构建 AssetBundle 时使用了 `BuildAssetBundleOptions.ChunkBasedCompression`,因此 PersistentData 下的普通本地文件优先改为 `LoadFromFileAsync` 是合理方向。
|
||||
|
||||
需要注意:如果资源包需要先经过 `LoadFileByDecryptFunc` 解密,或者来源是 StreamingAssets URL、CDN 下载数据、小游戏文件系统 byte[],仍然需要保留 `LoadFromMemoryAsync` 路径。
|
||||
|
||||
### 2.3 StreamingAssets 存在忙等阻塞
|
||||
|
||||
`ESF_AssetBundleHelper.OnReadStreamingAssets` 使用 `UnityWebRequest.SendWebRequest()` 后 `while (!async.isDone)` 空循环等待,会阻塞主线程。该问题在移动端和 WebGL/小游戏类环境中风险更高。
|
||||
|
||||
### 2.4 下载队列缺少统一并发上限
|
||||
|
||||
`ESF_DownLoadGroup` 的 `m_MaxTask` 由任务文件数量传入,可能导致一次任务内所有文件同时下载。资源数量较多时容易造成网络拥塞、内存峰值和下载回调竞争。
|
||||
|
||||
### 2.5 进度与回收存在边界风险
|
||||
|
||||
`ESF_ABMgr.progress` 在没有 active loader 时存在除零风险。GC 回收时销毁 loader GameObject 后,字典与内存统计需要同步清理,否则可能残留失效 loader 或重复统计。
|
||||
|
||||
## 三、目标架构
|
||||
|
||||
重构后仍保留 `ESF_ABMgr` 作为统一对外入口,并继续允许使用 partial 分布类组织代码;优化重点是把内部职责封装成更明确的方法组或内部服务。
|
||||
|
||||
| 模块 | 职责 |
|
||||
|-----|-----|
|
||||
| Manifest 管理封装 | 读取 Manifest、维护带 hash 包名映射、处理语言回退、提供依赖查询 |
|
||||
| Loader 管理封装 | 管理 assetBundleKey 到 loader 的映射、创建/复用/重载 loader |
|
||||
| Bundle 读取策略封装 | 根据加载模式选择 `LoadFromFileAsync`、`LoadFromMemoryAsync` 或下载数据加载 |
|
||||
| 下载队列封装 | 控制下载并发、失败重试、进度回调、下载完成数据回传 |
|
||||
| GC 缓存策略封装 | 维护 ACTIVE/Normal/LAZY 生命周期,回收时同步移除 loader 映射和内存统计 |
|
||||
| CDN 落盘路径策略封装 | 管理 CDN 下载后的保存根目录,支持外部传入平台专用路径,默认回退 PersistentData |
|
||||
|
||||
### 3.1 资源加载主流程
|
||||
|
||||
```
|
||||
LoadInMemory
|
||||
│
|
||||
▼
|
||||
Manifest 管理:确认 StreamingAssets / PersistentData / CDN 来源
|
||||
│
|
||||
▼
|
||||
Manifest 管理:按当前语言和各来源 Manifest 修正 ESF_AssetBundlePathAttribute
|
||||
│
|
||||
▼
|
||||
Loader 管理:创建或复用 ESF_AssetLoader
|
||||
│
|
||||
▼
|
||||
ESF_AssetLoader:加载依赖包
|
||||
│
|
||||
▼
|
||||
Bundle 读取策略:加载主 AssetBundle
|
||||
│
|
||||
▼
|
||||
ESF_AssetResponse:按原有回调返回资源
|
||||
```
|
||||
|
||||
### 3.2 本地加载策略
|
||||
|
||||
Unity 常用资源路径语义:
|
||||
|
||||
| 路径 | Unity 语义 | 常见平台表现 | 框架用途 |
|
||||
|-----|-----------|-------------|---------|
|
||||
| `Application.streamingAssetsPath` | 随包只读资源目录,构建时原样拷贝 `Assets/StreamingAssets` 内容 | Editor/Windows/Linux 多数是普通目录;macOS、iOS 有各自包内路径;Android 为 APK/JAR 内 URL;WebGL/Web 平台也是 URL 类路径 | 放母包内置 AssetBundle,作为首次启动和无热更文件时的基础资源 |
|
||||
| `Application.persistentDataPath` | 运行期可写、跨启动保留的数据目录 | iOS/Android 更新 App 后通常保留;Windows/macOS/Linux 为用户目录;WebGL/Web 为浏览器持久化文件系统;tvOS 不支持 | 放热更下载后的 AssetBundle,作为 StreamingAssets 缺失后的本地补充资源 |
|
||||
| `Application.dataPath` | 当前项目或 Player 数据目录 | 本框架只允许在 Editor 宏包裹的代码中使用;Editor 下指向项目 `Assets` | 仅用于 Editor 下路径推导和资源分析,不参与 Player 运行期加载策略 |
|
||||
|
||||
框架目标读取优先级:
|
||||
|
||||
```text
|
||||
非 CDN 模式:
|
||||
StreamingAssets Manifest
|
||||
│
|
||||
├─ Manifest 包含目标资源:从 StreamingAssets 加载随包资源
|
||||
│
|
||||
└─ Manifest 不包含目标资源
|
||||
│
|
||||
▼
|
||||
PersistentData Manifest
|
||||
│
|
||||
├─ Manifest 包含目标资源:从 PersistentData 加载热更资源
|
||||
│
|
||||
└─ Manifest 不包含目标资源
|
||||
│
|
||||
▼
|
||||
如果配置 CDN 路径,则从 CDN 下载
|
||||
|
||||
CDN 模式:
|
||||
CDN / 小游戏缓存 / 小游戏平台文件系统
|
||||
│
|
||||
└─ 不走 StreamingAssets / PersistentData fallback
|
||||
```
|
||||
|
||||
具体策略:
|
||||
|
||||
- 非 CDN 模式不直接按文件是否存在做第一判断,而是先读取 StreamingAssets 下的 Manifest。
|
||||
- 如果 StreamingAssets Manifest 包含目标资源,则从 `SFGlobalData.streamingAssetsRoot` 加载对应 AssetBundle。
|
||||
- 如果 StreamingAssets Manifest 不包含目标资源,再读取 PersistentData 下的 Manifest。
|
||||
- 如果 PersistentData Manifest 包含目标资源,则从 `SFGlobalData.assetBundleRoot` 加载对应 AssetBundle。
|
||||
- 如果两个本地 Manifest 都不包含目标资源,且配置了 CDN 路径,则从 CDN 下载目标资源;如果没有配置 CDN 路径,则返回明确失败。
|
||||
- StreamingAssets 是随包母包资源,PersistentData 是热更落盘资源;二者通过各自 Manifest 判断是否拥有目标资源。
|
||||
- PersistentData 命中且是普通文件系统路径时,优先使用 `AssetBundle.LoadFromFileAsync(path)`,避免 `File.ReadAllBytes + LoadFromMemoryAsync` 带来的额外 byte[] 内存峰值。
|
||||
- Android、WebGL/Web 等 StreamingAssets URL 类路径不能用普通 `File.Exists` / `File.ReadAllBytes` 判断和读取,需要通过 `UnityWebRequest` 异步读取。
|
||||
- StreamingAssets 读取到 byte[] 后仍使用 `AssetBundle.LoadFromMemoryAsync(data)`。
|
||||
- `Application.dataPath` 只允许出现在 `UNITY_EDITOR` 宏包裹的编辑器逻辑中,用于路径推导、AssetDatabase 资源定位或构建分析;运行期加载不能依赖该路径。
|
||||
- 显式 CDN 模式仍视为独立资源来源:优先走 CDN/小游戏缓存/小游戏文件系统逻辑,不参与 StreamingAssets 和 PersistentData 的本地 fallback。
|
||||
- 非 CDN 模式下的 CDN 下载只作为本地两个 Manifest 都不包含资源后的最后补充来源,前提是已经配置 CDN 地址。
|
||||
- CDN 下载返回的数据、小程序缓存读取返回的数据、需要 `LoadFileByDecryptFunc` 解密的数据,仍保持 byte[] 加载路径。
|
||||
- 后续如果发现部分平台对 `LoadFromFileAsync` 支持不一致,再增加内部策略开关;第一版不暴露为公开 API。
|
||||
|
||||
### 3.2.1 CDN 热更新落盘路径
|
||||
|
||||
CDN 下载属于热更新链路。下载成功后,需要把资源保存到当前平台可读写的位置,供后续启动或二次加载复用。
|
||||
|
||||
落盘根目录优先级:
|
||||
|
||||
```text
|
||||
外部传入 CDN 保存根目录
|
||||
│
|
||||
├─ 有效:使用外部路径
|
||||
│
|
||||
└─ 无效或未设置:使用 SFGlobalData.assetBundleRoot / PersistentData
|
||||
```
|
||||
|
||||
计划新增接口:
|
||||
|
||||
```csharp
|
||||
public void SetCdnSaveRootPath(string _saveRootPath);
|
||||
```
|
||||
|
||||
行为要求:
|
||||
|
||||
- 外部可在初始化资源系统时传入平台专用保存根目录。
|
||||
- 如果 `_saveRootPath` 为 `null`、空字符串或非法路径,则不覆盖默认值。
|
||||
- 未设置外部路径时,CDN 下载文件统一保存到 `SFGlobalData.assetBundleRoot`,也就是框架的 PersistentData 资源根路径。
|
||||
- 保存路径只影响 CDN 下载后的本地落盘位置,不改变 CDN 下载 URL。
|
||||
- 保存时仍保留 AssetBundle 的相对路径结构,避免不同 bundle 重名覆盖。
|
||||
- 小游戏平台如果有专用文件系统路径,应通过平台宏或外部初始化代码传入,不在通用逻辑里硬编码平台路径。
|
||||
- 后续读取 CDN 已下载文件时,优先从该保存根目录查找;未命中再走 CDN 下载。
|
||||
|
||||
平台隔离原则:
|
||||
|
||||
- 通用 ESF_ 逻辑只维护“外部传入路径优先,否则 PersistentData”的规则。
|
||||
- 平台差异通过宏定义包裹在平台适配层,例如小游戏、WebGL、原生移动端等各自传入保存路径。
|
||||
- 不在业务层暴露 `assetBundleKey` 或内部 Manifest 路径;业务只配置 CDN 地址和可选保存根目录。
|
||||
|
||||
### 3.3 StreamingAssets 异步读取
|
||||
|
||||
`OnReadStreamingAssets` 不再提供阻塞式读取给加载主链路使用。主链路应改为异步读取:
|
||||
|
||||
- 使用 `UnityWebRequest.Get`。
|
||||
- 通过协程或 loader 状态机等待完成。
|
||||
- 下载成功后使用 `AssetBundle.LoadFromMemoryAsync(data)`。
|
||||
- 下载失败时进入统一失败处理或 CDN fallback。
|
||||
|
||||
### 3.4 下载队列
|
||||
|
||||
下载管理器增加内部并发上限,建议默认值为 3。
|
||||
|
||||
要求:
|
||||
|
||||
- 单个 `ESF_DownLoadGroup` 不再以文件数量作为最大并发数。
|
||||
- 高优先级任务优先执行,但不能绕过全局并发上限。
|
||||
- 下载失败保持现有回调语义。
|
||||
- `WaitDownload` 回调保持兼容。
|
||||
|
||||
### 3.5 GC 与 loader 映射
|
||||
|
||||
GC 回收和主动卸载共用同一套内部释放逻辑,避免自动回收与业务手动卸载产生两套状态。
|
||||
|
||||
GC 自动回收时需要同时处理:
|
||||
|
||||
- `AssetBundle.Unload(false)`,默认不销毁已经实例化出去的对象。
|
||||
- Destroy loader GameObject。
|
||||
- 从 `mESF_AssetLoaderMaping` 移除对应 key。
|
||||
- 从生命周期池和内存统计中移除对应 key。
|
||||
|
||||
回收后再次加载同一资源时,必须能够重新创建 loader 并正常触发回调。
|
||||
|
||||
业务主动卸载接口:
|
||||
|
||||
```csharp
|
||||
public bool TryUnloadPrefabInMemory(string _prefabName);
|
||||
public bool TryUnloadAssetInMemory(string _assetName);
|
||||
```
|
||||
|
||||
行为要求:
|
||||
|
||||
- 根据 `_prefabName` 或 `_assetName` 在当前已加载 loader 中查找匹配资源,匹配字段以 `abPathAttr.assetName` 为准。
|
||||
- 找到唯一 loader,且 loader 已完成加载、没有排队请求、没有 active 任务、生命周期不为 ACTIVE 时,执行内部释放流程并返回 `true`。
|
||||
- 未找到 loader、名称匹配多个 loader、loader 未完成加载、资源正在使用、或资源已被释放时,返回 `false`,不抛异常。
|
||||
- 如果资源正在加载中,第一版不强制中断异步加载,避免破坏现有回调语义。
|
||||
- 卸载成功后,`mESF_AssetLoaderMaping`、生命周期池、内存统计必须同步移除对应 key。
|
||||
- 后续再次请求同一资源时,必须走正常创建 loader 和加载流程。
|
||||
- `AssetBundle.Unload(false)` 不会销毁已经实例化出去的 GameObject,但为了避免业务继续依赖原始 asset 对象,业务接口仍必须通过框架引用判断后才允许释放。
|
||||
|
||||
内部卸载能力:
|
||||
|
||||
```csharp
|
||||
bool OnUnloadInMemory(int _assetBundleKey);
|
||||
bool OnCanUnloadInMemory(ESF_AssetLoader _loader);
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- 按 key 卸载只作为框架内部实现和调试面板基础能力,不作为普通业务接口。
|
||||
- 所有卸载入口都必须经过 `OnCanUnloadInMemory` 安全判断。
|
||||
- 后续如果需要更精确的“完全没有引用”,应引入框架级引用计数或资源句柄释放机制;第一版以 loader 生命周期、请求队列和窗口/任务活跃状态作为安全判断依据。
|
||||
|
||||
内存查询接口:
|
||||
|
||||
```csharp
|
||||
public long GetLoadedAssetBundleMemorySize();
|
||||
```
|
||||
|
||||
行为要求:
|
||||
|
||||
- 返回当前 AssetBundle 加载器统计到的内存大小,单位为 byte。
|
||||
- 统计值以 loader 的 `Size` 为准;`LoadFromFileAsync` 无 byte[] 时,使用文件长度作为估算值。
|
||||
- 每次主动卸载或 GC 回收后同步扣减。
|
||||
- 只作为运行时查看和调试依据,不作为精确 Profiler 替代。
|
||||
|
||||
## 四、分批实施顺序
|
||||
|
||||
### 第一批:低风险修复
|
||||
|
||||
- 修正 `ESF_ABMgr.progress` 无 loader 时除零问题。
|
||||
- 新增 `TryUnloadPrefabInMemory(...)` 和 `TryUnloadAssetInMemory(...)` 业务主动卸载接口。
|
||||
- 新增 `GetLoadedAssetBundleMemorySize()` 内存统计查询接口。
|
||||
- 下载队列增加内部并发上限。
|
||||
- GC 回收时同步移除失效 loader。
|
||||
- 保持现有公开接口兼容。
|
||||
|
||||
### 第二批:加载策略重构
|
||||
|
||||
- 为 `ESF_AssetLoader` 引入更清晰的内部状态。
|
||||
- 非 CDN 模式读取优先级改为 `StreamingAssets Manifest -> PersistentData Manifest -> CDN fallback`。
|
||||
- Manifest 管理支持同时维护 StreamingAssets 与 PersistentData 两套本地 Manifest 查询结果。
|
||||
- CDN 下载支持外部传入保存根目录,未设置时默认保存到 PersistentData。
|
||||
- PersistentData 普通文件命中时优先使用 `AssetBundle.LoadFromFileAsync`。
|
||||
- 保留 StreamingAssets、CDN、小游戏缓存的 byte[] 加载路径。
|
||||
- 保持 `ESF_AssetResponse.GetAsset<T>()` 行为不变。
|
||||
|
||||
### 第三批:StreamingAssets 异步化
|
||||
|
||||
- 移除主链路中的忙等读取。
|
||||
- 将 StreamingAssets 读取接入 loader 状态机。
|
||||
- 统一成功、失败、fallback、进度回调。
|
||||
|
||||
### 第四批:内部封装整理
|
||||
|
||||
- 保留 `ESF_ABMgr` 统一入口和 partial 分布类组织方式。
|
||||
- 在不改变公开接口的前提下,将 Manifest、Loader 管理、Bundle 读取策略、下载队列、GC 策略封装成更清晰的内部方法组或内部服务。
|
||||
- 清理重复路径拼接和重复错误处理。
|
||||
|
||||
## 五、验证方案
|
||||
|
||||
验证以 Editor 菜单测试为主,建议新增或复用一个内部测试入口,用于覆盖以下场景:
|
||||
|
||||
| 场景 | 验证点 |
|
||||
|-----|-------|
|
||||
| Editor 模式加载 prefab | `LoadInMemory` 成功回调,`GetAsset<GameObject>()` 正常 |
|
||||
| PersistentData 本地 AB | 普通文件路径优先走 `LoadFromFileAsync` |
|
||||
| StreamingAssets 加载 | 不出现主线程忙等卡死 |
|
||||
| Manifest 缺失 | 返回清晰失败日志,不静默卡住 |
|
||||
| 资源缺失 | 触发 failed 回调,错误信息包含资源路径 |
|
||||
| 语言回退 | 当前语言缺资源时回退默认语言 |
|
||||
| CDN/边玩边下 | 触发 `WaitDownload`,下载完成后继续加载 |
|
||||
| SpriteAtlas 请求 | `SpriteAtlasManager.atlasRequested` 能正确加载图集 |
|
||||
| 多资源并发下载 | 并发数不超过内部上限 |
|
||||
| 非 CDN 读取优先级 | StreamingAssets Manifest 包含目标资源时不读取 PersistentData 资源 |
|
||||
| PersistentData fallback | StreamingAssets Manifest 不包含目标资源时,再查 PersistentData Manifest |
|
||||
| CDN fallback | 两个本地 Manifest 都不包含目标资源且配置 CDN 时,触发 CDN 下载 |
|
||||
| 无 CDN fallback | 两个本地 Manifest 都不包含目标资源且未配置 CDN 时,返回明确失败 |
|
||||
| CDN 模式路径隔离 | CDN 模式不走 StreamingAssets / PersistentData fallback |
|
||||
| CDN 默认落盘路径 | 未设置外部保存路径时,下载文件保存到 PersistentData 资源根目录 |
|
||||
| CDN 外部落盘路径 | 设置外部保存路径时,下载文件保存到外部路径并保留相对目录结构 |
|
||||
| 平台路径隔离 | 平台专用保存路径通过宏或初始化代码传入,通用逻辑不硬编码平台路径 |
|
||||
| GC 后重新加载 | 回收后能重新创建 loader 并成功加载 |
|
||||
| 主动卸载资源 | `TryUnloadPrefabInMemory` 返回成功,内存统计下降,再次加载正常 |
|
||||
| 正在使用资源卸载 | 资源处于 ACTIVE 或仍有框架侧引用时返回 `false`,游戏对象不受影响 |
|
||||
| 重名资源卸载 | 多个已加载资源同名时返回 `false`,要求后续使用更明确名称或内部诊断工具处理 |
|
||||
| 查询加载内存 | `GetLoadedAssetBundleMemorySize` 返回 byte 数,加载/卸载后变化合理 |
|
||||
|
||||
## 六、风险点
|
||||
|
||||
- `LoadFileByDecryptFunc` 依赖 byte[],如果某些本地包需要解密,不能直接走 `LoadFromFileAsync`。
|
||||
- StreamingAssets 在不同平台路径格式不同,异步读取必须保留原有平台兼容性。
|
||||
- 小游戏环境使用 `MiniGame` 抽象,不能直接套用普通文件系统逻辑。
|
||||
- GC 逻辑改动会影响资源复用,需要重点验证窗口、图集、材质、文本配置等常驻资源。
|
||||
- CopyTo 后 `ESF_` 会替换为 `HF_`,新增内部类命名必须遵守替换规则,避免生成后引用不一致。
|
||||
|
||||
## 七、Unity 路径参考
|
||||
|
||||
- Unity Manual - Streaming Assets: `https://docs.unity3d.com/2022.3/Documentation/Manual/StreamingAssets.html`
|
||||
- Unity API - `Application.streamingAssetsPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-streamingAssetsPath.html`
|
||||
- Unity API - `Application.persistentDataPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-persistentDataPath.html`
|
||||
- Unity API - `Application.dataPath`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/Application-dataPath.html`
|
||||
- Unity API - `AssetBundle.LoadFromMemoryAsync`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/AssetBundle.LoadFromMemoryAsync.html`
|
||||
- Unity API - `AssetBundle.LoadFromFileAsync`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/AssetBundle.LoadFromFileAsync.html`
|
||||
- Unity API - `BuildAssetBundleOptions.ChunkBasedCompression`: `https://docs.unity3d.com/2022.3/Documentation/ScriptReference/BuildAssetBundleOptions.ChunkBasedCompression.html`
|
||||
|
||||
## 八、默认决策
|
||||
|
||||
- 第一阶段只文档化方案,不改 ESF_ 代码。
|
||||
- 后续代码实现只改 `ESF_` 源头,不直接改 `HF_` 副本。
|
||||
- 公开接口保持兼容。
|
||||
- 允许新增 `TryUnloadPrefabInMemory(...)`、`TryUnloadAssetInMemory(...)` 和 `GetLoadedAssetBundleMemorySize()` 作为可选公开能力。
|
||||
- 允许新增 `SetCdnSaveRootPath(...)` 作为 CDN 热更新落盘根目录配置入口。
|
||||
- 允许新增内部 `ESF_` 类和内部辅助方法。
|
||||
- 非 CDN 模式默认按 Manifest 查找顺序读取:StreamingAssets Manifest、PersistentData Manifest、CDN fallback。
|
||||
- PersistentData 普通文件默认优先 `LoadFromFileAsync`。
|
||||
- StreamingAssets、CDN、小游戏路径保持内存加载。
|
||||
- 下载证书校验暂不作为第一批主目标,后续单独处理。
|
||||
- CDN 下载保存根目录允许外部传入;未传入时默认使用 PersistentData 资源根目录。
|
||||
|
||||
---
|
||||
|
||||
## 文档关联
|
||||
|
||||
> 当本文档发生变更时,应同步检查以下关联文档,避免信息不一致。
|
||||
|
||||
| 文档 | 关联原因 |
|
||||
|---|---|
|
||||
| [资源管理入口](README.md) | 本文件是资源管理模块的子文档 |
|
||||
| [资源管理使用指南](使用指南.md) | 当前资源加载使用指南 |
|
||||
| [modules/模块索引.md](../../模块索引.md) | 模块索引是项目知识总入口 |
|
||||
| [rules/10-架构说明.md](../../../rules/10-架构说明.md) | 架构说明定义总体分层和依赖方向 |
|
||||
| [rules/30-模块登记规则.md](../../../rules/30-模块登记规则.md) | 模块登记规则定义分类和状态口径 |
|
||||
@@ -31,7 +31,7 @@
|
||||
| [模拟行为系统](frameworks/通用框架/README.md) | `Assets/StrayFog/Core/SimulateBehaviour/` | 解耦 MonoBehaviour,模拟完整生命周期 | 已完成 |
|
||||
| [对象池](frameworks/内存对象管理/README.md) | `Assets/Game/GameHFScripte/CopyToHF/MemoryManager/` | GameObject 对象池、脚本对象池、类实例池 | 已完成 |
|
||||
| [事件系统](frameworks/事件系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/Event/` | CTC/STC 事件参数和事件派发 | 已完成 |
|
||||
| [资源管理](frameworks/资源管理/README.md) | `Assets/Game/GameHFScripte/CopyToHF/AssetBundle/` | AssetBundle、语言资源、图集、加载规则 | 已完成 |
|
||||
| [资源管理](frameworks/资源管理/README.md) | `Assets/Game/GameHFScripte/CopyToHF/AssetBundle/` | AssetBundle、语言资源、图集、加载规则;含 [资源加载重构方案](frameworks/资源管理/资源加载重构方案.md) | 已完成 |
|
||||
| [相机系统](frameworks/相机系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/` | 多相机管理、相机堆栈、渲染顺序 | 初步梳理 |
|
||||
| [实体系统](frameworks/实体系统/README.md) | `Assets/Game/GameHFScripte/GameFunction/GameEntity/` | 实体基类、模型/系统、实体池 | 已完成 |
|
||||
| [UI 窗口系统](frameworks/UI窗口系统/README.md) | `Assets/Game/GameHFScripte/CopyToHF/UIWindowMgr/` | UI 窗口管理、窗口基类、生命周期 | 已完成 |
|
||||
@@ -49,6 +49,8 @@
|
||||
|---|---|---|---|
|
||||
| [SF Editor](frameworks/编辑器工具/README.md#sf-editor) | `Assets/StrayFog/Editor/` | StrayFog 编辑器扩展 | 待验证 |
|
||||
| [项目 Editor Assets](frameworks/编辑器工具/README.md#project-editor-assets) | `Assets/Editor/` | 项目编辑器资源与脚本 | 待验证 |
|
||||
| [UI 拼接工具](frameworks/编辑器工具/UI拼接工具.md) | `Assets/Editor/Command/` | UI 预制体 JSON 双向转换 | 待验证 |
|
||||
| [MCP 工具](frameworks/编辑器工具/MCP工具/README.md) | `Tools/CodexUnityMcp/` | Codex ↔ Unity Editor MCP | 待验证 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user