Паттерн расширяемых перечислений
Паттерн расширяемых перечислений
При разработке игр часто приходится решать повторяющиеся или похожие задачи. К счастью, можно опереться на коллективный опыт инженеров-программистов, которые уже сталкивались с такими задачами и воплотили свои знания в паттернах проектирования. Паттерны проектирования предлагают универсальные решения для создания крупных масштабируемых приложений. Они повышают читаемость кода, делают кодовую базу чище и сокращают объем рефакторинга и время тестирования. Паттерн проектирования можно рассматривать как шаблон для решения распространенных задач, например: - эффективное хранение больших объемов данных;
- обмен данными между объектами из разных игровых систем;
- динамическая замена поведения во время выполнения. ScriptableObject помогает реализовать некоторые из этих паттернов. Вы уже видели, как он работает в роли контейнера данных, но его возможности не ограничиваются хранением значений и настроек. В следующих разделах рассматриваются более сложные варианты применения ScriptableObject.Категории, подобные enum Чтобы приносить пользу, ScriptableObject вовсе не обязан содержать данные. Даже пустой ScriptableObject можно применять, например, для сравнения с другими объектами ScriptableObject.
Предположим, в игре создается несколько ассетов на основе пустого ScriptableObject GameItemSO:
using UnityEngine;[CreateAssetMenu(fileName="GameItem")] public class GameItemSO : ScriptableObject {Пустые объекты ScriptableObject работают как значения enum.
Так можно создать в проекте любое количество ассетов. Даже если ScriptableObject не содержит данных, сам объект способен обозначать категорию или тип предмета подобно значению enum. Если две переменные ссылаются на один ScriptableObject, они обозначают один и тот же тип предмета. Если ссылки различаются, различаются и типы. Например, ScriptableObject может обозначать особый эффект урона, такой как холод, жар, электричество или магия, либо один из вариантов для игры с нулевой суммой «камень, ножницы, бумага».
Камень
Камень
Камень
Бумага
Ножницы
Сравнение объектов ScriptableObject
Если приложению нужна система инвентаря для экипировки игровых предметов, объекты ScriptableObject могут представлять типы предметов или слоты оружия. Поля в окне Inspector при этом образуют интерфейс настройки с перетаскиванием элементов.
Ячейки инвентаря распределены по категориям ScriptableObject
Категории на основе ScriptableObject можно назначать перетаскиванием
Такой удобный для художников интерфейс позволяет дизайнерам изменять и расширять игровые данные без дополнительной помощи разработчика. Если команда дизайнеров получает инструменты и ответственность за поддержку этих данных, каждый участник проекта может сосредоточиться на своей специализации.
Расширение поведения Применение ScriptableObject в роли enum становится еще интереснее, если расширить объект дополнительными данными. В отличие от обычного enum, ScriptableObject может содержать дополнительные поля и методы. Ниже показана адаптированная версия GameItem для игры «камень, ножницы, бумага». Ассет ScriptableObject по-прежнему задает категорию, подобную enum, но теперь он уже не пуст.
public class GameItem : ScriptableObject {
public GameItem weakness;
public bool IsWinner(GameItem other) { return other.weakness == this; }
}Теперь ScriptableObject содержит поле weakness, которое определяет, какой другой предмет победит при взаимодействии. Помимо данных, каждый ScriptableObject хранит простую логику сравнения в методе IsWinner. Каждому игровому предмету требуется MonoBehaviour со ссылкой на определенный ассет ScriptableObject. В этом примере роль контроллера выполняет следующий скрипт:
public class GameItemController : MonoBehaviour {
// камень, ножницы, бумага
public GameItem gameItem;private void OnTriggerEnter(Collider other) {
GameItemController otherController = other.GetComponent<GameItemController>();
GameItem otherGameItem = otherController.gameItem;
if (gameItem.IsWinner(otherGameItem)) {
Debug.Log(gameItem.name + " побеждает " + otherGameItem.name);}
}
}Скрипт хранит ссылку на ScriptableObject в поле. В OnTriggerEnter метод IsWinner позволяет определить победителя, когда gameItem соприкасается с другим предметом. Так создается основа для столкновения в духе игры «камень, ножницы, бумага». В отличие от enum, ScriptableObject легко расширять. Не нужно создавать отдельную таблицу поиска или связывать перечисление с новым массивом данных. Достаточно добавить поле и (или) метод с необходимой логикой.
Камень
Ножницы
Объекты ScriptableObject с логикой сравнения. Источник: Flaticon
Сравните это с поддержкой традиционного enum. Если длинный список его значений не имеет явной нумерации, вставка или удаление элемента может изменить порядок остальных. Такая перенумерация способна вызвать трудноуловимые ошибки или непредвиденное поведение. У перечислений на основе ScriptableObject этой проблемы нет. Добавляйте в проект новые ассеты или удаляйте существующие - остальные элементы продолжат работать. Предположим, предмет нужно сделать экипируемым в RPG. Для этого в ScriptableObject достаточно добавить логическое поле. Нужно запретить некоторым персонажам брать определенные предметы? Добавить магические свойства или особые способности? Перечисления на основе ScriptableObject поддерживают и такие сценарии.
Игровые данные могут развиваться по мере реализации дизайна игры. Команде потребуется согласовать первоначальную структуру полей, но затем дизайнеры смогут самостоятельно заполнять детали.