SO
Script ableObject,继承自UnityEngine.ScriptableObject 的资源,可以像预制体和材质一样,保存在项目的Assets文件夹中,成为一个文件
核心作用:
把“数据”从逻辑代码中抽离出来,编程可以独立的可配置资源
作用
常用于配置游戏数据,技能Buff设计,事件通道
优点
1.内存优化,节省冗余:多个敌人使用同一份数据(比如“哥布林属性”),内存中只存在一份 SO 实例,而不是每个敌人身上挂一个重复脚本和一堆字段。
脱离场景,独立管理:SO 是项目资产,不依赖任何场景。即使场景被删除,数据依然保留,可以跨场景调用,也方便版本控制。
实时热重载与快速迭代:在运行模式下修改 SO 的数值,停止运行后修改仍然保留(这是很多开发者最爱的特性)。可以边运行边调参数,调好后数值不丢失。
引用强类型,不易出错:技能 SO 可以直接引用音效 SO、图标 Sprite 等。拖动引用比写字符串路径更安全。
缺点
SO 不是 GameObject,无法像 MonoBehaviour 那样拥有 Transform、Collider 等组件,所以不能直接在物体上挂载组件。
无法绑定 UI 事件:按钮的 onClick 等事件只能指向场景中挂载了脚本的对象,不能直接指向 SO(需要中间脚本)。
容易产生大量小文件:如果不加节制(比如每一把武器、每一个敌人类型都做一个 SO),项目里会多出成百上千个 .asset 文件,管理起来很麻烦。
数据修改即全局影响:多个对象共享同一个 SO,若在运行时被代码修改了数据,所有使用该 SO 的对象都会受到影响。虽然提供了运行时复制机制(Instantiate),但会增加复杂度。
引用关系需要手动维护:SO 之间的引用如果丢失(移动文件等),需要逐个手动修复,大规模重构时可能较痛苦。
不适合频繁更改的动态数据:例如角色实时血量、当前位置等运行时状态,用普通变量就好,用 SO 反而不合适。
当SO充当事件通道
这就是事件通道:一个 ScriptableObject 作为管道,发者只管发,收者只管收,双方完全解耦。
SO文件充当的是中间的人的身份
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // GameEventSO.cs
using UnityEngine;
using UnityEngine.Events;
[CreateAssetMenu(menuName = "Events/Game Event")]
public class GameEventSO : ScriptableObject
{
// 可以携带参数的事件(传一个 GameObject 表示死者)
[System.Serializable]
public class UnityGameObjectEvent : UnityEvent<GameObject> { }
public UnityGameObjectEvent OnRaise;
public void Raise(GameObject sender)
{
OnRaise?.Invoke(sender);
}
}
|
玩家脚本在需要触发时,把自身传出去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // PlayerHealth.cs
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
public GameEventSO playerDiedEvent; // 引用 .asset
public int health = 100;
public void TakeDamage(int damage)
{
health -= damage;
if (health <= 0)
{
Die();
}
}
private void Die()
{
// 触发事件,把自身传出去
playerDiedEvent.Raise(gameObject);
// 可以在此处播放死亡动画等自身逻辑
}
}
|
监听事件
可以传入多个系统,进行订阅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // AudioManager.cs
using UnityEngine;
public class AudioManager : MonoBehaviour
{
public GameEventSO playerDiedEvent;
private void OnEnable()
{
playerDiedEvent.OnRaise.AddListener(OnPlayerDied);
}
private void OnDisable()
{
playerDiedEvent.OnRaise.RemoveListener(OnPlayerDied);
}
private void OnPlayerDied(GameObject player)
{
Debug.Log("播放死亡音效");
// AudioSource.PlayClipAtPoint(...);
}
}
|
SO事件相对于传统的事件系统,不需要手动的取消订阅事件,直接写在OnEnable()和 OnDisable()方法中能够在挂载物体销毁时,引用转为空