Как стать автором
Обновить

Комментарии 9

Хорошая статья ) спасиб ) на мой взгляд парадигма ECS очень подходит под разработку игр.
Как и во всем, каждый инструмент подходит для своей задачи. В последнем проекте я использую ECS только для игровой логики, для GUI он никак не подходит.
ну всё логично, в GUI по моему мнению лучше всё на ивентах аля/или реактивный код. В особо запущенных случаях — MVC. А ECS это таки про компоненты и что сущности можно собирать из компонентов.
Я уже и так и этак пытался применить ECS к сложной логике пошагового боя (например система d20) и никак не могу придумать случаи, когда ECS был быстр(никак не получается расположить данные, чтобы всё было кэш френдли) и удобен(создаются целые рои сущностей относящиеся к на самом деле к одному логическому объекту). Есть ли идеи как можно взглянуть на проблему так, чтобы ECS был полезен?
Справедливости ради, d20 чудовищно сложная для имплементация система, что на ECS, что на ООП, так что это не в пику ECS.
if (avatarRequest.Value.SpawnTime <= gs.Time)

Меня немного это смущает. Что будет если тик не произойдет? И вообще, неужели необходимо спавнить игрока таким образом?
В нашей системе тик произойдет в любом случае. Спавн персонажей выполняется только на сервере, а сервер тики не пропускает. Данный пример был взят из реального проекта где используется отложенный по времени спавн персонажа. В целом все таймеры в игре реализованы таким же способом. Записывается время тика на котором должно произойти событие. И условие которое проверят текущее время системы.
Тот же самый метод мы используем в примере HealthPowerUpMovementSystem:
if(pair.Value.NextChangeDirection <= gs.Time)
{
   pair.Value.NextChangeDirection = (uint) (healthPowerUpStats.SecondsToChangeDirection * GameState.Hz);
 movement.Velocity *= -1;
}
Здравствуйте Etlay,
Большое спасибо за статью, есть несколько вопросов после прочтения.
1) Правильно ли я понимаю, что у Вас возможно хранение компонентов без привязки к Entity?
(например компонент SpawnAvatarRequest, который не привязан к Entity и несет в себе данные о том, кого и когда необходимо заспаунить)
Почему не сделать SpawnAvatarRequst сущностью? Мое понимание ECS основано на том, что компонент обязательно должен принадлежать сущности.
2) Компоненты могут добавляться динамически в «рантайме». Действительно ли это нужно и используется ли это в игре?
То есть, можно в RuleBook расписать для каждой сущности набор ее компонент при спауне. В процессе игры добавляются ли к сущности новые компоненты, кроме тех, что добавились на спауне?
(интересен конкретно Ваш опыт, в теории конечно понятно, что динамическое добавление компонента к сущности нужно, но встречается ли это на практике у Вас? особенно в шутере)
3) Так как мир инициализируется на сервере и сущности спаунятся на сервере — бывают ли ситуации,
когда на клиенте эта самая инициализация (в момент передачи полного gameState при первом запросе или при рестарте соединения)
превышает время, отведенное для тика? (например Hz 20=50 ms, а полный gameState при ините мира/рестарте сети занимает 120ms).
Если такие ситуации бывают, как Вы с ними работаете?
4) var playerEntity = gs.WorldState.CreateEntity(avatarRequest.Value.PlayerId); И далее идет работа по инициализации компонент (playerEntity.Add...()).
В интерфейсе у сущности для каждого компонента есть своя функция по добавлению? Можете этот момент прокомментировать?) Правильно ли я понимаю, что мне как программисту при добавлении компонента нужно в интерфейсе сущности также еще добавить функцию, которая и реализует добавление этого нового компонента?

Еще раз спасибо за статью.
1) Нет, все компоненты привязаны к какой-то entity. Я упустил в статье момент создания SpawnAvatarRequest. Он у нас происходит не внутри систем ECS, а при первоночальном создании GameState:
var avatarRequestEntity = gs.WorldState.CreateEntity();
avatarRequestEntity.AddSpawnAvatarRequest(1); // создаем игроков на первом тике мира

2) Да такой механизм нужен и используется. Это по-сути одна из «фичей» ECS. Когда можно динамически добавить новое поведение к уже существующей сущности. На примере нашего шутера игроку в рантайме могут добавляться компоненты: неуязвимости, невидимости, ускорения движения и т.д. Пример кода из системы расчета невидимости:
if (ghost.IsInHidingZone) // если игрок в зоне "невидимости"
{  
     playerEntity.DelAimable(); // удаляем компонент, который отвечает за то что в игрока можно целится
     playerEntity.AddInvisible(); // добавляем компонент невидимости
}

3) Да такие ситуации бывают, вообще ответ на этот вопрос достоин отдельной статьи. О том как работает синхронизация я рассказывал в статье о сетевом коде проекта
Если вкратце, то клиент просто пропускает симуляцию нескольких тиков в случае хиккапа, и берет последние валидные данные с сервера. (игрок при этом видит подвисание).

4) Верно у нас в интерфейсе entity для каждого компонента существуют методы Add*MyComponent*() и Del*MyComponent(). Однако мы используем кодогенерацию в нашей ECS, и вам как программисту нужно лишь написать свой компонент, и указать что он используется в Enitity. Вот пример как выглядит класс Entity в проекте кодогенерации:
public class Entity
{
       public Transform Transform;
       public Movement Movement;
       public CharacterRotation CharacterRotation;

       [DontPack]
       public AmmoToAdd AmmoToAdd;
       
       [DontPack]
       public ShotDamage ShotDamage;
}

Кодогенератор добавить в проект все необходимые методы по созданию, удалению, итерированию и сериализации этого компонента.
Спасибо за ответ. Это довольно интересная тема для меня, если не против, еще пару вопросов:
1. Хотел уточнить (ответ впринципи ясен, но в статье этого не нашел), правильно ли я понимаю, что все поля, которые находятся внутри компонента должны быть либо простыми типами, либо IComponent,
чтобы кодогенерация могла понять, как их сериализовать и т.п. (аналог UPROPERTY в Unreal)? Не видно, чтобы сами поля обрамлялись в атрибуты (например [IComponent] Invulnerability{[UProperty] int duration;}),
значит поля рефлексией итерируются и делается определенное заключение. То есть в компоненте Transform поле Position является [IComponent]public class Vector{...}?
2. Если говорить о взаимодействии Моделей и Презентера, можете общими словами описать, как это происходит? Как я предполагаю, у Вас в ПрезенторКонтекстаБоя создается и инициализируется ECS.
В этом презенторе происходит подписывание на событие добавления/удаление сущностей. Когда сущность спаунится, презентор оповещается об этом и создает под нее ПрезентерСущности.
Далее вопрос, как построить презентер для компонентов сущности, которые добавляются в рантайме. Например сущность может стать на некоторое время неуязвимой и придумали визуально показывать щит вокруг персонажа.
Как ПрезентерСущности узнает, что появился новый компонент и нужно внутри добавить ПрезентерНеуязвимости? Он оповещается о всех новых добавленных компонентах и под каждый компонент
подбирает и создает нужный презентер, где происходит примерно следующее? public class InvulPresenter(IEntity entity, IInvulView viewContainer) {var invul = entity.GetInvul(); invul.Changed += OnInvulChanged; invul.Removed += OnInvulRemoved; ...}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий