Unity 6.3
0 онлайн 38 гостей 3 в системе
Вход
Модульная игровая архитектура на ScriptableObjects Глава 9 из 12 Оригинал, стр. 64

Паттерн Runtime Set

Паттерн Runtime Set

Во время выполнения часто требуется отслеживать список объектов GameObject или компонентов в сцене. Например, может понадобиться список противников или NPC. Экземпляр ScriptableObject существует на уровне проекта, поэтому может хранить данные, доступные любому объекту из любой сцены. Это обеспечивает почти такой же удобный глобальный доступ, как синглтон, но без известных недостатков этого паттерна. Чтение данных напрямую из ScriptableObject также эффективнее, чем поиск в иерархии сцены через Object.FindObjectOfType или GameObject.FindWithTag. В зависимости от задачи и размера иерархии эти методы сравнительно затратны и не подходят для вызова в каждом кадре.

Базовый Runtime Set Вместо этого можно хранить данные в ScriptableObject как Runtime Set. Это специализированный контейнер данных с общедоступной коллекцией элементов и базовыми методами добавления в коллекцию и удаления из нее.

Хранит коллекцию объектов или компонентов

Предоставляет коллекцию любой сцене

Runtime Set предоставляет глобальный доступ к коллекции данных.

Ниже приведен базовый Runtime Set, который отслеживает список объектов GameObject:

C#
using System.Collections.Generic;
using UnityEngine;
C#
[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);
    }
C#
public void Remove(GameObject thingToRemove) {
    if (items.Contains(thingToRemove)) items.Remove(thingToRemove);
}
}

Во время выполнения любой MonoBehaviour может обратиться к общедоступному свойству Items и получить полный список. За добавление объектов GameObject в этот список и удаление из него должен отвечать другой скрипт или компонент.

Проект

Иерархия

Ссылки GameObject добавляются в Runtime Set через события

Runtime Set для GameObject

Добавьте ссылку на Runtime Set в MonoBehaviour. Затем в функциях событий OnEnable и OnDisable добавляйте объект в список Items этого Runtime Set и удаляйте его оттуда. В качестве альтернативы используйте канал событий, передающий GameObject в качестве полезной нагрузки, например GameObjectEventChannel.

Обобщенная версия Иногда Runtime Set требуется для конкретного типа MonoBehaviour. Например, так можно хранить доступный всем объектам сцены список противников или подбираемых предметов. В этом случае для каждого типа можно создать отдельный Runtime Set, например EnemyRuntimeSet или PickupRuntimeSet.

Упростить создание дополнительных Runtime Set можно с помощью обобщенного абстрактного класса: public abstract class RuntimeSetSO<T> : ScriptableObject { [HideInInspector]

C#
public List<T> Items = new List<T>();
C#
public void Add(T thing) {
    if (!Items.Contains(thing)) Items.Add(thing);
}
C#
public void Remove(T thing) {
    if (Items.Contains(thing)) Items.Remove(thing);
}
}

Он работает аналогично исходному GameObjectRuntimeSet, но дает больше гибкости. Чтобы создать Runtime Set для пользовательского компонента Foo, определите конкретный FooRuntimeSetSO следующим образом:

C#
[CreateAssetMenu(menuName = "Foo Runtime Set", fileName = "FooRuntimeSet")] public class FooRuntimeSetSO : RuntimeSetSO<Foo> { }

Создайте столько конкретных классов, сколько требуется игровой логике. Противники, NPC, предметы инвентаря, задания и другие сущности могут иметь собственные Runtime Set. Для этого достаточно объявить новый пустой класс с нужным типом. Вместо событий каждый компонент Foo может самостоятельно добавлять и удалять себя в методах OnEnable и OnDisable. Если назначить ссылку в поле FooRuntimeSet окна Inspector, компонент Foo будет автоматически появляться в Runtime Set. Это особенно удобно при использовании компонента Foo с префабами.

C#
public class Foo : MonoBehaviour {
    public FooRuntimeSetSO RuntimeSet;
    private void OnEnable() { RuntimeSet.Add(this); }
    private void OnDisable() { RuntimeSet.Remove(this); }
}

Примечание. У этого приема есть ограничение: если просматривать ScriptableObject во время выполнения, содержимое списка Runtime Set не будет видно в окне Inspector. Если сделать список общедоступным и вывести его в окно Inspector, результат будет таким:

Runtime Set не отображает объекты и компоненты сцены в окне Inspector.

По умолчанию в поле каждого элемента отображается Type mismatch («Несоответствие типов»), поскольку ассет ScriptableObject не может сериализовать ссылку на объект сцены. Список при этом работает нормально, но его содержимое отображается некорректно. Чтобы не вводить пользователя в заблуждение и скрыть список из окна Inspector, предоставьте к нему доступ через общедоступное свойство или примените атрибут HideInInspector. Проблему отображения также можно решить с помощью скрипта пользовательского Editor, который настраивает отображение в окне Inspector. Хороший пример приведен в SOAP (ScriptableObject Architectural Pattern) в Asset Store.

Пользовательский Editor в SOAP отображает содержимое Runtime Set.

Интересные факты о foo и bar В программировании foo и bar часто используют как условные имена. Вероятно, их выбрали потому, что они короткие, легко запоминаются и необычно звучат. Точное происхождение этих терминов неизвестно. По одной из версий, их начали использовать операторы радиолокационных станций во время Второй мировой войны. Бессмысленное слово foo также стало крылатой фразой в комиксе 1930-х годов. В программировании распространение foo и bar обычно связывают с клубом железнодорожного моделизма Tech Model Railroad Club при MIT примерно в 1960-х годах. В помещении клуба у двери находились две универсальные кнопки с надписями foo и bar. Хакеры MIT часто заимствовали эти названия для своих идей, и со временем foo и bar стали общими именами переменных. Сегодня их использование в качестве условных имен широко распространено среди программистов.