Comments 48
И еще как вы встраиваете физическое состояние в ECS модель? Получается за позицию объекта отвечает и ECS системы и физический движок. Плюс всякие OnTriggerEnter и другие события надо перенести в ECS.
Вот вы говорите что у вас мобильная игра, а потом говорите про дельта-компрессию.
Как вы справляетесь с потерей пакетов? Избыточно перепосылаете состояние мира несколько раз?
Обратно сервер получает от клиента номер последнего дошедшего состояния, и есть параметр, который говорит, насколько часто надо посылать полный стейт (который тем не менее хитро запакован, с учетом знаний об игре — content-aware compression).
Да, ввод понятно что дублируете:)
Т.е. если я правильно понял, при потере 1 пакета состояния мира, вы не можете применить состояние на клиенте в течение еще 1 RTT?
т.к. клиент должен отправить на сервер nack, и только после этого сервер перепошлет потеряный пакет?
1. Клиент симулирует.
2. Интерполяция между дошедшими состояниями. Т.е. если дошли Состояние5 и Состояние7, а Состояния 6 еще нет, но уже нужно, оно получается с помощью интерполяции.
Клиент должен буферизировать baseline (т.к. входящие дельта-обновления будут приходить не для самого свежего baseline ввиду сетевой задержки).
Сервер должен буферизировать один baseline и последующие delta-обновления (чтобы в случае получения ACK на какое-либо дельта-обновление мог сделать baseline+delta и назначить его новым baseline).
Передавать полностью стейт мира (кроме initial при подключении) нужды нет вообще — у клиента будет гарантированно такой же стейт мира за счёт применения дельта-обновлений (для защиты от криворукости в дебаг-версию игры можно добавить baseline checksum в дельта-обновление — чтобы при построении нового baseline на клиенте сверить его с серверным).
Это довольно простой, но очень эффективный подход. Избыточность передаваемых данных крайне небольшая и компенсируется существенным выигрышем за счёт дельта-компрессии в целом.
Прям детерменизм нужен только если есть объекты которые очень редко обновляются (и то обычно они редко обновляются т.к. игрок их не видит, а значит и хрен с ним с недетерменизмом).
А так да, много раз в секунду отправляется все состояние мира.
Скажите пожалуйста, а как вы применяете команды (пользовательский ввод) в системах?
Удобно ли пользоваться подходом "все есть компонент" и в случае с "событием" Dead? Или все же есть ощущение что не хватает более callback ориентированного подхода?
По поводу событий: в некоторых реализациях ECS («закрытых» пока для внешнего мира, поэтому тут не упомянул) я видел подходы с реализованными событиями. Мне лично понравилось и мы думаем над ними.
Сейчас 2020 год. Они все еще закрытые? Не поделитесь ссылками?
Нашел вот такое на гитхабе.
https://github.com/ancientbuho/entitas-unirx-additions
Это extension для rx. Позволяет подписаться на добавление/удаление/изменение компонента. Пример:
entity.OnComponentAddedAsObservable().Subscribe(dead => do something);
Выглядит больше как сахар, чем подход, правда. Но удобен тем, что можно подписаться сразу на несколько событий (для тех кто знаком с rx). И тут сразу минус, т.к. подписка имеет состояние (запоминает сработавшие события когда ожидаешь много), то при перезагрузке игры из тех же компонент, не сработает. Т.е. ее тоже нужно сериализовать :(
И GameState с кучей таблиц разномастных компонентов, наследующих IComponent. Не пробовали собрать их в одну структуру с доступом по типу и id?
Формально мы не различаем типы сущностей. Для нас сущность может содержать любой компонент, хоть все сразу (но не более одного — особенность реализации). Фактически, системы в коде сами понимают, что это за сущность, по типу компонентов на ней. Например, на двери скорее всего будет компонент Door с ее параметрами и какой-нибудь Destructable, если ее можно разрушать.
По идее ECS все равно, что за объекты у вас в игре, вы сами решаете, каким образом их «обозначить» и нужно ли их как-то различать. Я видел примеры реализаций ECS как с различаемыми типами сущностей (где можно конкретно сказать, что это игрок, это оружие, это препятствие и у них могут быть только «вот эти» компоненты), так и с обобщенной сущностью, где понять, что это за сущность, можно только по ее компонентам (как реализовано у нас).
Может, будет удобней добавлять эту функциональность в Entity через методы расширения? Их можно писать/генерировать где-нибудь рядом с компонентом и добавлять или удалять вместе с ним же.
Забавно кстати что вы это решили включить в ECS:)
Кажется что интерполяция, сериализация и ECS — три разные вещи, а у вас они вместе лежат.
Кстати, у меня вот какой вопрос:
Своего игрока, как известно, надо экстрополировать (ну т.е. client-side prediction).
А вражеских игроков надо интерполировать.
Как у вас это разделение реализовано в концепции систем?
А еще: при выстреле вы наверняка используете Lag Compensation. Для этого надо откатывать во времени назад всех, кроме стрелка. Так что тут аналогичный вопрос. Или вы откатываете только физику, а поля компонент оставляете теми же?
P.S. если кто-то не понимает о чем я тут говорю, тут можно прочитать.
Ок, понятно.
Мы примерно из тех же соображений слили воедино сериализацию и дупликацию объектов:) Правда интерполяция у нас немного в стороне.
Скажите, когда на сервер приходит запоздалый выстрел, берется предыдущий от него стейт мира и на него заново накатываются инпуты + интерполируются позиции/ повороты перевычисляются последующие, вплоть до текущего стейта мира, который рассылается всем? Или какой-то менее расходный по ресурсам метод?
него заново накатываются инпуты
Нет-нет.
1) мир откатывается
2) проверяем, попал ли игрок во врага
3) мир возвращается в исходное состояние
4) попадание применяется (если оно было)
Мы много всяких прототипов писали, даже со сложными временными взаимодействиями, но никогда не надо было переприменять ввод на сервере.
Если кратко, на клиенте мы симулируем только локального игрока, остальных не трогаем, берем с сервера (т.е. игрок смотрит на мир в прошлом, а сам в нем в будущем).
Откат во времени есть, работает только на сервере. Подробнее об этом будет в статье.
1) Как Ваша реализация решает задачу зависимостей системы от набора компонентов самого Entity? т.е MovementSystem в прямом смысле требует чтобы у Entity были определены компоненты Position & Velosity.
Судя по описанной реализации GameState & Entity Вы вынуждены делать множественные относительно рандомные чтения из памяти только для получения списка Entity, которые обладают необходимым набором компонент для каждой конкретной системы, например:
for entity in entities: # cache friendly
if entity.movement and entity.velocity: # чтение из 2х разных источников в памяти
process(entity)
2) рассматривали ли Вы исходники реализаций ECS имеющиеся в общем доступе, например, EntityX?
2) Смотрели только те, что указаны в статье. Сейчас немного смотрим другие, как идеи для улучшения нашего фреймворка.
Тоже зацепил этот момент у вас.
Я в своей реализации держу у каждой системы кэш сущностей, по которым ей надо проходить в каждом цикле — а не компонентов.
Список обновляется хэндлером по эвенту ComponentAdded / Removed, в котором сущность проверяется на соответствие сигнатуре компонентов данной конкретной системы.
Выглядит слегка громоздко и накладывает оверхэд на каждое добавление / удаление компонента, зато сильно экономит ресурсы на сложных системах, работающих с несколькими компонентами.
Возможно в идеале надо совмещать оба эти подхода
Спасибо большое за статью! Хочу дополнить и спросить:
| entity ID as integer. Ни в одном из рассматриваемых решений поддержки не было.
По идее можно добавить компонент с единственным полем — айди. Назовем его IdComponent.
| join by ID reference O(N+M) - соответственно связь можно обеспечить по этому айди.
Для ускорения доступа можно завести систему-синглтон, которая будет например класть в словарик entity и отдавать по айди.
Не понял что значит M в O(N+M). N — это все entity в игре, так? Что тогда M?
| reuse component type — возможность использовать один раз написанный тип компонента в разных типах сущностей. Поддерживал только Entitas.
Не понимаю, что значит разные типы сущностей? Не могли бы Вы привести пример?
Новичок в юнити, возник вопрос: а как вы построили взаимодействие ECS<>Unity?
ECS у вас в отдельном потоке или основном?
Однако, если брать текущую реализацию unity ecs и jobs то можно построить и на разных потоках, но статья была написана раньше чем юнитеке сделали поадекватнее свой ецс)
Как и почему мы написали свой ECS