
Эта страница объясняет, как использовать ScriptableObjects в качестве контейнеров логики. Таким образом, вы можете рассматривать их как делегатные объекты или небольшие пакеты действий, которые вы можете вызывать по мере необходимости.
Это четвертая часть серии из шести мини-руководств, созданных для помощи разработчикам Unity с демонстрацией, которая сопровождает электронную книгу, Создайте модульную игровую архитектуру в Unity с помощью ScriptableObjects.
Демонстрация вдохновлена классической механикой аркадных игр с мячом и ракеткой и показывает, как ScriptableObjects могут помочь вам создать компоненты, которые можно тестировать, масштабировать и которые удобны для дизайнеров.
Вместе электронная книга, демонстрационный проект и эти мини-руководства предоставляют лучшие практики для использования шаблонов проектирования программирования с классом ScriptableObject в вашем проекте Unity. Эти советы могут помочь вам упростить код, сократить использование памяти и способствовать повторному использованию кода.
Эта серия включает в себя следующие статьи:
Прежде чем погрузиться в демонстрационный проект ScriptableObject и эту серию мини-руководств, помните, что в своей основе шаблоны проектирования — это просто идеи. Они не применимы ко всем ситуациям. Эти техники могут помочь вам изучить новые способы работы с Unity и ScriptableObjects.
Каждый шаблон имеет свои плюсы и минусы. Выбирайте только те, которые действительно приносят пользу вашему конкретному проекту. Ваши дизайнеры сильно полагаются на Unity Editor? Шаблон на основе ScriptableObject может быть хорошим выбором, чтобы помочь им сотрудничать с вашими разработчиками.
В конечном итоге, лучшая архитектура кода — это та, которая подходит вашему проекту и команде.

Используя шаблон стратегии, вы можете определить интерфейс или базовый класс ScriptableObject, а затем сделать эти делегатные объекты взаимозаменяемыми во время выполнения.
Одно из применений — инкапсуляция алгоритмов для выполнения конкретных задач в ScriptableObject, а затем использование этого ScriptableObject в контексте чего-то другого.
Например, если вы пишете систему ИИ или поиска пути для класса EnemyUnit, вы можете создать ScriptableObject с техникой поиска пути (например, A*, Дейкстра и т. д.).
Сам EnemyUnit на самом деле не будет содержать никакой логики поиска пути. Вместо этого он будет хранить ссылку на отдельный ScriptableObject «стратегии». Преимущество этого дизайна в том, что вы можете переключиться на другой алгоритм, просто заменив объекты. Это один из способов выбора различных поведений во время выполнения.
Когда MonoBehaviour нужно выполнить задачу, он вызывает внешние методы на ScriptableObject, а не свои собственные. Например, ScriptableObject может содержать публичные методы для MoveUnit или SetTarget для управления вражеским юнитом и указания назначения.
Вы можете улучшить этот шаблон с помощью абстрактного базового класса или интерфейса. Это означает, что любой ScriptableObject, который реализует стратегию, может быть заменен другим. Этот горячезаменяемый ScriptableObject "встраивается" в MonoBehaviour, ссылающийся на него - даже на лету во время выполнения.
Если вам нужно, чтобы EnemyUnit изменял поведение из-за игровых условий, внешний контекст (MonoBehaviour) может проверить эти условия. Затем он может подключить другой ScriptableObject в качестве ответа.
Разделив детали реализации на ScriptableObject, вы также можете облегчить лучшее распределение обязанностей среди вашей команды. Один разработчик может сосредоточиться на алгоритме внутри ScriptableObject, в то время как другой работает над контекстом MonoBehaviour.
Чтобы создать это подключаемое поведение, убедитесь, что вы:
Организация вашего кода таким образом может облегчить переключение между различными реализациями одной и той же стратегии. Это подключаемое поведение затем становится легче отлаживать и поддерживать.
Алгоритм или стратегия не обязательно должны быть сложными. Проект PaddleBallSO, например, демонстрирует довольно простую систему воспроизведения аудио в SimpleAudioDelegate.
Абстрактный класс AudioDelegateSO определяет единственный метод Play, который принимает параметр AudioSource. Конкретная реализация затем переопределяет это.
Подкласс SimpleAudioDelegateSO определяет массив AudioClips. Он выбирает случайный клип и воспроизводит его с помощью переопределенной реализации метода Play. Это добавляет вариации в высоте и громкости в пределах пользовательского диапазона.
Хотя это всего лишь несколько строк, вы можете создать множество различных аудиоэффектов с помощью приведенного ниже кода.
Хотя этот конкретный пример не совсем подходит для интенсивного использования аудио, он представлен здесь как базовая демонстрация использования ScriptableObjects в паттерне стратегии.
Дизайнер может создать множество различных ScriptableObjects для представления звуковых эффектов, не касаясь кода. Снова, это требует минимальной поддержки от разработчика, как только базовый ScriptableObject завершен.
В PaddleBallSO любой теперь может настроить новый массив звуков для воспроизведения, когда мяч ударяется о одну из стен уровня. Дизайнеры получают творческую независимость и гибкость, потому что они работают полностью в редакторе. Этот подход освобождает ресурсы программирования, так как разработчикам больше не нужно помогать с каждым дизайнерским решением.

