Hero background image
Last updated January 2020, 10 min. read

Три способа построить игру на основе объектов Scriptable Object

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

Что вы получите с этой страницы: Советы о том, как сделать код вашей игры легким для изменения и отладки, архитектурируя его с помощью Scriptable Objects.

Эти советы исходят от Райана Хиппла, главного инженера в Schell Games, который имеет большой опыт использования Scriptable Objects для архитектуры игр. Вы можете посмотреть выступление Райана на Unite о Scriptable Objects здесь; мы также рекомендуем вам посмотреть сессию инженера Unity Ричарда Файна для отличного введения в Scriptable Objects.

Что такое ScriptableObject?

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

Три кита игровой архитектуры

Используйте модульный дизайн:

  • Избегайте создания систем, которые напрямую зависят друг от друга. Например, система инвентаря должна иметь возможность общаться с другими системами в вашей игре, но вы не хотите создавать жесткую ссылку между ними, потому что это затрудняет повторную сборку систем в разные конфигурации и отношения.
  • Создавайте сцены как чистые листы: избегайте наличия временных данных между вашими сценами. Загрузка каждой новой сцены должна происходить заново. Это позволяет вам иметь сцены с уникальным поведением, которое отсутствует в других сценах, не прибегая к хакам.
  • Настройте Prefabs так, чтобы они работали самостоятельно. Каждый добавленный в сцену префаб должен по возможности иметь как можно более законченную функциональность. Это помогает контролировать кодовую базу в большой команде, где сцены представляют собой список префабов, а префабы самостоятельны в своих функциях. Таким образом, большинство ваших проверок находятся на уровне prefab, что приводит к меньшему количеству конфликтов в сцене.
  • Сосредоточьтесь на решении одной проблемы в каждом компоненте. Это упрощает сборку нескольких компонентов вместе для создания чего-то нового.

Упростите изменение и редактирование частей:

  • Сделайте как можно больше вашей игры управляемой данными. Когда вы проектируете свои игровые системы как машины, которые обрабатывают данные в виде инструкций, вы можете эффективно вносить изменения в игру, даже когда она запущена.
  • Если ваши системы настроены так, чтобы быть максимально модульными и основанными на компонентах, это упрощает их редактирование, включая работу ваших художников и дизайнеров. Если дизайнеры могут собирать вещи в игре, не спрашивая о конкретной функции – в значительной степени благодаря внедрению крошечных компонентов, каждый из которых выполняет только одну задачу – они могут комбинировать такие компоненты различными способами, чтобы найти новый игровой процесс/механику, Райан говорит, что некоторые из самых крутых функций, над которыми его команда работала в своих играх, происходят из этого процесса, который он называет "возникающим дизайном".
  • Крайне важно, чтобы ваша команда могла вносить изменения в игру во время выполнения. Чем больше вы можете изменять свою игру во время выполнения, тем больше вы можете находить баланс и значения, и, если вы можете сохранить свое состояние во время выполнения, как это делают Scriptable Objects, вы находитесь в отличном положении.

Упростите отладку:

Это действительно подстолп для первых двух. Чем выше модульность вашей игры, тем легче тестировать каждый из ее элементов по отдельности. Чем легче игра поддается изменению, чем больше в ней элементов, поддающихся контролю в окне Inspector, тем легче будет ее отладка. Убедитесь, что вы можете видеть состояние отладки в Инспекторе и никогда не считайте функцию завершенной, пока у вас нет плана, как вы собираетесь ее отлаживать.

Архитектура с упором на переменные

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

Каждый в вашей команде, независимо от уровня технической подготовки, может определить новую игровую переменную, создав новый актив FloatVariable. Любой MonoBehaviour или ScriptableObject может использовать публичный FloatVariable вместо публичного float, чтобы ссылаться на это новое общее значение.

Еще лучше, если один MonoBehaviour изменяет значение FloatVariable, другие MonoBehaviour могут видеть это изменение. Это создает уровень обмена сообщениями между системами, которые не нуждаются в ссылках друг на друга.

Пример: Очки здоровья игрока

Пример: Очки здоровья игрока

