Hero image

Как использовать набор времени выполнения на основе ScriptableObject

Эта веб-страница была переведена с помощью машинного перевода для вашего удобства. Мы не можем гарантировать точность или надежность переведенного контента. Если у вас есть вопросы о точности переведенного контента, обращайтесь к официальной английской версии веб-страницы.

Важное замечание перед началом работы

Прежде чем вы погрузитесь в демонстрационный проект ScriptableObject и эту серию мини-руководств, помните, что в своей основе паттерны проектирования - это всего лишь идеи. Они применимы не ко всем ситуациям. Эти приемы помогут вам освоить новые способы работы с Unity и ScriptableObjects.

У каждой модели есть свои плюсы и минусы. Выбирайте только те, которые приносят значительную пользу вашему конкретному проекту. Ваши дизайнеры в значительной степени полагаются на редактор Unity? Шаблон на основе ScriptableObject может стать хорошим выбором, чтобы помочь им сотрудничать с вашими разработчиками.

В конечном счете, лучшая архитектура кода - это та, которая подходит вашему проекту и команде.

Наборы времени выполнения

Объекты ScriptableObjects могут хранить статические данные. Их можно модифицировать и для хранения динамических данных. Это особенно полезно для обращения к коллекции игровых объектов или компонентов во время выполнения.

Подобный "набор времени выполнения" повторяет то, почему некоторые разработчики используют синглтоны - легкий глобальный доступ. К сожалению, синглтоны поставляются с таким багажом, как нежелательные зависимости. Эти дополнительные зависимости могут усложнить тестирование и затруднить отладку.

В качестве альтернативы можно использовать набор времени выполнения на основе ScriptableObject для часто используемых компонентов. Например, используйте их для отслеживания таких элементов игры, как появившиеся враги, коллекционные предметы или контрольные точки.

Будучи активами уровня проекта, ScriptableObjects обеспечивают глобальный доступ к данным для всех объектов в сцене. Эта функция позволяет обмениваться информацией без многих недостатков, связанных с одиночками.

Базовая реализация

Вот один из способов создания набора времени выполнения для GameObjects. Думайте о нем как о контейнере данных, который хранит общедоступную коллекцию элементов, но также включает методы для добавления и удаления членов.

Во время выполнения любой MonoBehaviour может ссылаться на публичное свойство Items, чтобы получить полный список GameObjects. Другой сценарий или компонент может вызвать Add или Remove для изменения списка Items.

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, чтобы настроить или обновить его во время игры.

Если вы хотите, чтобы компонент автоматически включал себя в набор, вы также можете вызвать методы Add или Remove набора во время выполнения из OnEnable и OnDisable поведения MonoBehaviour.

Например, ваш вымышленный класс Enemy может выглядеть так, как показано в следующем фрагменте кода.

Здесь каждый компонент Enemy может добавлять или удалять себя с помощью своих методов OnEnable или OnDisable. Если в Инспекторе установлено поле m_RuntimeSet, компонент Enemy автоматически появится в EnemyRuntimeSetSO. Это особенно удобно, если вы используете компонент Enemy с префабами.

Одно из ограничений этой техники заключается в том, что если вы проинспектируете ScriptableObject во время выполнения, вы не сможете увидеть содержимое списка Items в Инспекторе по умолчанию.

Вместо этого в каждом поле элемента появляется сообщение "Несоответствие типа". По своей природе ScriptableObject не может сериализовать объект сцены. Список действительно работает, но данные отображаются некорректно.

Используйте публичное свойство или атрибут HideInInspector, если хотите избежать путаницы и предотвратить отображение списка в Инспекторе.

Эту проблему также можно решить с помощью пользовательского скрипта редактора и инспектора. Хорошими примерами являются паттерн архитектуры Soap - ScriptableObject в магазине активов Unity или демонстрационный пример паттернов ниже.

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

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

     private void OnDisable()
     {
         m_RuntimeSet.Remove(this);
     }
}
Наборы времени выполнения

Демонстрация узоров

Хотя в PaddleBallSO нет сценария использования наборов времени выполнения, демо-версия Patterns проекта включает небольшой пример, демонстрирующий этот паттерн проектирования в работе.

Нажмите кнопку Spawn Block, чтобы постепенно заполнить стену блоками. Мяч разрушает блок при контакте.

Выберите Select Set, чтобы показать BlockRuntimeSet_Data в инспекторе. Здесь содержится список всех блоков в сцене.

Вы можете непрерывно заполнять сетку блоками так быстро, как их убирает шарик. В то же время набор времени выполнения отслеживает активно изменяющееся количество блоков в списке Items.

Демонстрация Patterns показывает несколько улучшений качества жизни при работе с наборами времени выполнения:

  • Пользовательский инспектор: Поскольку ScriptableObjects не может сериализовать объекты сцены из коробки, скрипт GameObjectRuntimeSetEditor предоставляет пользовательский инспектор, который отменяет стандартную ошибку несоответствия типов. Щелкните любой блок в Инспекторе, чтобы выделить его в Иерархии сцены.
  • Обновление событий: В наборе времени выполнения есть System.Action под названием ItemsChanged. Это уведомляет сценарий редактора при добавлении или удалении членов из списка элементов. Это обновляет инспектор при изменении списка элементов. Канал событий также синхронизирует текущее количество элементов с пользовательским компонентом RuntimeSetCounter. Это показывает общее количество блоков в экранном пользовательском интерфейсе.
Пользовательский инспектор

Как помогают наборы времени выполнения

В следующий раз, когда вам понадобится централизованный список игровых объектов или компонентов, подумайте о наборах времени выполнения. Они могут быть менее затратными, чем операция поиска(Object.FindObjectOfType или GameObject.FindWithTag), и менее проблематичными, чем синглтоны.

Как ScriptableObjects, они отделены от отдельных GameObjects в сцене. Храните столько наборов для выполнения, сколько необходимо для всего, что вы хотите часто отслеживать во время выполнения.

Наборы времени выполнения могут упростить процесс наблюдения за игровыми объектами без лишних зависимостей. Время, сэкономленное на отладке и тестировании, вы можете потратить на разработку новых игровых возможностей для своих игроков.

скриптовое аутро

Другие ресурсы ScriptableObject

Мы надеемся, что наборы времени выполнения могут принести пользу вашим будущим проектам.

Подробнее о паттернах проектирования с помощью ScriptableObjects читайте в нашей технической электронной книге, Создание модульной игровой архитектуры в Unity с помощью ScriptableObjects. Вы также можете узнать больше о распространенных паттернах разработки Unity в Повысьте уровень своего кода с помощью паттернов программирования игр.

Понравился ли вам этот контент?