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