"ECS навязывает очень определенный подход к архитектуре, который подходит к некоторым видам игр вроде RTS, но плохо подходит ко всему остальному." - по моему мнению, он нормально подходит к совершенно разным проектам, у меня есть опыт использования ECS с разными играми: и с гк, и с шутерами, и с ртс, и с данжн кроулерами. В крупных проектах по типу Overwatch, Minecraft и, насколько я знаю, в играх от Larian тоже используют этот подход для игровой логики, а это не RTS игры.
Pathfinding, ИИ через граф (FSM/BT/DT/GOAP/etc), UI и другие иерархии - это все можно реализовать отдельными сервисами с нужным API и использовать его внутри систем.
Не совсем понимаю, что имеется в виду под комплексной логикой взаимодействия. Ничего не развалится, если правильно называть компоненты и не допускать логических ошибок. А то, что могут быть баги... ну, баги везде могут быть, что с ECS, что без. В любом случае он дает хорошую модульность и возможность проще писать игровую логику.
Что именно непонятно? По-моему, все просто. Весь список систем, выполняющихся друг за другом, можно просмотреть в стартап-классе Game. Они друг о друге не знают, коммуникация между разными частями проекта происходит через данные в компонентах и сущности. Как раз эта слабая связанность дает модульность и в итоге легче рефакторить код, изменяя или добавляя новые механики. Также проще смешивать разное поведение с помощью разных наборов компонентов. События тоже проще обрабатывать – просто создаешь сущность с нужным компонентом и ловишь в системе. Сами системы можно легко разделить на несколько или наоборот объединить.
Все, что плохо ложится на ECS (иерархические структуры, например), можно реализовать через сервисы и инжектнуть в системы.
Насчет бойлерплейта - зависит от фреймворка. В LeoECS Lite с расширениями не так уж и много.
Пул компонентов дает возможность добавлять компоненты к сущности или удалять их. Рекомендую прочитать README в репозитории https://github.com/Leopotam/ecslite
Ну инжект в системы внедряет все данные сразу во все экземпляры систем.
Насчет CellView, там атрибуты под дефайном и будут работать только в редакторе, так что по идее норм. Вообще, скорее всего стоит разделить компонент на два: один отвечает за снап, другой за отрисовку клеток, так как магнитить к сетке нужно будет, скорее всего, не только клетки.
Чтобы ловить ивенты коллизии, по-любому нужен будет MonoBehaviour. Самый простой вариант - это создать тонкий монобех, повесить его на GameObject, дальше инжектнуть туда каким-то образом ссылку на EcsWorld (допустим, из инит-системы) и при коллизии создавать сущность с компонентом-ивентом.
Выглядит как-то так (советую посмотреть другие примеры в репозитории LeoECS тоже).
Можно посмотреть вот этот экстеншн, с ним не придется каждый раз создавать MonoBehaviour.
Чтобы "поймать" события штатной физики Unity, придется создавать тонкий MonoBehaviour класс и вешать его на GameObject. Внутри будет метод с нужным событием (OnTriggerEnter, например), а в нем уже как-то прокидываются события о физике в ECS. Например, метод может создать новую отдельную сущность с компонентом TriggerEnter и какими-то данными внутри, а может просто добавит компонент к уже существующей сущности. Прокинуть данные о ECS в MonoBehaviour можно тоже по-разному. Можно вручную заполнить поля в Init-системе, можно поместить их в сервис-локатор или синглтон.
Советую посмотреть вот это расширение. Я написал его, чтобы не приходилось каждый раз создавать MonoBehaviour класс для событий физики.
А еще можно посмотреть вот этот проект, там я прокидываю события физики в ECS без расширения.
А какая разница? Просто добавьте корректирующую систему, которая внесет изменения посередине. Сетевые пакеты - еще одна система ввода, которая сгенерирует новый ивент или поправит данные напрямую.
В этом и смысл работать ивентами - они могут возникать откуда угодно: хоть с клавомыши, хоть с тачпада, хоть от ИИ, хоть по сети.
Действительно, наличие подобных компонентов (своего рода "God class"-ов из ООП), которые будут только разрастаться и разрастаться, может лишить разработчика гибкости, которую дает паттерн ECS.
Но я решил оставить его. Пока что нет необходимости разбивать компонент на несколько. Да, скорее всего, она появится позже на практике (в последующих частях, например), и вот тогда мы сможем без проблем это исправить благодаря еще одному преимуществу ECS - быстрому рефакторингу. Мы можем легко склеивать несколько компонентов/систем в одно целое или наоборот разбивать на несколько более атомарных частиц.
Помещать, например, поле Health в компонент Player действительно не стоит, так как это свойство, которым обладают многие сущности в игре. Логика, которая связана с этим компонентом, будет работать для всех одинаково, а поэтому имеет смысл вынести Health в отдельный компонент.
Все потому что игра - это не только код, но и контент
Здесь еще много движков
600 полезных ссылок для движкописателей / Графика / Форум / Программирование игр / GameDev.ru — Разработка игр
Действительно, в AttackManager аллоцируются структуры, тогда да, согласен.
Что если сделать AttackData структурой, чтобы избежать аллокаций в AttackManager, а значения полей получать через геттер в интерфейсе?
"ECS навязывает очень определенный подход к архитектуре, который подходит к некоторым видам игр вроде RTS, но плохо подходит ко всему остальному." - по моему мнению, он нормально подходит к совершенно разным проектам, у меня есть опыт использования ECS с разными играми: и с гк, и с шутерами, и с ртс, и с данжн кроулерами. В крупных проектах по типу Overwatch, Minecraft и, насколько я знаю, в играх от Larian тоже используют этот подход для игровой логики, а это не RTS игры.
Pathfinding, ИИ через граф (FSM/BT/DT/GOAP/etc), UI и другие иерархии - это все можно реализовать отдельными сервисами с нужным API и использовать его внутри систем.
Не совсем понимаю, что имеется в виду под комплексной логикой взаимодействия. Ничего не развалится, если правильно называть компоненты и не допускать логических ошибок. А то, что могут быть баги... ну, баги везде могут быть, что с ECS, что без. В любом случае он дает хорошую модульность и возможность проще писать игровую логику.
Что именно непонятно? По-моему, все просто. Весь список систем, выполняющихся друг за другом, можно просмотреть в стартап-классе Game. Они друг о друге не знают, коммуникация между разными частями проекта происходит через данные в компонентах и сущности. Как раз эта слабая связанность дает модульность и в итоге легче рефакторить код, изменяя или добавляя новые механики. Также проще смешивать разное поведение с помощью разных наборов компонентов. События тоже проще обрабатывать – просто создаешь сущность с нужным компонентом и ловишь в системе. Сами системы можно легко разделить на несколько или наоборот объединить.
Все, что плохо ложится на ECS (иерархические структуры, например), можно реализовать через сервисы и инжектнуть в системы.
Насчет бойлерплейта - зависит от фреймворка. В LeoECS Lite с расширениями не так уж и много.
Спасибо. Да, может быть.
Почему? Наоборот, все становится куда проще благодаря слабой связанности и модульности.
Добрый вечер. Рад, что понравилось.
Пул компонентов дает возможность добавлять компоненты к сущности или удалять их. Рекомендую прочитать README в репозитории https://github.com/Leopotam/ecslite
Спасибо.
Ну инжект в системы внедряет все данные сразу во все экземпляры систем.
Насчет CellView, там атрибуты под дефайном и будут работать только в редакторе, так что по идее норм. Вообще, скорее всего стоит разделить компонент на два: один отвечает за снап, другой за отрисовку клеток, так как магнитить к сетке нужно будет, скорее всего, не только клетки.
Еще стоит включить инстансинг у террейна, если не включен.
Чтобы ловить ивенты коллизии, по-любому нужен будет MonoBehaviour. Самый простой вариант - это создать тонкий монобех, повесить его на GameObject, дальше инжектнуть туда каким-то образом ссылку на EcsWorld (допустим, из инит-системы) и при коллизии создавать сущность с компонентом-ивентом.
Выглядит как-то так (советую посмотреть другие примеры в репозитории LeoECS тоже).
Можно посмотреть вот этот экстеншн, с ним не придется каждый раз создавать MonoBehaviour.
Рад стараться! :)
Хорошая статья, пиши еще
Чтобы "поймать" события штатной физики Unity, придется создавать тонкий MonoBehaviour класс и вешать его на GameObject. Внутри будет метод с нужным событием (OnTriggerEnter, например), а в нем уже как-то прокидываются события о физике в ECS. Например, метод может создать новую отдельную сущность с компонентом TriggerEnter и какими-то данными внутри, а может просто добавит компонент к уже существующей сущности. Прокинуть данные о ECS в MonoBehaviour можно тоже по-разному. Можно вручную заполнить поля в Init-системе, можно поместить их в сервис-локатор или синглтон.
Советую посмотреть вот это расширение. Я написал его, чтобы не приходилось каждый раз создавать MonoBehaviour класс для событий физики.
А еще можно посмотреть вот этот проект, там я прокидываю события физики в ECS без расширения.
Рад стараться!
Рад, что понравилось :)
Да, продолжение будет.
А какая разница? Просто добавьте корректирующую систему, которая внесет изменения посередине. Сетевые пакеты - еще одна система ввода, которая сгенерирует новый ивент или поправит данные напрямую.
В этом и смысл работать ивентами - они могут возникать откуда угодно: хоть с клавомыши, хоть с тачпада, хоть от ИИ, хоть по сети.
На самом деле, к этому быстро привыкаешь. Особенно учитывая то, что в большинстве случаев сами фильтры небольшие.
Можно посмотреть LeoEcsLite - здесь с этим проблем нет и четко видно, какой компонент ты получаешь.
Действительно, наличие подобных компонентов (своего рода "God class"-ов из ООП), которые будут только разрастаться и разрастаться, может лишить разработчика гибкости, которую дает паттерн ECS.
Но я решил оставить его. Пока что нет необходимости разбивать компонент на несколько. Да, скорее всего, она появится позже на практике (в последующих частях, например), и вот тогда мы сможем без проблем это исправить благодаря еще одному преимуществу ECS - быстрому рефакторингу. Мы можем легко склеивать несколько компонентов/систем в одно целое или наоборот разбивать на несколько более атомарных частиц.
Помещать, например, поле Health в компонент Player действительно не стоит, так как это свойство, которым обладают многие сущности в игре. Логика, которая связана с этим компонентом, будет работать для всех одинаково, а поэтому имеет смысл вынести Health в отдельный компонент.