Информация, представленная на этой странице, скорее всего, устарела и может быть уже неактуальна.
Entities, components и systems
Хотя Space Station 14 написан на C#, объектно-ориентированном языке программирования, он использует другую модель данных для представления предметов в игре. Эта модель данных называется entity component system (ECS). (Почему мы это делаем? См. ECS)Entities
Каждый предмет в игре представлен entity. Игроки, бананы, дубинки — всё это представлено entity. Entity представлено целым числом. Никакие два entity не имеют одинакового целочисленного представления. Сами по себе entity только отличают один предмет от другого. Без компонентов entity не имеет поведения.Components
Components имеют две основные функции:-
Помечать определённые entity как имеющие определённое поведение. Например, в одной конкретной игре entity, представленная целым числом 37629, содержит
NukeComponentиActivatableUIComponent. Это означает, что эта entity ведёт себя как ядерная бомба, а также имеет пользовательский интерфейс, который можно вызвать, активировав её. -
Хранить данные, необходимые для обработки её поведения. Например,
NukeComponentможет иметь поле данныхTimer, которое представляет, сколько времени осталось до детонации бомбы.
Systems
Entity system (часто сокращённо «система») содержит логику, реализующую поведения для определённых компонентов. В то время как в одной игре может быть несколько entity сNukeComponent, существует только один NukeSystem. Этот единственный NukeSystem отвечает за обработку всех entity с NukeComponent.
Entity systems реализуют поведение, определяя обработчики событий (event handlers) или реализуя update метод, вызываемый каждый тик.
В качестве другого примера рассмотрим FoodComponent. Программист может создать EatingSystem для обработки поедания пищи. EatingSystem слушает событие OnUseInHand — всякий раз, когда OnUseInHand срабатывает, EatingSystem проверяет, есть ли FoodComponent в объекте, который был использован. Если есть, то он уменьшает значение nutritionLeft и воспроизводит звук чавканья.
Вот суть ECS. Если вы хотите узнать больше об этом, загляните в Your mind on ECS. Подход ECS действительно мощный и позволяет нам избегать спагетти-кода, несмотря на сложность SS14.
Вам не обязательно идеально понимать архитектуру ECS с самого начала. Она может пугать как новых программистов, так и тех, кто привык к традиционному ООП. Однако общее «ощущение» и преимущества архитектуры должны проясниться по мере её использования.
Как мне создать Entity и дать ей Components?
SS14 использует систему, которую мы называем prototypes. Это, по сути, «пресеты entity». Они похожи на prefabs в Unity или подтип/obj или /mob в BYOND.
Прототипы entity определяют, какие компоненты находятся на entity и какие данные эти компоненты содержат. Они также определяют базовые данные, такие как имя entity, описание и ID прототипа (используется для спавна).
Пример показан ниже:
Resources/Prototypes/Entities/Objects/Fun/skub.yml. Все прототипы должны находиться в папке Resources/Prototypes и быть организованы в соответствующую папку.
Если вам нужно больше информации о YAML, посмотрите YAML Crash Course и Serialization.
Показанный прототип entity — это «Skub», который выглядит в игре так:

EmitSoundOnUse и ItemCooldown. Задача кодеров — определить, какие данные содержат компоненты и как системы придают им поведение.
Чтобы заспавнить предметы в игре из прототипа, вы можете нажать F5, чтобы открыть Entity Spawn Panel. Также есть способ спавнить прототипы в коде.
Итак, я хочу бибикать!
Ваша цель — сделать Clown Horn, который бибикает при использовании. Для этого нам нужен компонент на entity со звуком, который нужно воспроизвести, и система, которая воспроизводит этот звук после использования в руке (click, или активация с Z).Обычно вам нужно поискать в кодовой базе и спросить других кодеров, существует ли уже компонент/система, которая это делает. В данном случае
EmitSoundOnUse действительно существует в основной кодовой базе SS14. Но для целей этого руководства мы представим, что его нет, и попробуем реализовать его сами!
Для начала давайте создадим простой прототип клоунского клаксона. Я создам новый файл с именем clown_horn.yml и добавлю его в папку Resources\Prototypes\Entities\Objects.

SpriteComponent. Посмотрите спецификацию RSI, если вы не знакомы с системой RSI, но суть в том, что у нас есть два поля для SpriteComponent: путь к RSI относительно Resources/Textures (в данном случае папка называется bikehorn.rsi) и состояние иконки.
Стоит отметить, что прототипы поддерживают наследование (parenting). В данном случае BaseItem является нашим родителем и содержит множество компонентов, универсальных для всех предметов. Таким образом, наш клоунский клаксон также будет иметь эти компоненты: базовые компоненты, такие как Item, Pullable и Physics. Родители не обязательны, но они полезны в определённых случаях, как здесь.
Теперь давайте скомпилируем и проверим наш предмет в игре:

Создание нашего компонента
Чтобы сделать наш компонент, нам нужно создать новый класс, назовём егоPlaySoundOnUseComponent. Но погодите-ка…

