You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 00:15:05