ScriptableObject
ScriptableObject 是 Unity 独立的数据容器,不挂在 GameObject 上,保存为 Asset,可被多个对象共享引用而不复制数据。适合游戏配置表、武器数值、对话与事件频道,帮助程序与策划把数据与场景实例解耦,减少 Prefab 膨胀,并提升长期可维护性与运行时热调与协作效率。
想象一下...
ScriptableObject 就像一份餐厅菜单。菜单(SO)保存菜品信息——名称、价格、描述。每张桌子(GameObject)都引用同一份菜单;没人私藏复印件。在主菜单上改价格 → 每张桌子立刻看到。菜单不关心谁在读——它只存数据。
概念详解
ScriptableObject 继承
UnityEngine.Object(不是 MonoBehaviour)。用
[CreateAssetMenu] 创建 → 会出现在 Assets → Create 下。文件以
.asset 形式存在 Assets 中。
为何不用 static 或 [SerializeField]?Static 不会序列化,
也不方便在 Inspector 中编辑。MonoBehaviour 上的 [SerializeField]
会按实例复制数据 → 更占内存、难同步。SO:一份源头,多处引用。
用途:道具/敌人数值(WeaponData、EnemyStats)、 游戏配置(AudioSettings、InputConfig)、事件通道(松耦合)、状态容器 (GameStateSO)。SO 不跑 Update/Awake,但会收到 OnEnable/OnDisable。
Editor 与 Runtime:编辑模式下的修改会持久化。Play Mode 中的修改在 Editor 里也会持久化(与 MonoBehaviour 字段不同)。在玩家构建中,游戏重启时 SO 值会从 asset 重置。
SO 与 MonoBehaviour 数据对比
❌ [SerializeField] on MonoBehaviour
Enemy_01 (MonoBehaviour)
hp: 100, speed: 3
← 各自的副本
Enemy_02 (MonoBehaviour)
hp: 100, speed: 3
← 各自的副本
Enemy_03 (MonoBehaviour)
hp: 100, speed: 3
← 各自的副本
3 份副本 × N 字节 = 浪费内存
✅ ScriptableObject
GoblinData.asset (SO)
hp: 100, speed: 3
Enemy_01
→ ref SO
Enemy_02
→ ref SO
Enemy_03
→ ref SO
仅一份 — 改 SO 源 → 全部更新
动手步骤
创建 ScriptableObject 类
添加 [CreateAssetMenu(menuName="Game/ItemData")],使其出现在 Assets → Create 下。
在 Project 中创建 Asset
右键 → Assets → Create → Game → ItemData。按数据命名(例如 Sword_Data.asset)。
在 Inspector 中填写字段
选中 .asset → Inspector 显示字段 → 填入数值、精灵、音效…
从 MonoBehaviour 引用
[SerializeField] private ItemData _data; → 把 asset 拖进 Inspector。
交互模拟器
选择一个 ScriptableObject asset,编辑字段,观察所有消费者立刻更新。
Inspector (ScriptableObject)
使用此 SO 的对象
📌 Shared Reference
上方对象都引用同一资源。改一次 → 全部更新。
代码示例
基础创建 WeaponData ScriptableObject,并从 MonoBehaviour 使用它。
using UnityEngine;
// Menu: Assets → Create → Game/Data → Weapon Data
[CreateAssetMenu(menuName = "Game/Data/Weapon Data", fileName = "New Weapon")]
public class WeaponData : ScriptableObject
{
public string weaponName;
public int damage;
public float attackSpeed;
public Sprite icon;
public AudioClip swingSound;
}
public class PlayerWeapon : MonoBehaviour
{
// Drag the .asset from Project onto this field
[SerializeField] private WeaponData currentWeapon;
public void Attack()
{
Debug.Log($"Attacking with {currentWeapon.weaponName}: {currentWeapon.damage} dmg");
AudioSource.PlayClipAtPoint(currentWeapon.swingSound, transform.position);
}
public void EquipWeapon(WeaponData newWeapon)
{
currentWeapon = newWeapon; // instant swap — only the reference changes
}
}代码示例
进阶把 SO 当作 Event Channel——解耦广播者与监听者。
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
// Event Channel — Project asset, independent of scene hierarchy
[CreateAssetMenu(menuName = "Events/Void Event")]
public class VoidEventSO : ScriptableObject
{
private List<UnityAction> _listeners = new();
public void Raise()
{
for (int i = _listeners.Count - 1; i >= 0; i--)
_listeners[i]?.Invoke();
}
public void Subscribe(UnityAction listener) => _listeners.Add(listener);
public void Unsubscribe(UnityAction listener) => _listeners.Remove(listener);
}
// Broadcaster: does not know who is listening
public class PlayerDeath : MonoBehaviour
{
[SerializeField] private VoidEventSO onPlayerDied;
public void Die() => onPlayerDied.Raise();
}
// Listener: does not know who raised the event
public class GameOverUI : MonoBehaviour
{
[SerializeField] private VoidEventSO onPlayerDied;
void OnEnable() => onPlayerDied.Subscribe(ShowGameOver);
void OnDisable() => onPlayerDied.Unsubscribe(ShowGameOver);
void ShowGameOver() => gameObject.SetActive(true);
}📌 快速记忆
- ▸SO = 不绑定场景的数据容器
- ▸多对象引用同一 SO —— 不复制数据
- ▸Play Mode 中修改 SO 在 Editor 里会持久化
- ▸SO Event Channel = 无需场景引用的解耦 pub/sub
⚠️ 常见错误
❌ 在正式构建的运行时修改共享 SO
共享 SO —— 改动会影响到使用它的每一个对象
✅ 需要按实例数据时克隆:Instantiate(soAsset)
❌ 忘记写 [CreateAssetMenu]
无法从 Editor 菜单创建 .asset
✅ 加上该特性,或用 ScriptableObject.CreateInstance<T>()