Вы также можете увидеть аудио пример в демо Patterns. Каждый звук происходит от немного другого актива SimpleAudioDelegateSO, с небольшими вариациями между экземплярами.
В этом примере каждый угол включает AudioSource. Кастомный MonoBehaviour AudioModifier использует делегат на основе ScriptableObject для воспроизведения звука.
Различия в высоте звука возникают только из-за настроек каждого актива ScriptableObject (BeepHighPitched_SO, BeepLowPitched_SO и т.д.).
Использование ScriptableObject для управления логикой действий может облегчить вашей команде дизайнеров эксперименты с идеями. Это позволяет дизайнеру работать более независимо от разработчика.

Проект PaddleBallSO также использует паттерн стратегии в своей системе целей. Хотя это не то, что должно изменяться во время выполнения, инкапсуляция каждого объекта в ScriptableObject предоставляет гибкий способ тестирования условий победы и поражения.
Абстрактный базовый класс, ObjectiveSO, хранит такие значения, как имя цели и завершена ли она.
Конкретные подклассы, такие как ScoreObjectiveSO, затем реализуют фактическую логику завершения каждой цели. Они делают это, переопределяя метод CompleteObjective класса ObjectiveSO и добавляя логику условий победы.
Должен ли игрок достичь определенного счета или победить определенное количество врагов? Должны ли они достичь определенного места или подобрать определенный предмет? Это общие условия победы, которые могут стать целями на основе ScriptableObject.
ObjectiveManager служит более широким контекстом для ScriptableObjects. Он поддерживает список ObjectiveSOs и полагается на каждый ScriptableObject, чтобы определить, завершен ли он. Когда каждый ObjectiveSO показывает состояние завершения, игра окончена.
Например, ScoreObjectiveSO показывает один из способов реализации цели по набираемым очкам:
ObjectiveManager заботится только о том, чтобы все заданные цели были выполнены. Он не осведомлен о деталях каждой цели.
Снова, цель здесь - модульность. Это позволяет вам настраивать каждую ObjectiveSO, не затрагивая уже существующие.
Игра PaddleBallSO на самом деле имеет только одну цель. Если один из игроков достигает цели выигрышного счета, игра заканчивается.
Тем не менее, вы можете расширить это или объединить цели, чтобы создать более сложную систему целей. Экспериментируйте и посмотрите, сможете ли вы создать новые игровые режимы (например, набрать минимальное количество очков до истечения времени).
Поскольку логика инкапсулирована внутри ScriptableObject, вы можете заменить любую ObjectiveSO на другую. Создание нового условия победы просто включает в себя перенастройку списка в ObjectiveManager. В некотором смысле, цель является "встраиваемой" в окружающий контекст.
Обратите внимание, что одним из удобных аспектов ObjectiveSO является событие, используемое для отправки сообщений между GameObjects. Далее мы исследуем, как использовать ScriptableObjects для реализации этой архитектуры, основанной на событиях.

Узнайте больше о шаблонах проектирования с ScriptableObjects в электронной книге Create modular game architecture in Unity with ScriptableObjects. Вы также можете узнать больше о распространенных шаблонах проектирования разработки Unity в Level up your code with game programming patterns.