Примером использования этого является здоровье игрока (HP). В однопользовательской игре за показатель здоровья игрока может отвечать ассет FloatVariable под названием PlayerHP. Когда игрок получает урон, это вычитает из PlayerHP, а когда игрок исцеляется, это добавляет к PlayerHP.

Теперь представьте себе префаб полосы здоровья в сцене. Индикатор здоровья отслеживает переменную PlayerHP для обновления. Этот индикатор с легкостью можно перенастроить на другую переменную, например, PlayerMP. Полоса здоровья не знает ничего о игроке в сцене, она просто считывает из той же переменной, в которую записывает игрок.

Как только мы настроены таким образом, легко добавить больше вещей для наблюдения за PlayerHP. Музыкальная система может изменяться, когда PlayerHP становится низким, враги могут менять свои атакующие паттерны, когда знают, что игрок слаб, эффекты на экране могут подчеркивать опасность следующей атаки и так далее. Ключевой момент в том, что скрипт Player не отправляет сообщения этим системам, а сами системы не знают о существовании GameObject игрока. Вы также можете зайти в Инспектор, когда игра запущена, и изменить значение PlayerHP для тестирования.

При редактировании значения FloatVariable может быть хорошей идеей скопировать ваши данные в значение времени выполнения, чтобы не изменять значение, хранящееся на диске для ScriptableObject. Если вы это сделаете, MonoBehaviours должны обращаться к RuntimeValue, чтобы предотвратить редактирование InitialValue, который сохраняется на диске.

Архитектура событий

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

Следующие примеры кода взяты из системы событий, которая состоит из двух частей: ScriptableObject GameEvent и MonoBehaviour GameEventListener. Дизайнер может создать любое количество объектов GameEvent в проекте, каждый из которых будет отвечать за обмен важной информацией. GameEventListener ожидает, когда будет вызван конкретный GameEvent, и реагирует, вызывая UnityEvent (который не является истинным событием, а скорее сериализованным вызовом функции).

Пример кода: GameEvent ScriptableObject

ScriptableObject GameEvent:

Пример кода: GameEventListener

GameEventListener:

Система событий, обрабатывающая смерть игрового персонажа

Система событий, обрабатывающая смерть игрового персонажа

Примером этого является обработка смерти игрока в игре. Это момент, где возможны сильные изменения, для которого сложно определить реализацию логики. Какое событие должен запускать скрипт Player — отображение интерфейса окончания игры или смену музыки? Должны ли враги проверять факт смерти игрока в каждом кадре? Система событий позволяет нам избежать проблемных зависимостей, подобных этим.

Когда игрок умирает, скрипт Player вызывает Raise на событии OnPlayerDied. Скрипту Player нет необходимости знать, каким системам это интересно, поскольку сообщение доступно всем системам. Интерфейс Game Over UI ожидает события OnPlayerDied и начинает анимацию, скрипт камеры на основе события может начать затемнение, а система музыки — изменить оформление. Мы также можем сделать так, чтобы каждый враг слушал OnPlayerDied, вызывая анимацию насмешки или изменение состояния, чтобы вернуться к бездействию.

Эта схема делает невероятно простым добавление новых реакций на смерть игрока. Кроме того, легко протестировать реакцию на смерть игрока, вызывая Raise на событии из некоторого тестового кода или кнопки в Инспекторе.

Система событий, которую они построили в Schell Games, превратилась во что-то гораздо более сложное и имеет функции для передачи данных и автоматической генерации типов. Этот пример по сути был отправной точкой для того, что они используют сегодня.

Архитектура других систем

Scriptable Objects не обязательно должны быть только данными. Попробуйте реализовать в MonoBehaviour любую систему, и вы поймете, что реализацию вполне можно переместить в ScriptableObject. Вместо того чтобы иметь InventoryManager на MonoBehaviour с DontDestroyOnLoad, попробуйте разместить его на ScriptableObject.

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

Здесь вы можете представить скрипт Player, который ссылается на систему инвентаря. При появлении игрока скрипт опрашивает инвентарь на предмет наличия в нем предметов и создает объекты снаряжения. Интерфейс экипировки также может ссылаться на инвентарь и проходить по предметам, чтобы определить, что рисовать.

Создавайте задел на эффективное обновление и отладку архитектуры благодаря ScriptableObject | Unity