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

如何处理运行时需修改数据的Scriptable Object物品

问题背景与现有代码

我有一个Item类用于存储ItemSO脚本对象:

public class Item : MonoBehaviour
{
    [SerializeField] private ItemSO inventoryItem;
}

ItemSO是抽象类,存储所有物品共有的通用信息:

public abstract class ItemSO : ScriptableObject
{
    public string ItemName;
    public GameObject prefab;
}

每种特定物品类型都继承自ItemSO,比如燃料类:

public class FuelItemSO : ItemSO, IDestroyableItem
{
    [Tooltip("Burn Time of the fuel in minutes")]
    public float BurnTimeIncreasePerKg;
    public float TemperatureIncreasePerKg;    
}

此前用Scriptable Object做数据容器一切正常,但现在需要添加可修改变量的物品类型,比如容器类:

public class ContainerItemSO : ItemSO, IDestroyableItem
{
    public float maxAmount;
    public float CurrentAmount;    
}

我无法让Item类根据ItemSO的类型获取和修改可变数据(比如给容器加水)。曾考虑让ContainerItemSO继承IHaveChangableData接口:

public interface IHaveChangableData 
{
    ContainerData GetData();
}

并通过ContainerData存储变量,但不知道如何存储ItemSO的特定数据,不确定这种方式是否过于复杂。同时想了解:如果有多个可修改数据的物品,是否应该从Scriptable Object切换为普通类?


解决方案

1. 核心问题:ScriptableObject的特性

ScriptableObject是共享数据容器,修改它的字段会影响所有引用它的实例。像CurrentAmount这种每个物品实例独有的可变数据,绝对不能放在ScriptableObject里——这会导致所有容器物品的当前水量同步变化,完全不符合需求。

2. 正确方案:模板与实例数据分离

把数据拆成两类:

  • 模板数据(不可变):放在ScriptableObject中,比如物品名称、最大容量maxAmount、预制体等,同类型物品共享。
  • 实例数据(可变):每个物品实例单独存储,比如CurrentAmount,放在Item类或专门的实例数据类中。

具体实现步骤

步骤1:修改ContainerItemSO,只保留模板数据

public class ContainerItemSO : ItemSO, IDestroyableItem
{
    public float maxAmount; // 最大容量是模板数据,不可变
    // 移除CurrentAmount,它属于实例可变数据
}

步骤2:在Item类中存储实例可变数据

给Item类添加字段存储可变数据,用字典适配多种类型:

public class Item : MonoBehaviour
{
    [SerializeField] private ItemSO inventoryItem;
    private Dictionary<Type, object> _instanceData = new Dictionary<Type, object>();

    private void Awake()
    {
        // 初始化对应类型的实例数据
        if (inventoryItem is ContainerItemSO)
        {
            _instanceData.Add(typeof(ContainerData), new ContainerData());
        }
        // 其他可变数据类型同理,比如可升级物品的UpgradeData
    }

    // 提供获取可变数据的通用方法
    public T GetInstanceData<T>() where T : class
    {
        _instanceData.TryGetValue(typeof(T), out var data);
        return data as T;
    }
}

// 容器实例数据类
public class ContainerData
{
    public float CurrentAmount; // 初始值可在Awake时设置
}

步骤3:用接口统一处理可变数据(可选,更优雅)

如果有多种可变数据类型,用接口定义行为,避免大量类型判断:

// 通用可变数据接口
public interface IInstanceDataHolder
{
    object CreateInstanceData();
}

// 让ContainerItemSO实现接口
public class ContainerItemSO : ItemSO, IDestroyableItem, IInstanceDataHolder
{
    public float maxAmount;

    public object CreateInstanceData()
    {
        return new ContainerData();
    }
}

// 修改Item类自动初始化数据
public class Item : MonoBehaviour
{
    [SerializeField] private ItemSO inventoryItem;
    private Dictionary<Type, object> _instanceData = new Dictionary<Type, object>();

    private void Awake()
    {
        if (inventoryItem is IInstanceDataHolder dataHolder)
        {
            var data = dataHolder.CreateInstanceData();
            _instanceData.Add(data.GetType(), data);
        }
    }

    public T GetInstanceData<T>() where T : class
    {
        _instanceData.TryGetValue(typeof(T), out var data);
        return data as T;
    }
}

步骤4:使用示例(给容器加水)

public void AddWaterToContainer(Item containerItem)
{
    var containerData = containerItem.GetInstanceData<ContainerData>();
    var containerSO = containerItem.GetComponent<Item>().inventoryItem as ContainerItemSO;
    
    if (containerData != null && containerSO != null)
    {
        // 加水不超过最大容量
        containerData.CurrentAmount = Mathf.Min(containerData.CurrentAmount + 10, containerSO.maxAmount);
    }
}

3. 是否需要切换为普通类?

不需要完全抛弃ScriptableObject,它在存储共享模板数据时效率极高(不用重复创建相同配置)。只需要把实例独有的可变数据从ScriptableObject中剥离,放到普通类即可。

如果所有物品数据都是每个实例独有的、没有共享模板的需求,才考虑用普通类;但大部分游戏物品都有共享配置(比如所有水罐的最大容量一致),所以混合使用是最优解:

  • ScriptableObject存模板(不变)
  • 普通类存实例数据(可变)

内容的提问来源于stack exchange,提问作者Vav Avagyan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 21:37:04