Hero image

如何使用基于 ScriptableObject 的运行时集

为方便起见,此网页已进行机器翻译。我们无法保证翻译内容的准确性或可靠性。如果您对翻译内容的准确性有疑问,请参阅此网页的官方英文版本。

开始前的重要说明

在深入研究 ScriptableObject 演示项目和本系列迷你指南之前,请记住,设计模式本质上只是想法而已。它们并不适用于所有情况。这些技术可以帮助您学习使用 Unity 和 ScriptableObjects 的新方法。

每种模式都有优点和缺点。仅选择那些对您的特定项目有意义的内容。您的设计师是否严重依赖 Unity 编辑器?基于 ScriptableObject 的模式可能是帮助他们与您的开发人员合作的一个不错的选择。

最终,最好的代码架构是适合您的项目和团队的架构。

运行时集

ScriptableObjects 可以 存储静态数据。它们也可以被修改来保存动态数据。这对于在运行时引用游戏对象或组件的集合特别有用。

像这样的“运行时集”复制了一些开发人员使用单例的原因——轻松的全局访问。不幸的是,单例会带来一些负担,比如不良依赖性。这些额外的依赖关系可能会使测试变得复杂,并使调试更加困难。

作为替代方案,请考虑对经常引用的组件使用基于 ScriptableObject 的运行时集。例如,使用它们来追踪游戏元素,如生成的敌人、收藏品或检查点。

作为项目级资产,ScriptableObjects 为场景内的所有对象提供全局数据访问。此功能允许信息共享,而没有单例带来的许多缺点。

基本实现

这是为 GameObjects 设置运行时的一种方法。可以将其视为一个数据容器,它保存元素的公共集合 - 但也包含添加和删除成员的方法。

在运行时,任何 MonoBehaviour 都可以引用公共 Items 属性 来获取 GameObjects 的完整列表。另一个脚本或组件可以调用 添加删除 来修改 项目 列表。

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "GameObject Runtime Set", fileName
= "GORuntimeSet")]
public class GameObjectRuntimeSetSO: ScriptableObject
{

   private List<GameObject> items = new List<GameObject>();
   public List<GameObject> Items => items;

   public void Add(GameObject thingToAdd)
   {
       if (!items.Contains(thingToAdd))
            items.Add(thingToAdd);
   }

   public void Remove(GameObject thingToRemove)
   {
       if (items.Contains(thingToRemove))
            items.Remove(thingToRemove);
   } 
}

通用版本

您可以使运行时设置更加具体,以便它仅跟踪特定类型的 MonoBehaviour。在这种情况下,为每种类型创建特定的集合(例如,EnemyRuntimeSet、PickupRuntimeSet 等)。

通用抽象类可以简化这一过程。然后您可以从这个基类派生其他运行时集。例如,如果您想为自定义 Enemy 组件创建运行时集,则可以为通用 RuntimeSetSO<T> 和具体 EnemyRuntimeSetSO 定义类,如下面的代码片段所示。

请注意,具体类 EnemyRuntimeSetSO 没有任何实现细节,只有 CreateAssetMenu 属性。

您想要跟踪的每个新组件只需要一个相应的运行时设置来管理它。根据需要构建任意数量的具体类。敌人、NPC、库存物品、任务等都可以拥有自己的运行时集。

public abstract class RuntimeSetSO<T>: ScriptableObject
{
   public List<T> Items = new List<T>();

   public void Add(T thing)
   {
       if (!Items.Contains(thing))
            Items.Add(thing);
   }

   public void Remove(T thing)
   {
       if (Items.Contains(thing))
            Items.Remove(thing);
   } 
}

[CreateAssetMenu(menuName = "Enemy Runtime Set", fileName =
"EnemyRuntimeSet")]

public class EnemyRuntimeSetSO: RuntimeSet<Enemy>
{

}
不匹配错误

使用和限制

管理运行时集由您决定。通常,您可以使用第二个 MonoBehaviour 在游戏过程中进行设置或更新它。

如果您希望组件自动将其自身包含在集合中,您还可以从 MonoBehaviour 的 OnEnableOnDisable调用运行时集合的 AddRemove 方法。

例如,您虚构的 敌人 类可能看起来像下面的代码片段。

这里,每个 敌人 组件都可以使用其 OnEnableOnDisable 方法添加或删除自身。如果在 Inspector 中设置了 m_RuntimeSet 字段,则 Enemy 组件将自动出现在 EnemyRuntimeSetSO 中。如果您将 敌人 组件与预制件一起使用,这将特别方便。

此技术的一个限制是,如果您在运行时检查 ScriptableObject,则默认情况下您将无法看到 Inspector 中 Items 列表的内容。

相反,每个元素字段都会出现“类型不匹配”。根据设计,ScriptableObject 无法序列化场景对象。该列表实际上是有效的,但数据无法正确显示。

如果您想避免混淆并阻止列表显示在 Inspector 中,请使用公共属性或 HideInInspector 属性。

您还可以使用自定义编辑器脚本和检查器解决此问题。好的例子包括 Unity Asset Store 中的 Soap – ScriptableObject 架构模式 或下面的模式演示示例。

public class Enemy: MonoBehaviour
{
     [SerializeField] private EnemyRuntimeSetSO m_RuntimeSet;

     private void OnEnable()
     {
         m_RuntimeSet.Add(this);
     }

     private void OnDisable()
     {
         m_RuntimeSet.Remove(this);
     }
}
运行时集

Patterns demo

虽然 PaddleBallSO 没有提供运行时集的用例,但该项目的模式演示包含一个小示例,展示了该设计模式的实际工作。

单击 “生成方块” 按钮可以逐步填充方块墙。球一接触就摧毁了障碍物。

选择 “选择集” 以在检查器中显示 BlockRuntimeSet_Data。这包含场景中所有块的列表。

您可以像球移除方块一样快速地将方块填充到网格中。同时,运行时设置会跟踪项目列表中主动变化的块数。

模式演示展示了使用运行时集时的一些生活质量增强:

  • 定制检查员:由于 ScriptableObjects 无法开箱即用地序列化场景对象,因此 GameObjectRuntimeSetEditor 脚本提供了一个自定义 Inspector 来覆盖默认的类型不匹配错误。单击检查器中的任意块以在场景层次结构中突出显示它。
  • 更新事件:运行时集有一个名为 ItemsChanged的 System.Action。当从项目列表中添加或删除成员时,这会通知编辑器脚本。当项目列表发生变化时,这将刷新检查器。事件通道还将当前的 Items 数量同步到自定义的 RuntimeSetCounter 组件。这会在屏幕 UI 中显示总块数。
定制检验员

运行时集如何提供帮助

下次您需要一个集中的游戏对象或组件列表时,请考虑运行时集。它们比查找操作(Object.FindObjectOfTypeGameObject.FindWithTag)成本更低,问题也比单例更少。

作为 ScriptableObjects,它们与场景中的各个 GameObject 分离。对于您想要在运行时频繁跟踪的任何内容,请根据需要保留尽可能多的运行时集。

运行时集可以简化您监控游戏对象的方式,而不会产生不必要的依赖。您可以利用调试和测试期间节省的时间来为玩家开发新的游戏体验。

可编写脚本的结局

更多 ScriptableObject 资源

我们希望运行时集能够使您的即将开展的项目受益。

在我们的技术电子书《 使用 ScriptableObjects 在 Unity 中创建模块化游戏架构》中了解有关使用 ScriptableObjects 的设计模式的更多信息。您还可以在 使用游戏编程模式提升您的代码水平中了解有关常见 Unity 开发设计模式的更多信息。

您喜欢本文吗?

是的!

还行。