1. 世界管理核心组件
世界管理逻辑由以下几个核心脚本协同实现:
- WorldManager: 负责世界数据的核心管理,包括创建、保存、加载、删除世界,以及跟踪和管理场景中的动态对象。
- WorldUploader: 处理世界的导入和导出功能,将世界数据序列化为文件并支持压缩存档。
- SceneCacheManager: 缓存场景中的对象数据,优化场景加载和对象管理的性能。
- PrefabNamingManager: 管理世界中预制体对象的命名,确保对象名称在世界中的唯一性。
- MainMenuController: 提供用户界面交互,允许玩家通过UI创建、选择、加载和删除世界。
2. 世界数据结构
世界数据由 WorldManager.WorldData
类定义,包含以下关键字段:
worldName
: 世界名称,用于标识世界。baseSceneName
: 世界基于的场景名称(如BaseScene
、ForestScene
)。saveTime
: 保存时间,记录世界的最后保存时间。objectStates
: 存储场景中动态对象的状态(位置、旋转、缩放、脚本等)。trackedObjects
: 跟踪当前世界中的动态对象(GameObject)。
每个对象的状态由 WorldManager.ObjectState
定义,包含:
name
: 对象名称(代替GUID,确保唯一性)。prefabPath
: 对象的预制体路径。position
,rotation
,scale
: 对象的变换信息。nativeScript
,compiledScripts
: 对象的脚本数据(原生脚本和编译后的脚本)。
3. 世界管理逻辑
3.1 世界创建
- 触发方式: 通过
MainMenuController
的“新建世界”面板,用户输入世界名称并选择基础场景(BaseScene
,ForestScene
,DesertScene
)。 - 流程:
MainMenuController
调用WorldManager.CreateNewWorld
,传入世界名称和基础场景名称。WorldManager
异步加载指定场景(SceneManager.LoadSceneAsync
)。- 创建新的
WorldData
实例,初始化世界名称、场景名称、保存时间等。 - 扫描场景中的根对象(通过
CollectSceneObjects
),将动态对象添加到trackedObjects
。 - 将新世界添加到
worlds
列表,更新currentWorldIndex
。 - 保存世界列表(
SaveWorldList
)到持久化存储(使用 ES3)。
- 命名管理:
PrefabNamingManager
确保场景中对象的名称唯一,基于基础名称递增计数(如Object (1)
,Object (2)
)。
3.2 世界保存
- 触发方式: 通过
WorldManager.SaveCurrentWorld
手动触发,或在创建/加载世界时自动调用。 - 流程:
- 获取当前世界(
worlds[currentWorldIndex]
)和场景中的动态对象(trackedObjects
)。 - 如果
trackedObjects
为空,重新扫描场景动态对象(CollectSceneObjects
)。 - 遍历动态对象,生成
ObjectState
(包括名称、预制体路径、变换信息、脚本等)。 - 分批保存对象状态(每批50个对象,优化性能)。
- 使用 ES3 将对象状态(
World_{index}_Objects
)和世界数据(World_{index}
)保存到持久化存储。 - 更新世界列表(
SaveWorldList
)。
- 获取当前世界(
- 注意: 跳过带有
UnableToSave
标签的对象,确保只保存动态对象。
3.3 世界加载
- 触发方式: 通过
MainMenuController
的世界列表选择世界,调用WorldManager.LoadWorld
。 - 流程:
- 异步加载目标世界的场景(
baseSceneName
)。 - 获取当前场景对象(
CollectSceneObjects
)和保存的对象状态(World_{index}_Objects
)。 - 比较当前场景对象和保存状态的名称:
- 删除多余对象(场景中有但保存状态中没有)。
- 实例化缺失对象(保存状态中有但场景中没有),通过
Resources.Load
加载预制体。
- 恢复对象状态(位置、旋转、缩放、脚本等),分批处理(每批50个对象)。
- 更新
trackedObjects
和currentWorldIndex
。
- 异步加载目标世界的场景(
- 脚本恢复: 如果对象有脚本(
nativeScript
或compiledScripts
),通过ScriptCompiler
重新编译并应用。
3.4 世界导入/导出
- 导出(
WorldUploader.ExportWorld
):- 收集当前世界数据(
ExportWorldData
),包括世界名称、场景名称、保存时间、对象状态、预制体编号表(prefabNumberTable
)和蓝图数据(blueprints
)。 - 使用 ES3 保存数据到
.es3
文件。 - 将
.es3
文件压缩为.zip
文件,删除临时文件。 - 提供进度回调(
onProgress
)和完成回调(onComplete
)。
- 收集当前世界数据(
- 导入(
WorldUploader.ImportWorld
):- 解压
.zip
文件到临时目录,提取.es3
文件。 - 加载
ExportWorldData
,验证数据有效性(世界名称、场景名称等)。 - 创建新世界,生成唯一的世界名称(避免冲突,如
World (1)
)。 - 保存对象状态、预制体编号表和蓝图数据到 ES3。
- 更新世界列表,通知
PrefabNamingManager
切换世界。 - 删除临时目录。
- 解压
- 注意: 导入时会检查数据完整性(如
objectStates
和prefabNumberTable
是否存在)。
3.5 世界删除
- 触发方式: 通过
MainMenuController
的世界列表删除按钮,调用WorldManager.DeleteWorld
。 - 流程:
- 删除指定世界的持久化数据(
World_{index}_Objects
和World_{index}
)。 - 从
worlds
列表移除世界。 - 更新
currentWorldIndex
(如果当前世界被删除)。 - 保存更新后的世界列表。
PrefabNamingManager.DeleteWorldCounters
清除该世界的命名计数器。
- 删除指定世界的持久化数据(
4. 场景缓存管理
- 作用:
SceneCacheManager
缓存场景中的对象数据,减少重复扫描的开销。 - 流程:
- 启动时加载缓存(
ES3.Load("SceneCache")
),如果不存在则生成。 - 生成缓存(
GenerateSceneCache
):- 异步加载模板场景(
templateScenes
)。 - 扫描场景中的根对象,跳过
UnableToSave
标签的对象。 - 记录对象名称和预制体路径(
SceneObjectData
)。 - 保存缓存到 ES3。
- 异步加载模板场景(
- 提供
GetSceneObjectData
接口,供其他脚本查询场景对象数据。
- 启动时加载缓存(
- 优化: 使用正则表达式清理对象名称(去除
(Clone)
或(1)
等后缀)。
5. 用户界面交互
- 主菜单 (
MainMenuController
):- 显示主菜单、设置、退出按钮。
- “进入世界”按钮显示世界管理面板。
- 世界管理面板:
- 列出所有世界(名称、保存时间、大小),支持加载和删除。
- “新建世界”按钮显示创建世界面板。
- 创建世界面板:
- 输入世界名称,选择基础场景。
- 验证输入(名称非空、场景已选)后调用
WorldManager.CreateNewWorld
。
- 加载面板:
- 显示加载进度(场景加载占50%,对象恢复占50%)。
- 使用
loadingSlider
和loadingText
提供反馈。
- 错误提示:
- 显示错误消息(如“世界名称不能为空”),用户确认后关闭。
6. 关键优化
- 分批处理: 保存和加载对象状态时分批处理(每批50个对象),避免性能瓶颈。
- 异步加载: 使用
SceneManager.LoadSceneAsync
和协程(IEnumerator
)异步加载场景和处理数据。 - 持久化存储: 使用 ES3 序列化世界数据,确保数据在游戏重启后保留。
- 名称唯一性:
PrefabNamingManager
确保对象名称唯一,防止冲突。 - 缓存机制:
SceneCacheManager
缓存场景数据,减少运行时开销。
此方悬停