在Unity中使用Json存储地图游戏对象状态的实现方法咨询
实现方案
核心思路
所有可交互场景对象(树木、岩石、木桶等)绑定全局唯一ID,通过ID关联运行时状态与存档数据,读档时根据存档状态控制对象的生成/销毁、坐标与旋转赋值。
具体实现步骤
1. 给可交互对象加唯一标识组件
给所有需要存档的场景对象挂载下面的SceneObjectIdentifier组件,编辑阶段就生成固定GUID,避免运行时动态生成导致ID变化。
using UnityEngine; // 挂在所有需要存档的场景静态对象上 public class SceneObjectIdentifier : MonoBehaviour { [Header("对象唯一标识,编辑阶段自动生成不要手动修改")] public string guid; [Header("对象类型,匹配资源库中的Prefab名")] public string objectType; // 编辑模式下自动生成GUID,只在第一次挂载/对象新建时生成 #if UNITY_EDITOR private void OnValidate() { if (string.IsNullOrEmpty(guid)) { guid = System.Guid.NewGuid().ToString(); } } #endif }
2. 定义存档序列化结构
在你的存档数据类里加入场景对象的存储结构:
using System; using UnityEngine; [Serializable] public class SceneObjectSaveData { public string guid; // 标记对象是否已被销毁,true=已销毁,false=存在 public bool isDestroyed; // 坐标 public float posX, posY, posZ; // 旋转(欧拉角,存储更直观) public float rotX, rotY, rotZ; public SceneObjectSaveData() {} public SceneObjectSaveData(SceneObjectIdentifier obj) { guid = obj.guid; isDestroyed = false; Vector3 pos = obj.transform.position; posX = pos.x; posY = pos.y; posZ = pos.z; Vector3 rot = obj.transform.eulerAngles; rotX = rot.x; rotY = rot.y; rotZ = rot.z; } } // 你的主存档类里新增这个字段 [Serializable] public class GameSaveData { // 你原来的玩家数据、背包数据等字段保留 // ... // 新增场景对象存储列表 public SceneObjectSaveData[] sceneObjects; }
3. 实现场景对象管理单例
做全局单例统一管理所有场景对象的状态、存档、读档逻辑:
using System.Collections.Generic; using UnityEngine; public class SceneObjectManager : MonoBehaviour { public static SceneObjectManager Instance; // 存储所有当前场景存在的对象 private Dictionary<string, SceneObjectIdentifier> sceneObjects = new Dictionary<string, SceneObjectIdentifier>(); // 存储已被销毁的对象ID private HashSet<string> destroyedObjects = new HashSet<string>(); private void Awake() { if (Instance == null) Instance = this; else Destroy(gameObject); } // 场景加载完调用,缓存所有场景里的可存档对象 public void InitSceneObjects() { sceneObjects.Clear(); destroyedObjects.Clear(); var allObjects = FindObjectsOfType<SceneObjectIdentifier>(); foreach (var obj in allObjects) { if (!sceneObjects.ContainsKey(obj.guid)) { sceneObjects.Add(obj.guid, obj); } } } // 销毁对象时调用,先标记再销毁 public void DestroyObject(string guid) { if (sceneObjects.TryGetValue(guid, out var obj)) { destroyedObjects.Add(guid); Destroy(obj.gameObject); sceneObjects.Remove(guid); } } // 存档时调用,生成所有场景对象的存档数据 public SceneObjectSaveData[] GetSaveData() { List<SceneObjectSaveData> saveList = new List<SceneObjectSaveData>(); // 先存当前存在的对象 foreach (var obj in sceneObjects.Values) { saveList.Add(new SceneObjectSaveData(obj)); } // 再存已销毁的对象,标记状态 foreach (var guid in destroyedObjects) { saveList.Add(new SceneObjectSaveData() { guid = guid, isDestroyed = true }); } return saveList.ToArray(); } // 读档时调用,加载场景对象状态 public void LoadSaveData(SceneObjectSaveData[] saveData) { if (saveData == null || saveData.Length == 0) return; foreach (var data in saveData) { if (data.isDestroyed) { // 已销毁的对象,场景里存在就直接删掉 if (sceneObjects.TryGetValue(data.guid, out var destroyObj)) { Destroy(destroyObj.gameObject); sceneObjects.Remove(data.guid); } destroyedObjects.Add(data.guid); } else { // 存在的对象,赋值坐标和旋转 if (sceneObjects.TryGetValue(data.guid, out var existObj)) { existObj.transform.position = new Vector3(data.posX, data.posY, data.posZ); existObj.transform.eulerAngles = new Vector3(data.rotX, data.rotY, data.rotZ); } // 如果你是动态生成对象的方案,这里加实例化Prefab的逻辑即可 } } } }
4. 接入你原有存档流程
- 场景加载完成后,先调用
SceneObjectManager.Instance.InitSceneObjects()缓存所有对象 - 玩家销毁对象时(比如砍树、打碎木桶),不要直接调用
Destroy,改为调用SceneObjectManager.Instance.DestroyObject(obj.guid) - 存档时,把
SceneObjectManager.Instance.GetSaveData()的返回值赋值给GameSaveData的sceneObjects字段,再序列化为Json即可 - 读档时,把Json反序列化为
GameSaveData后,调用SceneObjectManager.Instance.LoadSaveData(gameSaveData.sceneObjects)即可
可选优化
- 如果场景对象数量过多,可以按区块拆分存储,只加载玩家所在区块的对象数据,减少读档耗时
- 如果对象有额外状态(比如树木的剩余血量、木桶里的道具),可以直接扩展
SceneObjectSaveData结构添加对应字段即可
内容的提问来源于stack exchange,提问作者Máté Potyesz
相关产品推荐
相关产品推荐