Парадигма клиент-сервер
Если вы ещё не читали Codebase Organization, возможно, стоит прочитать. Но для этого руководства нужно понять всего две вещи:- СЕРВЕР и КЛИЕНТ выполняются РАЗДЕЛЬНО.
- Сервер должен обрабатывать большую часть логики для предотвращения эксплойтов. Всё, что находится на клиенте, может быть изменено злоумышленником.
- Клиент отправляет «Я использую этот предмет» на сервер.
- Сервер получает это, проверяет, имеет ли это смысл, и отправляет «воспроизвести бибик» всем клиентам в радиусе.
- Клиент получает это и воспроизводит «бибик».
UseInHandEvent, которое вызывается на сервере при использовании предмета, и функция SoundSystem.Play(), которая воспроизводит звук для клиентов в радиусе.
Эти помощники можно рассматривать как обработку клиентский клик -> сервер и сервер -> клиентский звук за нас. Таким образом, всё, что нам нужно, — это компонент на сервере, который связывает одно с другим.
Базовая реализация компонента
В проектеContent.Server есть папка Sound. В этой папке находится папка Components. Это кажется хорошим местом для нашего нового компонента (и на самом деле, именно здесь находится настоящий EmitSoundOnTriggerComponent). Давайте назовём нашу версию PlaySoundOnUseComponent. Примечание: если вы просто скопируете этот код, он может не сработать, так как вам нужно импортировать различные классы. Ваша IDE может сделать это за вас.
Теперь создадим самый простой компонент:
Component. Если вы хотите, чтобы ваш компонент считывался из YAML, вам нужно добавить [RegisterComponent] над классом. Кроме того, все компоненты должны быть помечены как sealed и partial по причинам, связанным с движком. Вам не нужно слишком беспокоиться о том, что это значит.
В нашем прототипе выше вы можете вспомнить, что мы добавили Sprite, а не SpriteComponent к прототипу ClownHorn. Это потому, что «имена» компонентов генерируются автоматически из имени класса. В данном случае имя нашего компонента — PlaySoundOnUse, которое генерируется простым удалением Component из имени класса.
Теперь давайте добавим PlaySoundOnUse в наш прототип.
Вы должны удалить часть
Component из суффикса класса при использовании в yaml прототипа. Таким образом, PlaySoundOnUseComponent будет разрешён как PlaySoundOnUse в списке components: в yaml-определении.Sprite на нашем клаксоне есть два перечисленных поля: sprite и state. Всё, что вы поместите в эти поля, будет передано в компонент при его создании, и затем наш EntitySystem сможет использовать эти данные для выполнения каких-либо действий.
В нашем случае нам, вероятно, понадобится поле с именем sound в нашем компоненте, которое будет хранить путь к звуку для воспроизведения при активации entity. Это довольно легко сделать:
[DataField], который содержит имя поля, и задать ему значение по умолчанию, в данном случае string.Empty. Теперь мы можем добавить наш звук в прототип клаксона:
Resources (которую SoundSystem всегда предполагает), и мы также предполагаем, что файл Resources/Audio/Items/bikehorn.ogg существует. Если проверить, он есть! Но если нужного звука нет, вы всегда можете добавить его самостоятельно где-нибудь в папке Audio.
Создание нашего EntitySystem
Давайте наконец добавим изюминку нашему клаксону, заставив его… на самом деле бибикать. Как было сказано ранее, нам понадобитсяEntitySystem, который подключается к событию UseInHandEvent и вызывает оттуда некоторый код. Давайте создадим наш EntitySystem PlaySoundOnUseSystem в той же папке Content.Server/Sound:
EntitySystem. Это автоматически регистрирует её как полноценный EntitySystem в игре и позволяет нам использовать некоторые полезные зависимости и переопределять некоторые методы для добавления поведения.
Чтобы подписаться на вызываемое событие, нам нужно переопределить метод Initialize системы; этот метод вызывается при создании EntitySystem.
В этом методе мы добавим вызов SubscribeLocalEvent, и я объясню детали после.
- UID (уникальный идентификатор) entity, на которой было вызвано событие
- Компонент, указанный в подписке, чтобы вы могли получить доступ к его данным и использовать их для изменения поведения
- Само событие, которое содержит полезные данные, такие как entity, которая активировала предмет.
OnUseInHand будет вызываться, когда мы активируем предмет, и мы сможем воспроизвести там наш звук.
Кроме того, мы добавили [Dependency] private readonly SharedAudioSystem в класс. Это позволит нам воспроизводить аудио современным способом (вместо использования устаревшего SoundSystem.Play) в дальнейшем.
PlayPvs полезен для воспроизведения звуков. Он имеет два аргумента:
- Звук для воспроизведения.
sound из нашего PlaySoundOnUseComponent.
- Исходная entity
Owner entity). Если этот аргумент не указан, звук воспроизводится глобально и будет слышен всем игрокам.
Если вы скомпилируете игру и заспавните наш клаксон с помощью меню F5 Entity Spawn Menu, вы можете попробовать активировать его в руке и — невероятно! Он правильно воспроизводит звук! Надеюсь! Если нет, возможно, вы что-то напутали в YAML или пропустили метод в EntitySystem.
Кроме того, PlayPvs автоматически управляет фильтрацией по расстоянию, так что вам не нужно об этом беспокоиться.
На этом всё
С этим руководство завершено! Если вы хотите продолжить экспериментировать с вашим новым клаксоном, вот несколько идей:- Попробуйте реализовать клаксон с использованием существующих компонентов. Вы можете обратиться к skub.yml выше на этой странице.
- Добавьте задержку нажатия, добавив
ItemCooldownв ваш прототип и вызываяRefreshItemCooldownEvent. - Настройте громкость/вариативность воспроизводимого звука (см. аргумент
audioParamsфункцииPlayPvs()). - Сделайте так, чтобы звук также воспроизводился, когда на клаксон наступают.
- Это довольно сложно и включает добавление большого количества новых данных! Посмотрите на стекло (glass shards) в качестве примера.
- Сделайте так, чтобы клаксон наносил урон при атаке с помощью
MeleeWeaponComponent. - Сделайте клаксон съедобным с помощью
FoodComponentиSolutionContainerComponent. - Добавьте поддержку воспроизведения случайного звука из SoundCollection или SoundSpecifier вместо одного звука (настоящий EmitSoundOnUse делает это, если вам нужны подсказки).
- Погрузитесь в код взрывов и дайте ему 5% шанс взорваться при каждом бибикании!