На каком движке сделан world of tanks blitz

Обновлено: 06.07.2024

Игра про танки должна быть реалистичной даже на мобильниках, поэтому мы улучшаем графику постоянно. Но проводить в игре серьезные визуальные изменения — задача эпических масштабов. Просто держите в уме, что World of Tanks Blitz скачали 137 миллионов раз во всем мире, из них только в России 36,4 миллиона — сочетаний операционных систем (не только типов, но и версий, от старых до новых) + процессоров + графических ускорителей + объёма накопителей и объёма оперативной памяти немыслимое количество. «Танкисты» играют как на новых флагманах, так и на слабых устройствах, которые появились задолго до того, как вышла первая версия WoT Blitz. А игра должна работать у всех, и, желательно, с примерно одинаковой графикой, чтобы в онлайн-баталиях все были равны. Поэтому среди множества графических улучшений в обновление 7.0 вошли в первую очередь те, которые будут «летать» на максимально возможном количестве смартфонов, планшетов и компьютеров.

А теперь подробнее о семи графических апгрейдах:

Откат орудийных стволов при выстреле

Одна из самых частых просьб игроков — опция отката орудийных стволов при выстреле. Во имя реалистичности и исторической точности! Дело в том, что в реальной жизни танковое орудие не может быть закреплено жестко в башне, потому что энергия отдачи попросту разрушит её. Чтобы этого избежать, и были придуманы механизмы отката орудия. В обновлении 7.0 их реализовали в игре, чтобы всё было действительно по-настоящему.

Сейчас откат орудий доступен на большинстве танков I–X уровней, в будущем он появится и на оставшуейся технике. При этом лучше всего он будет заметен на машинах с открытой казенной частью орудия. Кстати, специально включать пользователям данную опцию не нужно, она работает на всех устройствах независимо от настроек графики.

Следы от попаданий по танкам

Шрамы украшают мужчин, а следы от снарядов — танки. Если, конечно, не выводят их из строя. Теперь на технике будут оставаться «памятные метки» после выстрелов. В отличие от предыдущей, исключительно «косметической» фичи, следы от попаданий по танкам имеют и практическое применение. Игрокам будет проще понять, куда попал их снаряд после выстрела или, наоборот, в какое место был сделан выстрел по их танку, каким типом снаряда нанесен урон. Всё это помогает «танкисту» лучше планировать действия: смену позиции, более точное наведение орудия и т. д. Да и само осознание, что на танке противника остались «шрамы» от твоих выстрелов, значительно повышает чувство превосходства в игре.

image

Эта история началась более трех лет назад. Наша небольшая компания DAVA стала частью Wargaming, и мы начали обдумывать, какие проекты делать дальше. Чтобы напомнить, каким был мобайл три года назад, скажу, что тогда не было ни Clash Of Clans, ни Puzzle & Dragons, ни многих очень известных сегодня проектов. Mid-core тогда только-только начинался. Рынок был в разы меньше сегодняшнего.

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

Тогда в разработке у нас находилось несколько игр. Одна из них носила рабочее название «Sniper». Основной геймплей-идеей была стрельба в снайперском режиме из стоящего в обороне танка, по другим танкам, которыми управлял AI и которые могли атаковать в ответ.

В какой-то момент нам показалось, что стоящий танк — это очень скучно, и за неделю мы сделали прототип мультиплеера, где танки уже могли ездить и атаковать друг друга.

С этого все и началось!

Когда мы начинали разработку “Снайпера”, то рассматривали технологии, которые тогда были доступны для мобильных платформ. На тот момент Unity был еще на достаточно ранней стадии своего развития: по сути, необходимых нам технологий еще не было.

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

В итоге, мы решили дорабатывать свой движок!

Он на тот момент уже использовался в наших предыдущих казуальных проектах. Движок имел достаточно хорошо написанный низкий уровень работы с платформами и поддерживал iOS, PC, Mac, плюс были начаты работы по Android. Было написано много функциональности для создания 2D-игр. То есть, был неплохой UI и много всего для работы с 2D. В нем были первые шаги по 3D-части, так как одна из наших игр была полностью трехмерной.

Что у нас было в 3D-части движка:

  • Простейший граф сцены.
  • Возможность рисования статических мешей.
  • Возможность рисования анимированных мешей со скелетной анимацией.
  • Экспорт объектов и анимаций из Collada-формата.
Начало работ

Началось все с доказательства возможности отрисовать ландшафт на мобильных устройствах: тогда это были iPhone 4 и iPad 1.

После нескольких дней работы мы получили вполне функциональный динамический ландшафт, который работал довольно сносно, требовал где-то 8MB памяти и давал 60fps на этих устройствах. После этого мы начали полноценную разработку игры.

Прошло около полугода, и маленький мини-проект превратился в то, чем сейчас является Blitz. Появились совершенно новые требования: MMO, AAA-качество и другие требования, которые движок в его изначальном виде на тот момент уже не мог обеспечить. Но работа кипела полным ходом. Игра работала и работала неплохо. Однако производительность была средней, объектов на картах было мало, и, собственно, было множество других ограничений.

На этом этапе мы начали понимать, что фундамент, который мы заложили в движок, не выдержит пресса реального проекта.

Как все работало на тот момент

Вся отрисовка сцен была основана на простой концепции Scene Graph.

Основной концепции являлись два класса:

  • Scene — контейнер сцены, внутри которого происходили все действия
  • над сценой.
  • SceneNode — базовый класс узла сцены, от которого наследовались все классы, которые находились в сцене:
  • MeshInstanceNode — класс для отрисовки мешей.
  • LodNode — класс для переключения лодов.
  • SwitchNode — класс для переключения свитч объектов.
  • еще около 15-ти классов наследников SceneNode.
  • Update — функция которая вызывалась для каждого узла, для того чтобы сделать Update-сцены.
  • Draw — функция, которая вызывалась для каждого узла, для того чтобы отрисовать этот узел.
  • Когда количество нодов в уровне достигло 5000, оказалось что просто пройти по всем пустым функциям Update, занимает около 3ms.
  • Аналогичное время уходило на пустые ноды, которым не требовалось Draw.
  • Огромное количество кэш-миссов, так как работа всегда велась с разнотипными данными.
  • Невозможность распараллелить работу на несколько ядер.
  • Изменение кода в базовых классах влияло на всю систему целиком, то есть каждое изменение SceneNode::Update могло сломать что угодно и где угодно. Зависимости становились все сложнее и сложнее, и каждое изменение внутри движка почти гарантированно требовало тестирования всей связанной функциональности.
  • Невозможно было сделать локальное изменение, например, в трансформациях, чтобы не задеть остальные части сцены. Очень часто малейшие изменения в LodNode (узел для переключения лодов), ломали что-то в игре.
Первые шаги по улучшению ситуации

Для начала мы решили полечить проблемы с производительностью и сделать это быстро.

Собственно, сделали мы это, введя дополнительный флаг NEED_UPDATE в каждой ноде. Он определял, нужно ли такой ноде вызывать Update. Это действительно повысило производительность, но создало целый ворох проблем. Фактически код функции Update выглядел вот так:


Это вернуло нам часть производительности, однако началось много логических проблем там, где их не ждали.

LodNode, и SwitchNode — ноды, отвечающие, соответственно, за переключение лодов (по расстоянию) и переключение объектов (например, разрушенных и неразрушенных) — начали регулярно ломаться.

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

Самым первым шагом было заложить архитектуру, которая позволит в перспективе решить все возникающие у нас проблемы.

  • Минимизация зависимости между независимыми подсистемами.
  • Изменения в трансформациях не должны ломать систему лодов, и наоборот
  • Возможность положить код на многоядерность.
  • Чтобы не было функций Update или аналогичных, в которых выполнялся разнородный независимый код. Легкая расширяемость системы новой функциональностью без полного перетестирования старой. Изменения в одних подсистемах не влияет на другие. Максимальная независимость подсистем.
  • Возможность расположить данные линейно в памяти для максимальной производительности.
Комбинирование компонентного и data-driven-подхода

Решением этой проблемы стал компонентный подход, комбинированный c data-driven подходом. Дальше по тексту я буду употреблять data-driven-подход, так как не нашел удачного перевода.

Вообще понимание компонентного подхода у многих людей самое разное. То же — и с data-driven.

