Обновить
7
0
Игорь Гулькин@StarKRE

Senior Game Developer

Отправить сообщение

Спасибо большое за развёрнутый и честный фидбек! Я действительно ценю, что вы нашли время так подробно разобрать 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'ы.

Такое сработает, если разработчик уже решал похожую задачу ранее. Если для специалиста задача новая, то легко угодить в ловушку двух зайцев. Думать одновременно о том, чтобы код и работал и был причесан, может очень сильно тормозить процесс разработки (по себе знаю). В результате будет потрачено больше времени, сил и энергии. Поэтому рабочий код и чистый код — это разного рода задачи, и лучше рефакторить после проверки работоспособности кода.

Главное — не сдохнуть на этом проекте ?

Да, я оч хочу добавить механику командиров и механику различных активностей на карте)

Если говорить честно: и да, и нет. Для более сложных задач сначала писались тесты, а потом код, для более простых задач — сначала код, потом тесты)

Честно, не понял про способность прогнознозировать

Зов души, мечта детства)

Огонь, спасибо большое! ?

Очень полезная информация! ❤️

Благодарю за обратную связь, сам только на 5-й год допер :)

Да, делегат можно создать без обертки, но вот получить ссылку в чистом виде на событие 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();
      }
    }
}

Информация

В рейтинге
Не участвует
Откуда
Рубцовск, Алтайский край, Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Разработчик игр, Дизайнер игр
Ведущий
C#
Unity3d
Разработка игр