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
相关产品推荐
相关产品推荐

