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

Unity如何为不同类型NPC的ScriptableObject逻辑存储通用运行时数据

优化方案推荐

方案1:类型安全黑板模式(最灵活,优先推荐)

直接替换原有的多数组结构,用封装好的黑板容器统一存储所有自定义运行时数据,无需提前声明数组、配置长度,新增数据不需要修改Npc基类代码,完全兼容现有ScriptableObject状态逻辑。

public class NpcBlackboard
{
    private readonly Dictionary<string, object> _data = new Dictionary<string, object>();

    // 泛型方法保证类型安全
    public T Get<T>(string key)
    {
        if (_data.TryGetValue(key, out var value) && value is T typedValue)
        {
            return typedValue;
        }
        return default;
    }

    public void Set<T>(string key, T value)
    {
        _data[key] = value;
    }

    public bool TryGet<T>(string key, out T value)
    {
        if (_data.TryGetValue(key, out var obj) && obj is T typedObj)
        {
            value = typedObj;
            return true;
        }
        value = default;
        return false;
    }
}

在Npc类中添加黑板实例即可:

public class Npc : MonoBehaviour
{
    // 原有公共属性保留,删除所有indexedXXX数组、计数变量
    public NpcBlackboard Blackboard { get; private set; }

    public void Awake()
    {
        Blackboard = new NpcBlackboard();
        // 原有逻辑保留
    }
}

状态脚本中存取数据示例:

// 存数据
npc.Blackboard.Set<float>("targetHeight", 8f);
npc.Blackboard.Set<IEnumerator>("jumpRoutine", currentRoutine);
// 取数据
float height = npc.Blackboard.Get<float>("targetHeight");

方案2:无装箱高性能黑板(适合百级以上NPC并发场景)

如果对性能要求高,不想承担泛型装箱的开销,可以用按类型拆分的字典实现,性能与原有数组方案接近,灵活性远高于原有方案。

public class NpcBlackboard
{
    private readonly Dictionary<string, float> _floatData = new();
    private readonly Dictionary<string, bool> _boolData = new();
    private readonly Dictionary<string, Vector3> _vector3Data = new();
    private readonly Dictionary<string, IEnumerator> _coroutineData = new();

    public float GetFloat(string key) => _floatData.TryGetValue(key, out var v) ? v : default;
    public void SetFloat(string key, float value) => _floatData[key] = value;
    public bool GetBool(string key) => _boolData.TryGetValue(key, out var v) ? v : default;
    public void SetBool(string key, bool value) => _boolData[key] = value;
    public Vector3 GetVector3(string key) => _vector3Data.TryGetValue(key, out var v) ? v : default;
    public void SetVector3(string key, Vector3 value) => _vector3Data[key] = value;
    public IEnumerator GetCoroutine(string key) => _coroutineData.TryGetValue(key, out var v) ? v : default;
    public void SetCoroutine(string key, IEnumerator value) => _coroutineData[key] = value;
}

方案3:子类继承(适合NPC类型少、专属属性固定场景)

如果你的NPC类型数量不多,且每个类型的专属属性是固定的,可以用继承实现,性能最优,完全没有额外查找开销:

// 基础Npc类只保留所有NPC共有的属性
public class Npc : MonoBehaviour
{
    public int npcId;
    public NpcType type;
    public float maxHealth;
    // 其他公共属性、方法保留
}

// 不同类型NPC单独做子类实现,存储专属属性
public class FlyingNpc : Npc
{
    public float targetHeight;
    public Vector3 flyDirection;
    public Coroutine currentFlyRoutine;
}

public class MeleeNpc : Npc
{
    public float lastAttackTime;
    public Transform currentTarget;
}

状态脚本中使用时直接做类型转换即可:

public override void UpdateState(Npc npc)
{
    if (npc is FlyingNpc flyingNpc)
    {
        flyingNpc.targetHeight = 10f;
    }
}

配套优化建议

  • 所有数据存取的Key可以定义为常量字符串,避免手写字符串出错,同时IDE支持自动补全
  • 若担心字符串Key的查找性能,可以改用int类型的Key,提前为每个需要存储的字段分配唯一ID,查找效率更高

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 05:45:06