В моем понимании, компонентный подход — это когда некая необходимая функциональность строится на основе независимых компонентов. Самый простой пример — это электроника. Есть чипы, у каждого чипа есть входы и выходы. Если чипы подходят друг к другу, их можно соединить. На базе такого подхода построена вся индустрия электроники. Есть тысячи разных компонентов: соединяя их друг с другом, можно получать совершенно разные вещи.

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

Что же такое data-driven. В моем понимании, это подход к проектированию программного обеспечения, когда за основу потока выполнения программы берутся данные, а не логика.

На нашем примере представим следующую иерархию классов:


Код обхода этой иерархии иерархически выглядит так:


В данной иерархии C++ наследования мы имеем три различных независимых потока данных:

Давайте представим, как это должно выглядеть в data-driven подходе. Напишу на псевдокоде, чтобы была понятна идея:


По сути, мы развернули циклы работы программы, сделав это таким образом, чтобы все отталкивалось от данных.

Данные в data-driven подходе являются ключевым элементом программы. Логика — лишь механизмы обработки данных.

Новая архитектура

В какой-то момент стало понятно, что надо идти в сторону Entity-based подхода к организации сцены, где Entity являлась сущностью, состоящей из многих независимых компонентов. Хотелось, чтобы компоненты были полностью произвольными и легко комбинировались между собой.

Читая информацию по этой теме, я наткнулся на блог T-Machine.

• Entity не содержит никакой логики, это просто ID (или указатель).
• Entity знает только ID компоненты, которые ей принадлежат (или указатель).
• Компонент — это только данные, то есть. компонент не содержит никакой логики.
• Система — это код, который умеет обрабатывать определенный набор данных и выдавать на выходе другой набор данных.

Когда я понял это, в процессе дальнейшего изучения различной информации наткнулся на Artemis Framework и увидел хорошую реализацию этого подхода.
Исходники тут, если предыдущий линк не работает: Artemis Original Java Source Code

Если вы разрабатываете на Java, то очень рекомендую посмотреть на него. Очень простой и концептуально правильный Framework. На сегодняшний день он спортирован на кучу языков.

То, чем является Artemis, сегодня называют ECS (Entity Component System). Вариантов организации сцены на базе Entity, компонентов и data-driven достаточно много, однако мы по итогу пришли к архитектуре ECS. Сложно сказать, насколько это общепринятый термин, однако ECS значит, что есть следующие сущности: Entity, Component, System.

Самое главное отличие от других подходов это: Обязательное отсутствие логики поведения в компонентах, и отделение кода в системы.

Этот пункт очень важен в “православном” компонентном подходе. Если нарушить первый принцип, появится очень много соблазнов. Один из первых — сделать наследование компонентов.

Несмотря на гибкость, заканчивается обычно макаронами.

image

Изначально кажется, что при таком подходе можно будет сделать множество компонентов, которые ведут себя похожим образом, но чуть-чуть по-разному. Общие интерфейсы компонентов. В общем, можно опять свалиться в ловушку наследования. Да, это будет чуть лучше, чем классическое наследование, однако постарайтесь не попасть в эту ловушку.

ECS — более чистый подход, и решает больше проблем.

Чтобы посмотреть на примере, как это работает в Artemis, можете глянуть вот тут.

Я на примере покажу, как это работает у нас.

Главным классом контейнером является Entity. Это класс, который содержит массив компонентов.

Вторым классом является Component. В нашем случае, это просто данные.

Вот список компонентов, используемых у нас в движке, на сегодняшний день:


Третим классом является SceneSystem:


Функции RegisterEntity, UnregisterEntity вызываются для всех систем в сцене тогда, когда мы добавляем или удаляем Entity из сцены.


Функции RegisterComponent, UnregisterComponent вызываются для всех систем в сцене, тогда, когда мы добавляем или удаляем Component в Entity в сцене.
Также для удобства есть еще две функции:


Эти функции вызываются, когда уже создан заказанный набор компонентов с помощью функции SetRequiredComponents.

Например, мы можем заказать получение только тех Entities, у которых есть ACTION_COMPONENT и SOUND_COMPONENT. Передаю это в SetRequiredComponents и — вуаля.

