返回技能市场
开发运维 安全

unity-scriptableobjects

@admin/unity-scriptableobjects

用 Unity ScriptableObject 组织数据资产、共享变量、事件通道和运行时集合,构建解耦系统。

admin 热度 270v0.0.1

Unity ScriptableObject 架构

在 Unity 6.3 LTS 中,使用 ScriptableObject 资产来存储共享数据并解耦系统—— 配置、事件通道和注册表以项目资产的形式存在,而不是硬编码到场景或单例中。目标为 Unity 6.3 LTS (6000.3)

何时使用

  • 当你需要可由设计师编辑的配置(武器属性、关卡数据)、在不相关的系统之间共享一个值、通过事件通道将发送方与监听方解耦,或者构建一个活动对象的运行时注册表时使用——而无需 static/单例管理器。
  • 当项目中有由 : ScriptableObject 类支持的 *.asset 数据文件时使用。

何时不使用: 每个 GameObject 不同的实例运行时状态(那应放在 MonoBehaviour 上)——ScriptableObject 资产会被所有引用它的对象共享。将玩家进度保存到磁盘 → save-systems。永远不需要成为资产的普通 DTO 可以只是 [System.Serializable] 类。

核心工作流

  1. 定义类,派生自 ScriptableObject,并用 [CreateAssetMenu] 标记,以便设计师可以从 Assets 菜单创建实例。
  2. 创建一个或多个 .asset 实例,在 Project 窗口中;每个实例都是被 [SerializeField] 字段引用的共享、具名数据。
  3. 引用而不是复制。 MonoBehaviour 持有对资产的引用;它们都看到相同数据,因此更改资产会更改每个消费者。
  4. 对于解耦,将 _信号_ 和 _共享变量_ 建模为 ScriptableObject:一个 HUD 读取而玩家写入的 "FloatVariable";一个玩家触发而许多系统监听的“事件通道”。双方都不引用对方。
  5. OnEnable 中重置运行时修改,如果资产在播放期间被修改,因为在 Editor 运行时对资产所做的编辑会持久化在资产上(这是“我玩过一次后值变了”的常见来源)。
  6. 验证:在 Play 模式下检查资产值,并确认消费者会响应。

模式

1. 配置/数据资产

using UnityEngine;

[CreateAssetMenu(fileName = "WeaponData", menuName = "Game/Weapon Data", order = 0)]
public class WeaponData : ScriptableObject
{
    public string displayName = "Pistol";
    public int    damage = 10;
    public float  fireRate = 0.25f;
    public GameObject projectilePrefab;
}
public class Weapon : MonoBehaviour
{
    [SerializeField] private WeaponData data;   // assign the shared asset in the Inspector
    private void Fire() => Debug.Log($"{data.displayName} for {data.damage}");
}

2. 共享运行时变量(将生产者与消费者解耦)

[CreateAssetMenu(menuName = "Game/Float Variable")]
public class FloatVariable : ScriptableObject
{
    [SerializeField] private float initialValue;
    [System.NonSerialized] public float runtimeValue;   // not saved to the asset

    private void OnEnable() => runtimeValue = initialValue;  // reset each play session
}
// Player writes playerHealth.runtimeValue; the HUD reads it — neither references the other.

3. 在运行时创建实例(不是磁盘上的资产)

// For transient SO data you build in code (e.g. a generated config).
var temp = ScriptableObject.CreateInstance<WeaponData>();
temp.damage = 25;
// ...use temp...  Destroy(temp);   // clean up runtime-created instances

陷阱

  • 在 Editor 运行时编辑 SO 会持久化 — 你在 Play 期间更改的值在你停止后仍会留在资产上。将可变运行时状态保留在 [NonSerialized] 字段中,并在 OnEnable 中重置,否则它会给你造成意外。(在 _构建版本_ 中,资产编辑不会跨启动持久化。)
  • 禁用 Domain Reload 会跳过你的 OnEnable 重置 — 当启用 Enter Play Mode Options 并关闭 Reload Domain(Unity 6.3 LTS 的快速迭代设置)时,已加载的 SO 在你按 Play 时 _不会_ 重新创建,因此 OnEnable 不会触发,runtimeValue 会保留上一次会话的值。请改用 ISerializationCallbackReceiver 或场景加载钩子显式重置,而不是仅依赖 OnEnable
  • 期望每个对象拥有独立状态 — 每个引用都指向 _同一个_ 资产。如果两个敌人需要不同的当前 HP,请将 HP 存储在 MonoBehaviour 上,而不是共享的 SO 上。
  • 没有帧生命周期 — ScriptableObject 有 OnEnable/OnDisable/OnDestroy,但没有 Update。不要期待每帧回调。
  • 将 SO 用作存档文件 — 它们是作者资产,不是运行时持久化;请使用 save-systems 写入进度。
  • 泄漏 CreateInstance 对象 — 运行时创建的实例不会像普通 C# 对象那样被垃圾回收;完成时请 Destroy 它们。

参考

  • 有关 事件通道 模式(一个 GameEvent SO + 监听器、类型安全的载荷)以及 运行时集合/注册表(共享的活动敌人列表),请阅读 references/event-channels.md
  • 主要文档:Unity Manual “ScriptableObject”(/Manual/class-ScriptableObject.html)以及 ScriptReference/ScriptableObjectScriptReference/CreateAssetMenuAttribute

相关技能

  • unity-csharp-scripting — 使用这些资产的 MonoBehaviour。
  • save-systems — 将状态持久化到磁盘(SO _不是_ 用于此目的)。
  • card-game / rpg / survival-crafting — 依赖 SO 驱动数据的类型。
qianwen skills install @admin/unity-scriptableobjects