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

4.7 KiB
Raw Blame History

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

字段值的三层来源

当前脚本字段至少可能来自三层:

  1. 类默认值 来自 MonoScriptRuntime::TryGetClassFieldDefaultValues(),反映 C# 初始化后的真实默认状态。
  2. 存储覆盖值 来自 ScriptComponent::GetFieldStorage(),会进入场景序列化。
  3. 活体托管值 来自当前运行中的托管实例。

ScriptEngine::TryGetScriptFieldModel() 会把这三层合并成 ScriptFieldModel

  • defaultValue 表示类默认值
  • storedValue 表示场景/本地缓存中的覆盖值
  • value + valueSource 表示当前真正应该展示给 UI 的值

创建实例时字段怎么进入托管世界

MonoScriptRuntime::CreateScriptInstance() 当前会按这条顺序工作:

  1. 查类元数据
  2. 创建托管对象
  3. 注入 gameObjectUUIDscriptComponentUUID
  4. 遍历 ScriptFieldStorage
  5. 只把“字段名存在且类型匹配”的覆盖值写进托管实例

这意味着:

  • 场景里保存的覆盖值可以覆盖类默认值
  • 存储里已经遗留、但脚本类里不存在的字段,不会被写入托管实例
  • 类型不匹配的字段也不会被偷偷应用

生命周期后为什么还要回写

ScriptEngine 每次调用生命周期方法后,都会紧接着调用运行时的 SyncManagedFieldsToStorage()

Mono 当前只会回写:

  • 本地已经存在于 ScriptFieldStorage 的字段
  • 且字段在类元数据里仍然存在
  • 且存储类型与类声明类型匹配

这样设计的好处是:

  • 不会把运行时临时字段自动污染到场景数据
  • 类型漂移会显式表现成 TypeMismatch
  • 存储层和托管层的职责边界更清楚

批量编辑和清除覆盖怎么工作

当前推荐给编辑器用的不是直接操作 ScriptFieldStorage,而是:

原因很直接:

  • 批量写会同时校验类元数据、活体实例和本地存储
  • 清除覆盖会把活体托管字段恢复到类默认值,而不是只删一份本地缓存

如果脚本类已经丢失,系统仍允许对现有存储字段做有限编辑或删除,但会用 StoredOnly / Missing 这类状态明确告诉你当前已经脱离类声明。

当前限制

  • 只支持受支持类型的 publicstatic 实例字段
  • 只发现应用程序集里的非抽象 MonoBehaviour 子类
  • 没有热重载或程序集增量刷新
  • 不会自动把“运行时新增但本地不存在”的字段持久化

推荐阅读

  1. Scripting
  2. MonoScriptRuntime
  3. ScriptEngine
  4. ScriptField