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缓存场景数据,减少运行时开销。
此方悬停