Files
XCEngine/docs/api/_guides/Scripting/Project-Script-Assembly-And-Field-Sync.md

124 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)