Спасибо большое за развёрнутый и честный фидбек! Я действительно ценю, что вы нашли время так подробно разобрать Atomic.
Вы затронули несколько ключевых тем — попробую прокомментировать их коротко по сути.
1. Про сходство с ECS
Да, вы правы. Atomic действительно унаследовал часть идей ECS, но основная цель — не оптимизация под DOD, а упрощение разработки игры в виде возможности быстро собирать механики из атомарных элементов без проектирования ООП иерархий.
Можно сказать, что это “ECS без конвейера”, где внимание сосредоточено не на кешах и батчах, а на композиции данных и логики.
2. Разделение данных и логики
Вы верно заметили, что разделение данных и логики может повысить когнитивную сложность при чтении кода, особенно если механика разнесена по нескольким behaviour’ам.
Atomic решает это за счёт единообразной структуры ESB (Entity-State-Behaviour) и кодогенерации, которая делает доступ к данным явным и типобезопасным. Вместо поиска по строковым ключам используем entity.GetMoveSpeed(), а вместо “магических” связей — декларативные инсталлеры.
Это не отменяет сложности — но снижает её до уровня «читаемого procedural code».
3. Boilerplate и Unity-интеграция
Да, количество декларативного кода выше, чем в MonoBehaviour-подходе, и это осознанный компромисс. Atomic — это не «ещё один DI-фреймворк», а средство разработки игры в едином паттерне. Boilerplate компенсируется единообразием. Все сущности, UI, контексты и системы собираются одинаково, что в долгосрочной перспективе уменьшает энтропию проекта.
4. Производительность
Вы абсолютно правы, что каждое обращение к данным сущности дороже, чем прямой доступ к полю. Вместе с тем, большинство атомарных сущностей разрешают ссылки один раз при Init(), а дальше работают с ними напрямую без повторных обращений и боксинга.
Сравнение с чистыми C#-объектами справедливо, так как Atomic не претендует на скорость ECS, его цель — структурная производительность, когда добавление механик не замедляет команду разработки.
5. Частичный отказ от ООП
Тут согласен. Фреймворк сознательно ломает классическую инкапсуляцию. Это цена за “атомарность” — возможность динамически собирать и разбирать сущность без знания её внутренней структуры. Но при этом Atomic сохраняет полиморфизм и строгую типизацию интерфейсов, что отличает его от “анемичных объектов” в procedural-подходе.
Резюме
В итоге вы правы: Atomic — не “универсальное решение”, но и не попытка заменить ООП или ECS. Это экспериментальная архитектура для быстрой сборки игрового поведения и изоляции симуляции от Unity. Кому-то она покажется лишней абстракцией, а кому-то удобным слоем над чистым C#. Но ваш отзыв помог чётче осознать эти границы. Спасибо за это!
IVariable — это интерфейс атомарного элемента, он сам по себе не генерируется. Кодогенерация создаёт ключи (accessors), которые возвращают конкретный тип данных, связанный с сущностью. То есть экстеншены не плодятся для всех IVariable<int> подряд, а жёстко типизированы под конкретные атомы.
За счёт этого список методов остаётся компактным и понятным — ты сразу видишь только релевантные поля и поведение, без дублирования по типам.
Скорее ООП с элементами ECS. Подход гибридный. Он строго разделяет данные и логику, но при этом поведение крепится к каждой сущности и обрабатывает объекты вместо структур. Такой подход позволяет делать данные полиморфными и управлять индивидуально сущностью в отличие от ECS
Да, всё верно. У каждой сущности может быть свой набор состояний и поведений. Если в игре, например, есть пять персонажей, то у каждого будет своя механика перемещения. Такой подход удобен тем, что, скажем, если нужно наложить эффект яда, ты просто добавляешь ещё одно поведение конкретному персонажу — без изменения остальной логики.
Это да, но аббревиатура KISS расшифровывается: Keep It Simple Stupid. Последнее слово как-раз имеется ввиду, что ты можешь сделать решение более топорное, возможно даже немного накостылять и захардкодить
В идеале так и нужно писать простой код, соблюдая SOLID. В статье речь идет о том, что можно уйти в крайности:
Простая система, может превратиться в God Object
Супер гибкая система, может превратиться в спагетти.
Поэтому в статье объясняется, что нужно держать баланс. Нельзя просто следовать SOLID. В тоже время нужно контролировать, чтобы Stupid классы не превращались в God Object'ы.
Такое сработает, если разработчик уже решал похожую задачу ранее. Если для специалиста задача новая, то легко угодить в ловушку двух зайцев. Думать одновременно о том, чтобы код и работал и был причесан, может очень сильно тормозить процесс разработки (по себе знаю). В результате будет потрачено больше времени, сил и энергии. Поэтому рабочий код и чистый код — это разного рода задачи, и лучше рефакторить после проверки работоспособности кода.
Да, делегат можно создать без обертки, но вот получить ссылку в чистом виде на событие OnDamageTaken не получится, так как язык C# это не позволяет. Модификатор unsafe действительно позволяет работать с указателями, но обычно он используется в блоке метода. Но передать указатель на поле класса не получится
public unsafe class A {
public int health;
public unsafe void Do()
{
var b = new B(&this.health) //Нельзя!!!
}
}
Приведенный класс Health написан хорошо, так как соблюдает принцип ед. ответственности и является автономным (Open-Closed Principle). Но поскольку он совмещает данные и логику, мб проблематично добавлять новые механики, которые будут пересекаться с механикой здоровья. Например, если мы захотим сделать броню, которая будет поглощать часть урона, то придется переписывать класс Health, поэтому рекомендую отделить данные от логики, например так, без использования AtomicVariable<T> и AtomicEvent:
//Данные:
public sealed class HealthData {
public Action<int> TakeDamageRequest;
public Action<int> TakeDamageEvent;
public Action DeathEvent; //Тут можно и свои делегаты
public int health;
}
//Логика:
public sealed class TakeDamageMechanics {
private HealthData data;
public TakeDamageMechanics(HealthData data)
{
this.data = data;
}
public void OnEnable()
{
this.data.TakeDamageRequest += OnTakeDamage;
}
public void OnDisable()
{
this.data.TakeDamageRequest -= OnTakeDamage;
}
private void OnTakeDamage(int damage)
{
if (this.data.health <= 0)
{
return;
}
this.data.health -= damage;
this.data.OnDamageTaken?.Invoke(damage);
if (this.data.health <= 0) {
this.data.OnDead?.Invoke();
}
}
}
Спасибо большое за развёрнутый и честный фидбек! Я действительно ценю, что вы нашли время так подробно разобрать Atomic.
Вы затронули несколько ключевых тем — попробую прокомментировать их коротко по сути.
1. Про сходство с ECS
Да, вы правы. Atomic действительно унаследовал часть идей ECS, но основная цель — не оптимизация под DOD, а упрощение разработки игры в виде возможности быстро собирать механики из атомарных элементов без проектирования ООП иерархий.
Можно сказать, что это “ECS без конвейера”, где внимание сосредоточено не на кешах и батчах, а на композиции данных и логики.
2. Разделение данных и логики
Вы верно заметили, что разделение данных и логики может повысить когнитивную сложность при чтении кода, особенно если механика разнесена по нескольким behaviour’ам.
Atomic решает это за счёт единообразной структуры ESB (Entity-State-Behaviour) и кодогенерации, которая делает доступ к данным явным и типобезопасным. Вместо поиска по строковым ключам используем
entity.GetMoveSpeed(), а вместо “магических” связей — декларативные инсталлеры.Это не отменяет сложности — но снижает её до уровня «читаемого procedural code».
3. Boilerplate и Unity-интеграция
Да, количество декларативного кода выше, чем в MonoBehaviour-подходе, и это осознанный компромисс. Atomic — это не «ещё один DI-фреймворк», а средство разработки игры в едином паттерне. Boilerplate компенсируется единообразием. Все сущности, UI, контексты и системы собираются одинаково, что в долгосрочной перспективе уменьшает энтропию проекта.
4. Производительность
Вы абсолютно правы, что каждое обращение к данным сущности дороже, чем прямой доступ к полю. Вместе с тем, большинство атомарных сущностей разрешают ссылки один раз при
Init(), а дальше работают с ними напрямую без повторных обращений и боксинга.Сравнение с чистыми C#-объектами справедливо, так как Atomic не претендует на скорость ECS, его цель — структурная производительность, когда добавление механик не замедляет команду разработки.
5. Частичный отказ от ООП
Тут согласен. Фреймворк сознательно ломает классическую инкапсуляцию. Это цена за “атомарность” — возможность динамически собирать и разбирать сущность без знания её внутренней структуры. Но при этом Atomic сохраняет полиморфизм и строгую типизацию интерфейсов, что отличает его от “анемичных объектов” в procedural-подходе.
Резюме
В итоге вы правы: Atomic — не “универсальное решение”, но и не попытка заменить ООП или ECS. Это экспериментальная архитектура для быстрой сборки игрового поведения и изоляции симуляции от Unity. Кому-то она покажется лишней абстракцией, а кому-то удобным слоем над чистым C#. Но ваш отзыв помог чётче осознать эти границы. Спасибо за это!
IVariable — это интерфейс атомарного элемента, он сам по себе не генерируется.
Кодогенерация создаёт ключи (accessors), которые возвращают конкретный тип данных, связанный с сущностью.
То есть экстеншены не плодятся для всех
IVariable<int>подряд, а жёстко типизированы под конкретные атомы.За счёт этого список методов остаётся компактным и понятным — ты сразу видишь только релевантные поля и поведение, без дублирования по типам.
Подробности можно посмотреть в статье, где разбирается процесс кодогенерации: https://github.com/StarKRE22/Atomic/blob/main/Docs/Entities/EntityAPI/Manual.md.
Преимущество индивидуального поведения заключается в возможности использовать реактивное программирование и слушать изменения данных через подписку.
Скорее ООП с элементами ECS. Подход гибридный. Он строго разделяет данные и логику, но при этом поведение крепится к каждой сущности и обрабатывает объекты вместо структур. Такой подход позволяет делать данные полиморфными и управлять индивидуально сущностью в отличие от ECS
Да, всё верно. У каждой сущности может быть свой набор состояний и поведений. Если в игре, например, есть пять персонажей, то у каждого будет своя механика перемещения. Такой подход удобен тем, что, скажем, если нужно наложить эффект яда, ты просто добавляешь ещё одно поведение конкретному персонажу — без изменения остальной логики.
Это да, но аббревиатура KISS расшифровывается: Keep It Simple Stupid. Последнее слово как-раз имеется ввиду, что ты можешь сделать решение более топорное, возможно даже немного накостылять и захардкодить
В идеале так и нужно писать простой код, соблюдая SOLID. В статье речь идет о том, что можно уйти в крайности:
Простая система, может превратиться в God Object
Супер гибкая система, может превратиться в спагетти.
Поэтому в статье объясняется, что нужно держать баланс. Нельзя просто следовать SOLID. В тоже время нужно контролировать, чтобы Stupid классы не превращались в God Object'ы.
Такое сработает, если разработчик уже решал похожую задачу ранее. Если для специалиста задача новая, то легко угодить в ловушку двух зайцев. Думать одновременно о том, чтобы код и работал и был причесан, может очень сильно тормозить процесс разработки (по себе знаю). В результате будет потрачено больше времени, сил и энергии. Поэтому рабочий код и чистый код — это разного рода задачи, и лучше рефакторить после проверки работоспособности кода.
?
Главное — не сдохнуть на этом проекте ?
Веду разработку на канале: https://www.youtube.com@CodeCraftUnityEdition
Да, я оч хочу добавить механику командиров и механику различных активностей на карте)
Если говорить честно: и да, и нет. Для более сложных задач сначала писались тесты, а потом код, для более простых задач — сначала код, потом тесты)
Честно, не понял про способность прогнознозировать
Зов души, мечта детства)
Огонь, спасибо большое! ?
Очень полезная информация! ❤️
)))
Благодарю за обратную связь, сам только на 5-й год допер :)
Да, делегат можно создать без обертки, но вот получить ссылку в чистом виде на событие OnDamageTaken не получится, так как язык C# это не позволяет. Модификатор unsafe действительно позволяет работать с указателями, но обычно он используется в блоке метода. Но передать указатель на поле класса не получится
Приведенный класс Health написан хорошо, так как соблюдает принцип ед. ответственности и является автономным (Open-Closed Principle). Но поскольку он совмещает данные и логику, мб проблематично добавлять новые механики, которые будут пересекаться с механикой здоровья. Например, если мы захотим сделать броню, которая будет поглощать часть урона, то придется переписывать класс Health, поэтому рекомендую отделить данные от логики, например так, без использования AtomicVariable<T> и AtomicEvent: