124 lines
4.7 KiB
Markdown
124 lines
4.7 KiB
Markdown
|
|
# Project Script Assembly And Field Sync
|
|||
|
|
|
|||
|
|
## 这份指南解决什么问题
|
|||
|
|
|
|||
|
|
脚本模块这轮更新之后,已经不只是“Mono 能不能跑起来”的问题,还包括两条更具体的链路:
|
|||
|
|
|
|||
|
|
1. `project/Assets/**/*.cs` 怎么进入当前运行时能发现的 `GameScripts.dll`
|
|||
|
|
2. 字段默认值、场景存储覆盖值、活体托管值到底是怎么互相覆盖和同步的
|
|||
|
|
|
|||
|
|
这份指南就是把这两条链路放在一起说明。
|
|||
|
|
|
|||
|
|
## 项目脚本程序集是怎么生成的
|
|||
|
|
|
|||
|
|
`managed/CMakeLists.txt` 当前会构建两组托管输出:
|
|||
|
|
|
|||
|
|
- 通用脚本核心程序集:`XCEngine.ScriptCore.dll`
|
|||
|
|
- 游戏脚本程序集:`GameScripts.dll`
|
|||
|
|
|
|||
|
|
同时它还会扫描 `project/Assets/**/*.cs`,并把这些项目资产脚本编译到:
|
|||
|
|
|
|||
|
|
- `project/Library/ScriptAssemblies/XCEngine.ScriptCore.dll`
|
|||
|
|
- `project/Library/ScriptAssemblies/GameScripts.dll`
|
|||
|
|
- `project/Library/ScriptAssemblies/mscorlib.dll`
|
|||
|
|
|
|||
|
|
如果项目目录下暂时没有任何 `.cs` 文件,CMake 会生成一个占位源文件,保证 `GameScripts.dll` 仍然存在。
|
|||
|
|
|
|||
|
|
## 运行时如何接入这份项目程序集
|
|||
|
|
|
|||
|
|
`MonoScriptRuntime::Settings` 可以只指定:
|
|||
|
|
|
|||
|
|
- `assemblyDirectory`
|
|||
|
|
- `corlibDirectory`
|
|||
|
|
- 或更显式的 `coreAssemblyPath` / `appAssemblyPath`
|
|||
|
|
|
|||
|
|
`ResolveSettings()` 会根据这些字段补全剩余路径。当前 `tests/scripting/test_project_script_assembly.cpp` 的做法就是把 `assemblyDirectory` 指到 `project/Library/ScriptAssemblies`,再验证运行时能发现项目资产脚本。
|
|||
|
|
|
|||
|
|
已验证的真实行为包括:
|
|||
|
|
|
|||
|
|
- 运行时能发现 `ProjectScripts.ProjectScriptProbe`
|
|||
|
|
- 能返回它的字段元数据
|
|||
|
|
- 能返回它的默认字段值:
|
|||
|
|
- `EnabledOnBoot = true`
|
|||
|
|
- `Label = "ProjectScriptProbe"`
|
|||
|
|
- `Speed = 2.5f`
|
|||
|
|
|
|||
|
|
项目样例脚本当前位于 [ProjectScriptProbe.cs](../../../../project/Assets/Scripts/ProjectScriptProbe.cs)。
|
|||
|
|
|
|||
|
|
## 字段值的三层来源
|
|||
|
|
|
|||
|
|
当前脚本字段至少可能来自三层:
|
|||
|
|
|
|||
|
|
1. 类默认值
|
|||
|
|
来自 `MonoScriptRuntime::TryGetClassFieldDefaultValues()`,反映 C# 初始化后的真实默认状态。
|
|||
|
|
2. 存储覆盖值
|
|||
|
|
来自 `ScriptComponent::GetFieldStorage()`,会进入场景序列化。
|
|||
|
|
3. 活体托管值
|
|||
|
|
来自当前运行中的托管实例。
|
|||
|
|
|
|||
|
|
`ScriptEngine::TryGetScriptFieldModel()` 会把这三层合并成 `ScriptFieldModel`:
|
|||
|
|
|
|||
|
|
- `defaultValue` 表示类默认值
|
|||
|
|
- `storedValue` 表示场景/本地缓存中的覆盖值
|
|||
|
|
- `value` + `valueSource` 表示当前真正应该展示给 UI 的值
|
|||
|
|
|
|||
|
|
## 创建实例时字段怎么进入托管世界
|
|||
|
|
|
|||
|
|
`MonoScriptRuntime::CreateScriptInstance()` 当前会按这条顺序工作:
|
|||
|
|
|
|||
|
|
1. 查类元数据
|
|||
|
|
2. 创建托管对象
|
|||
|
|
3. 注入 `gameObjectUUID` 和 `scriptComponentUUID`
|
|||
|
|
4. 遍历 `ScriptFieldStorage`
|
|||
|
|
5. 只把“字段名存在且类型匹配”的覆盖值写进托管实例
|
|||
|
|
|
|||
|
|
这意味着:
|
|||
|
|
|
|||
|
|
- 场景里保存的覆盖值可以覆盖类默认值
|
|||
|
|
- 存储里已经遗留、但脚本类里不存在的字段,不会被写入托管实例
|
|||
|
|
- 类型不匹配的字段也不会被偷偷应用
|
|||
|
|
|
|||
|
|
## 生命周期后为什么还要回写
|
|||
|
|
|
|||
|
|
`ScriptEngine` 每次调用生命周期方法后,都会紧接着调用运行时的 `SyncManagedFieldsToStorage()`。
|
|||
|
|
|
|||
|
|
Mono 当前只会回写:
|
|||
|
|
|
|||
|
|
- 本地已经存在于 `ScriptFieldStorage` 的字段
|
|||
|
|
- 且字段在类元数据里仍然存在
|
|||
|
|
- 且存储类型与类声明类型匹配
|
|||
|
|
|
|||
|
|
这样设计的好处是:
|
|||
|
|
|
|||
|
|
- 不会把运行时临时字段自动污染到场景数据
|
|||
|
|
- 类型漂移会显式表现成 `TypeMismatch`
|
|||
|
|
- 存储层和托管层的职责边界更清楚
|
|||
|
|
|
|||
|
|
## 批量编辑和清除覆盖怎么工作
|
|||
|
|
|
|||
|
|
当前推荐给编辑器用的不是直接操作 `ScriptFieldStorage`,而是:
|
|||
|
|
|
|||
|
|
- [ScriptEngine::ApplyScriptFieldWrites](../../XCEngine/Scripting/ScriptEngine/ApplyScriptFieldWrites.md)
|
|||
|
|
- [ScriptEngine::ClearScriptFieldOverrides](../../XCEngine/Scripting/ScriptEngine/ClearScriptFieldOverrides.md)
|
|||
|
|
|
|||
|
|
原因很直接:
|
|||
|
|
|
|||
|
|
- 批量写会同时校验类元数据、活体实例和本地存储
|
|||
|
|
- 清除覆盖会把活体托管字段恢复到类默认值,而不是只删一份本地缓存
|
|||
|
|
|
|||
|
|
如果脚本类已经丢失,系统仍允许对现有存储字段做有限编辑或删除,但会用 `StoredOnly` / `Missing` 这类状态明确告诉你当前已经脱离类声明。
|
|||
|
|
|
|||
|
|
## 当前限制
|
|||
|
|
|
|||
|
|
- 只支持受支持类型的 `public` 非 `static` 实例字段
|
|||
|
|
- 只发现应用程序集里的非抽象 `MonoBehaviour` 子类
|
|||
|
|
- 没有热重载或程序集增量刷新
|
|||
|
|
- 不会自动把“运行时新增但本地不存在”的字段持久化
|
|||
|
|
|
|||
|
|
## 推荐阅读
|
|||
|
|
|
|||
|
|
1. [Scripting](../../XCEngine/Scripting/Scripting.md)
|
|||
|
|
2. [MonoScriptRuntime](../../XCEngine/Scripting/Mono/MonoScriptRuntime/MonoScriptRuntime.md)
|
|||
|
|
3. [ScriptEngine](../../XCEngine/Scripting/ScriptEngine/ScriptEngine.md)
|
|||
|
|
4. [ScriptField](../../XCEngine/Scripting/ScriptField/ScriptField.md)
|