Чтобы понять, как это работает, распишу на примерах, какие у нас есть системы:

  • TransformSystem — система которая отвечает за иерархию трансформаций.
  • SwitchSystem — система которая отвечает за переключения объектов, которые могут быть в нескольких состояниях, как например разрушенное и неразрушенное.
  • LodSystem — система которая отвечает за переключение лодов по расстоянию.
  • ParticleEffectSystem — система которая обновляет эффекты частиц.
  • RenderUpdateSystem — система которая обновляет рендер-объекты из графа сцены.
  • LightUpdateSystem — система которая обновляет источники света из графа сцены.
  • ActionUpdateSystem — система которая обновляет actions (действия).
  • SoundUpdateSystem — система которая обновляет звуки, их позицию и ориентацию.
  • UpdateSystem — система которая вызывает кастомные пользовательские апдейты.
  • StaticOcclusionSystem — система применения статического окклюжена.
  • StaticOcclusionBuildSystem — система построения статического окклюжена.
  • SpeedTreeUpdateSystem — система апдейта деревьев Speed Tree.
  • WindSystem — система расчета ветра.
  • WaveSystem — система расчета колебаний от взырвов.
  • FolliageSystem — система расчета растительности над ландшафтом.

В практически любой системе код выглядит следующим образом:


Системы можно классифицировать по тому как они обрабатывают объекты:

  • Требуется обработка всех объектов, которые находятся в системе:
    • Физика
    • Коллизии
    • Система трансформаций
    • Система actions (действий)
    • Система обработки звуков
    • Система обработки частиц
    • Static Occlusion System
    • Мы сильно повысили FPS, так как с компонентным подходом вещи стали более независимы и мы смогли их по отдельности развязать и оптимизировать.
    • Архитектура стала более простой и понятной.
    • Стало легко расширять движок, почти не ломая соседние системы.
    • Стало меньше багов из серии «сделав что-то c лодами, сломали свитчи», и наоборот
    • Появилась возможность это все распараллеливать на несколько ядер.
    • На текущий момент, уже работаем над тем, чтобы все системы запускать на всех доступных ядрах.

    Соответственно, если есть желание можете заходить и смотреть на нашу имплементацию в деталях.

    Учитывайте тот факт, что все писалось в реальном проекте, и, конечно, это не академическая реализация.

    image

    Эта история началась более трех лет назад. Наша небольшая компания DAVA стала частью Wargaming, и мы начали обдумывать, какие проекты делать дальше. Чтобы напомнить, каким был мобайл три года назад, скажу, что тогда не было ни Clash Of Clans, ни Puzzle & Dragons, ни многих очень известных сегодня проектов. Mid-core тогда только-только начинался. Рынок был в разы меньше сегодняшнего.

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

    Тогда в разработке у нас находилось несколько игр. Одна из них носила рабочее название «Sniper». Основной геймплей-идеей была стрельба в снайперском режиме из стоящего в обороне танка, по другим танкам, которыми управлял AI и которые могли атаковать в ответ.

    В какой-то момент нам показалось, что стоящий танк — это очень скучно, и за неделю мы сделали прототип мультиплеера, где танки уже могли ездить и атаковать друг друга.

    С этого все и началось!

    Когда мы начинали разработку “Снайпера”, то рассматривали технологии, которые тогда были доступны для мобильных платформ. На тот момент Unity был еще на достаточно ранней стадии своего развития: по сути, необходимых нам технологий еще не было.

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

    В итоге, мы решили дорабатывать свой движок!

    Он на тот момент уже использовался в наших предыдущих казуальных проектах. Движок имел достаточно хорошо написанный низкий уровень работы с платформами и поддерживал iOS, PC, Mac, плюс были начаты работы по Android. Было написано много функциональности для создания 2D-игр. То есть, был неплохой UI и много всего для работы с 2D. В нем были первые шаги по 3D-части, так как одна из наших игр была полностью трехмерной.

    Что у нас было в 3D-части движка:

    • Простейший граф сцены.
    • Возможность рисования статических мешей.
    • Возможность рисования анимированных мешей со скелетной анимацией.
    • Экспорт объектов и анимаций из Collada-формата.
    Начало работ

    Началось все с доказательства возможности отрисовать ландшафт на мобильных устройствах: тогда это были iPhone 4 и iPad 1.

    После нескольких дней работы мы получили вполне функциональный динамический ландшафт, который работал довольно сносно, требовал где-то 8MB памяти и давал 60fps на этих устройствах. После этого мы начали полноценную разработку игры.

    Прошло около полугода, и маленький мини-проект превратился в то, чем сейчас является Blitz. Появились совершенно новые требования: MMO, AAA-качество и другие требования, которые движок в его изначальном виде на тот момент уже не мог обеспечить. Но работа кипела полным ходом. Игра работала и работала неплохо. Однако производительность была средней, объектов на картах было мало, и, собственно, было множество других ограничений.

    На этом этапе мы начали понимать, что фундамент, который мы заложили в движок, не выдержит пресса реального проекта.

    Как все работало на тот момент

    Вся отрисовка сцен была основана на простой концепции Scene Graph.

    Основной концепции являлись два класса:

    • Scene — контейнер сцены, внутри которого происходили все действия
    • над сценой.
    • SceneNode — базовый класс узла сцены, от которого наследовались все классы, которые находились в сцене:
    • MeshInstanceNode — класс для отрисовки мешей.
    • LodNode — класс для переключения лодов.
    • SwitchNode — класс для переключения свитч объектов.
    • еще около 15-ти классов наследников SceneNode.
    • Update — функция которая вызывалась для каждого узла, для того чтобы сделать Update-сцены.
    • Draw — функция, которая вызывалась для каждого узла, для того чтобы отрисовать этот узел.
    • Когда количество нодов в уровне достигло 5000, оказалось что просто пройти по всем пустым функциям Update, занимает около 3ms.
    • Аналогичное время уходило на пустые ноды, которым не требовалось Draw.
    • Огромное количество кэш-миссов, так как работа всегда велась с разнотипными данными.
    • Невозможность распараллелить работу на несколько ядер.
    • Изменение кода в базовых классах влияло на всю систему целиком, то есть каждое изменение SceneNode::Update могло сломать что угодно и где угодно. Зависимости становились все сложнее и сложнее, и каждое изменение внутри движка почти гарантированно требовало тестирования всей связанной функциональности.
    • Невозможно было сделать локальное изменение, например, в трансформациях, чтобы не задеть остальные части сцены. Очень часто малейшие изменения в LodNode (узел для переключения лодов), ломали что-то в игре.
    Первые шаги по улучшению ситуации

    Для начала мы решили полечить проблемы с производительностью и сделать это быстро.

    Собственно, сделали мы это, введя дополнительный флаг NEED_UPDATE в каждой ноде. Он определял, нужно ли такой ноде вызывать Update. Это действительно повысило производительность, но создало целый ворох проблем. Фактически код функции Update выглядел вот так:


    Это вернуло нам часть производительности, однако началось много логических проблем там, где их не ждали.

    LodNode, и SwitchNode — ноды, отвечающие, соответственно, за переключение лодов (по расстоянию) и переключение объектов (например, разрушенных и неразрушенных) — начали регулярно ломаться.

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

    Самым первым шагом было заложить архитектуру, которая позволит в перспективе решить все возникающие у нас проблемы.

    • Минимизация зависимости между независимыми подсистемами.
    • Изменения в трансформациях не должны ломать систему лодов, и наоборот
    • Возможность положить код на многоядерность.
    • Чтобы не было функций Update или аналогичных, в которых выполнялся разнородный независимый код. Легкая расширяемость системы новой функциональностью без полного перетестирования старой. Изменения в одних подсистемах не влияет на другие. Максимальная независимость подсистем.
    • Возможность расположить данные линейно в памяти для максимальной производительности.
    Комбинирование компонентного и data-driven-подхода

    Решением этой проблемы стал компонентный подход, комбинированный c data-driven подходом. Дальше по тексту я буду употреблять data-driven-подход, так как не нашел удачного перевода.

    Вообще понимание компонентного подхода у многих людей самое разное. То же — и с data-driven.

    В моем понимании, компонентный подход — это когда некая необходимая функциональность строится на основе независимых компонентов. Самый простой пример — это электроника. Есть чипы, у каждого чипа есть входы и выходы. Если чипы подходят друг к другу, их можно соединить. На базе такого подхода построена вся индустрия электроники. Есть тысячи разных компонентов: соединяя их друг с другом, можно получать совершенно разные вещи.

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

    Что же такое data-driven. В моем понимании, это подход к проектированию программного обеспечения, когда за основу потока выполнения программы берутся данные, а не логика.

    На нашем примере представим следующую иерархию классов:


    Код обхода этой иерархии иерархически выглядит так:


    В данной иерархии C++ наследования мы имеем три различных независимых потока данных:

    Давайте представим, как это должно выглядеть в data-driven подходе. Напишу на псевдокоде, чтобы была понятна идея:


    По сути, мы развернули циклы работы программы, сделав это таким образом, чтобы все отталкивалось от данных.

    Данные в data-driven подходе являются ключевым элементом программы. Логика — лишь механизмы обработки данных.

    Новая архитектура

    В какой-то момент стало понятно, что надо идти в сторону Entity-based подхода к организации сцены, где Entity являлась сущностью, состоящей из многих независимых компонентов. Хотелось, чтобы компоненты были полностью произвольными и легко комбинировались между собой.

    Читая информацию по этой теме, я наткнулся на блог T-Machine.

    • Entity не содержит никакой логики, это просто ID (или указатель).
    • Entity знает только ID компоненты, которые ей принадлежат (или указатель).
    • Компонент — это только данные, то есть. компонент не содержит никакой логики.
    • Система — это код, который умеет обрабатывать определенный набор данных и выдавать на выходе другой набор данных.

    Когда я понял это, в процессе дальнейшего изучения различной информации наткнулся на Artemis Framework и увидел хорошую реализацию этого подхода.
    Исходники тут, если предыдущий линк не работает: Artemis Original Java Source Code

    Если вы разрабатываете на Java, то очень рекомендую посмотреть на него. Очень простой и концептуально правильный Framework. На сегодняшний день он спортирован на кучу языков.

    То, чем является Artemis, сегодня называют ECS (Entity Component System). Вариантов организации сцены на базе Entity, компонентов и data-driven достаточно много, однако мы по итогу пришли к архитектуре ECS. Сложно сказать, насколько это общепринятый термин, однако ECS значит, что есть следующие сущности: Entity, Component, System.

    Самое главное отличие от других подходов это: Обязательное отсутствие логики поведения в компонентах, и отделение кода в системы.

    Этот пункт очень важен в “православном” компонентном подходе. Если нарушить первый принцип, появится очень много соблазнов. Один из первых — сделать наследование компонентов.

    Несмотря на гибкость, заканчивается обычно макаронами.

    image

    Изначально кажется, что при таком подходе можно будет сделать множество компонентов, которые ведут себя похожим образом, но чуть-чуть по-разному. Общие интерфейсы компонентов. В общем, можно опять свалиться в ловушку наследования. Да, это будет чуть лучше, чем классическое наследование, однако постарайтесь не попасть в эту ловушку.

    ECS — более чистый подход, и решает больше проблем.

    Чтобы посмотреть на примере, как это работает в Artemis, можете глянуть вот тут.

    Я на примере покажу, как это работает у нас.

    Главным классом контейнером является Entity. Это класс, который содержит массив компонентов.

    Вторым классом является Component. В нашем случае, это просто данные.

    Вот список компонентов, используемых у нас в движке, на сегодняшний день:


    Третим классом является SceneSystem:


    Функции RegisterEntity, UnregisterEntity вызываются для всех систем в сцене тогда, когда мы добавляем или удаляем Entity из сцены.


    Функции RegisterComponent, UnregisterComponent вызываются для всех систем в сцене, тогда, когда мы добавляем или удаляем Component в Entity в сцене.
    Также для удобства есть еще две функции:


    Эти функции вызываются, когда уже создан заказанный набор компонентов с помощью функции SetRequiredComponents.

    Например, мы можем заказать получение только тех Entities, у которых есть ACTION_COMPONENT и SOUND_COMPONENT. Передаю это в SetRequiredComponents и — вуаля.

    Чтобы понять, как это работает, распишу на примерах, какие у нас есть системы:

    • TransformSystem — система которая отвечает за иерархию трансформаций.
    • SwitchSystem — система которая отвечает за переключения объектов, которые могут быть в нескольких состояниях, как например разрушенное и неразрушенное.
    • LodSystem — система которая отвечает за переключение лодов по расстоянию.
    • ParticleEffectSystem — система которая обновляет эффекты частиц.
    • RenderUpdateSystem — система которая обновляет рендер-объекты из графа сцены.
    • LightUpdateSystem — система которая обновляет источники света из графа сцены.
    • ActionUpdateSystem — система которая обновляет actions (действия).
    • SoundUpdateSystem — система которая обновляет звуки, их позицию и ориентацию.
    • UpdateSystem — система которая вызывает кастомные пользовательские апдейты.
    • StaticOcclusionSystem — система применения статического окклюжена.
    • StaticOcclusionBuildSystem — система построения статического окклюжена.
    • SpeedTreeUpdateSystem — система апдейта деревьев Speed Tree.
    • WindSystem — система расчета ветра.
    • WaveSystem — система расчета колебаний от взырвов.
    • FolliageSystem — система расчета растительности над ландшафтом.

    В практически любой системе код выглядит следующим образом:


    Системы можно классифицировать по тому как они обрабатывают объекты:

    • Требуется обработка всех объектов, которые находятся в системе:
      • Физика
      • Коллизии
      • Система трансформаций
      • Система actions (действий)
      • Система обработки звуков
      • Система обработки частиц
      • Static Occlusion System
      • Мы сильно повысили FPS, так как с компонентным подходом вещи стали более независимы и мы смогли их по отдельности развязать и оптимизировать.
      • Архитектура стала более простой и понятной.
      • Стало легко расширять движок, почти не ломая соседние системы.
      • Стало меньше багов из серии «сделав что-то c лодами, сломали свитчи», и наоборот
      • Появилась возможность это все распараллеливать на несколько ядер.
      • На текущий момент, уже работаем над тем, чтобы все системы запускать на всех доступных ядрах.

      Соответственно, если есть желание можете заходить и смотреть на нашу имплементацию в деталях.

      Учитывайте тот факт, что все писалось в реальном проекте, и, конечно, это не академическая реализация.




      Это скриптовый язык движка.
      А сам движок написан, скорей всего, на С++, т.к. требует набор библиотек Microsoft Visual C++ Redistributable

      Сервер написан на С++ (из вакансии)

      Клиент на питоне(он ругался по питоньи при запуске в линуксе)
      ну и на плюсах, следуя из цитаты.

      возможно использовались и другие ЯП при написании клиента и сервера.



      Сервер написан на С++ (из вакансии)

      Клиент на питоне(он ругался по питоньи при запуске в линуксе)
      ну и на плюсах, следуя из цитаты.

      возможно использовались и другие ЯП при написании клиента и сервера.

      Питон это скриптовый язык, написанный на нем клиент будет работать через опу, да и я не уверен, что питон сам по себе обеспечивает рендеринг. Поэтому используется язык более низкого уровня, и судя по требуемым библиотекам, С++ - это ядро движка. А надстройки пишутся на скриптовом питоне, а интерфейс юзает скалеформ.

      А вот про сервер ничего сказать не могу. Не видел.


      СССР:Тетрарх, T-127, СУ-26, Т-50, Валентайн, Матильда,Т-34, Т-50-2, Черчилль ІІІ, СУ-122-44, КВ-5, Т-54, ИС-4.
      Германия: Bizon, Marder II, T-15, VK 2801, Dicker Max, PzKpfw IV Schmalsturm, PzKpfw V-IV, GW Panther, Jagdpanther, PzKpfw VI Tiger P, Panther M10, Lowe, 8.8 cm PaK 43 Jagdtiger, GW Typ E. Великобритания: Vickers Mk. I, Matilda Black Prince, Comet.
      США: T1 Cunningham, T2lt, M22 Locust, M24 Chaffee, M41, M4A2E4, M12, M26 Pershing, T26E4 SuperPershing, T34, M46 Patton, T110E5. Франция: FCM 36 PaK 40, Somua SAu-40, 105leFH18B2, ELC AMX, AMX 13 F3 AM, AMX 13 90, AMX 50 100, Bat Chatillon 25 t.


      Читайте также: