IgdrasilECS — Entity Component System

Обзор

IgdrasilECS — модуль, реализующий архитектурный паттерн Entity Component System (ECS) для игрового движка Igdrasil.

ECS разделяет игровую логику на три основные части:


Архитектура ECS

Преимущества подхода

Гибкость — легко добавлять/удалять компоненты без изменения кода сущности
Переиспользуемость — компоненты и системы работают с любыми сущностями
Производительность — данные организованы эффективно для процессинга
Масштабируемость — легко управлять большим числом сущностей

Классическая иерархия

Entity (Сущность)
├── Component A (Данные)
├── Component B (Данные)
└── Component C (Данные)

System X (Логика)
├── Обрабатывает Entity1
├── Обрабатывает Entity2
└── Обрабатывает Entity3

Компоненты

Component — базовый класс для всех компонентов. Содержит только данные и состояние.

Создание пользовательского компонента

public class TransformComponent : Component
{
    [SerializableField]
    public float X { get; set; }
    
    [SerializableField]
    public float Y { get; set; }
    
    [SerializableField]
    public float Rotation { get; set; }
}

public class HealthComponent : Component
{
    [SerializableField]
    public float MaxHealth { get; set; }
    
    [SerializableField]
    public float CurrentHealth { get; set; }
    
    public bool IsAlive => CurrentHealth > 0;
}

public class VelocityComponent : Component
{
    [SerializableField]
    public float VelocityX { get; set; }
    
    [SerializableField]
    public float VelocityY { get; set; }
}

События компонентов

Каждый компонент имеет событие OnChange, которое вызывается при изменении:

var healthComponent = new HealthComponent { MaxHealth = 100, CurrentHealth = 100 };

healthComponent.OnChange += (component) => 
{
    Console.WriteLine("Компонент здоровья изменился!");
};

healthComponent.CurrentHealth = 50;
healthComponent.OnChange.Invoke();

Сущности

Entity — контейнер для компонентов. Управляет добавлением, удалением и поиском компонентов.

Создание и управление сущностью

var player = new Entity 
{ 
    Name = "Player1",
    IsActive = true
};

// Добавить компоненты
var transform = new TransformComponent { X = 0, Y = 0, Rotation = 0 };
var health = new HealthComponent { MaxHealth = 100, CurrentHealth = 100 };
var velocity = new VelocityComponent { VelocityX = 5, VelocityY = 0 };

player.AddComponent(transform);
player.AddComponent(health);
player.AddComponent(velocity);

// Или с тегом для быстрого доступа
player.AddComponent("MainHealth", health);

Получение компонентов

// Получить все компоненты типа
var transforms = player.GetComponents<TransformComponent>();

// Получить первый компонент типа
var firstTransform = player.GetComponent<TransformComponent>();

// Получить компонент по тегу
var healthByTag = player.GetComponent("MainHealth");

// Проверить наличие компонента
if (player.HasComponent<HealthComponent>())
{
    Console.WriteLine("У сущности есть компонент здоровья");
}

Удаление и ограничения

// Удалить компонент
player.RemoveComponent(transform);

// Очистить все компоненты
player.Clear();

// Ограничение количества компонентов одного типа
[LimitCount(1)]  // Максимум 1 компонент этого типа
public class UniqueComponent : Component { }

События сущности

player.OnComponentAdded += (component) => 
{
    Console.WriteLine($"Компонент {component.GetType().Name} добавлен");
};

player.OnComponentRemoved += (component) => 
{
    Console.WriteLine($"Компонент {component.GetType().Name} удален");
};

Системы

System — контейнер для логики обработки компонентов сущностей.

Создание пользовательской системы

public class PhysicsSystem : System
{
    public override void OnStart(Entity entity)
    {
        Console.WriteLine($"Система физики инициализирована для {entity.Name}");
    }
    
    public override void OnUpdate(Entity entity, float deltaTime)
    {
        var transform = entity.GetComponent<TransformComponent>();
        var velocity = entity.GetComponent<VelocityComponent>();
        
        if (transform == null || velocity == null)
            return;
        
        // Обновить позицию на основе скорости
        transform.X += velocity.VelocityX * deltaTime;
        transform.Y += velocity.VelocityY * deltaTime;
    }
    
    public override void OnEnd(Entity entity)
    {
        Console.WriteLine($"Система физики завершена для {entity.Name}");
    }
}

public class HealthSystem : System
{
    public override void OnUpdate(Entity entity, float deltaTime)
    {
        var health = entity.GetComponent<HealthComponent>();
        if (health == null) return;
        
        if (!health.IsAlive)
        {
            Console.WriteLine($"{entity.Name} умер!");
            entity.IsActive = false;
        }
    }
}

Системные события

public class GameSystem : System
{
    public override void OnStartScene()
    {
        Console.WriteLine("Сцена начата");
        // Инициализация на уровне сцены
    }
    
    public override void OnUpdateScene(float deltaTime)
    {
        Console.WriteLine($"Обновление сцены, прошло {deltaTime}s");
        // Логика на уровне сцены
    }
    
    public override void OnEndScene()
    {
        Console.WriteLine("Сцена завершена");
        // Очистка ресурсов сцены
    }
}

События (GameEvent)

GameEvent — система событий для безопасной передачи данных между компонентами и системами.

События без параметров

public class PlayerInputComponent : Component
{
    [SerializableField]
    public GameEvent OnJump { get; } = new();
    
    [SerializableField]
    public GameEvent OnAttack { get; } = new();
}

// Прослушивание события
var input = entity.GetComponent<PlayerInputComponent>();
input.OnJump += () => Console.WriteLine("Игрок прыгнул!");

// Вызов события
input.OnJump.Invoke();

События с параметрами

public class DamageComponent : Component
{
    [SerializableField]
    public GameEvent<float> OnDamage { get; } = new();
    
    [SerializableField]
    public GameEvent<string> OnStatusChanged { get; } = new();
}

// Прослушивание
var damage = entity.GetComponent<DamageComponent>();
damage.OnDamage += (amount) => Console.WriteLine($"Урон: {amount}");
damage.OnStatusChanged += (status) => Console.WriteLine($"Статус: {status}");

// Вызов с параметрами
damage.OnDamage.Invoke(25.5f);
damage.OnStatusChanged.Invoke("Отравлен");

Использование операторов

var onEvent = new GameEvent<int>();

// Добавить обработчик
onEvent += (value) => Console.WriteLine($"Значение: {value}");

// Удалить обработчик
onEvent -= (value) => Console.WriteLine($"Значение: {value}");

onEvent.Invoke(42);

Практические примеры

Пример 1: Простой игровой персонаж

// Компоненты
var player = new Entity { Name = "Player" };
player.AddComponent(new TransformComponent { X = 10, Y = 20 });
player.AddComponent(new HealthComponent { MaxHealth = 100, CurrentHealth = 100 });
player.AddComponent(new VelocityComponent { VelocityX = 2, VelocityY = 0 });

// Системы
var physicsSystem = new PhysicsSystem();
var healthSystem = new HealthSystem();

// Обновление
physicsSystem.OnUpdate(player, 0.016f);  // 60 FPS
healthSystem.OnUpdate(player, 0.016f);

Пример 2: Система боевых действий

public class CombatComponent : Component
{
    [SerializableField]
    public float Attack { get; set; }
    
    [SerializableField]
    public GameEvent<float> OnAttack { get; } = new();
}

public class CombatSystem : System
{
    public override void OnUpdate(Entity entity, float deltaTime)
    {
        var combat = entity.GetComponent<CombatComponent>();
        var health = entity.GetComponent<HealthComponent>();
        
        if (combat == null || health == null) return;
        
        // Простая логика атаки
        if (InputSystem.IsAttackPressed)
        {
            combat.OnAttack.Invoke(combat.Attack);
            health.CurrentHealth -= 5;  // Отдача
        }
    }
}

Пример 3: Система с множественными компонентами

public class InventoryComponent : Component
{
    [SerializableField]
    public List<string> Items { get; set; } = new();
    
    [SerializableField]
    public GameEvent<string> OnItemAdded { get; } = new();
}

var player = new Entity { Name = "Player" };
var inventory = new InventoryComponent();

inventory.OnItemAdded += (item) => 
{
    Console.WriteLine($"Получен предмет: {item}");
};

player.AddComponent(inventory);

// Добавить предмет
inventory.Items.Add("Меч");
inventory.OnItemAdded.Invoke("Меч");

Жизненный цикл ECS

Создание сущности
    ↓
Добавление компонентов
    ↓
OnStart системы
    ↓
Цикл обновления:
├─ OnUpdate систем
├─ Обработка событий
├─ Изменение состояния
└─ Повтор
    ↓
OnEnd системы
    ↓
Удаление сущности

Лучшие практики

✅ Правильно

// Компоненты содержат только данные
public class PositionComponent : Component
{
    public float X { get; set; }
    public float Y { get; set; }
}

// Системы содержат логику
public class MovementSystem : System
{
    public override void OnUpdate(Entity entity, float deltaTime)
    {
        var pos = entity.GetComponent<PositionComponent>();
        var vel = entity.GetComponent<VelocityComponent>();
        
        if (pos != null && vel != null)
        {
            pos.X += vel.VelocityX * deltaTime;
            pos.Y += vel.VelocityY * deltaTime;
        }
    }
}

❌ Неправильно

// Не смешивайте логику и данные в компоненте
public class BadComponent : Component
{
    public void Update() { }  // ❌ Логика в компоненте
}

// Не обращайтесь напрямую к другим сущностям без события
public class BadSystem : System
{
    public override void OnUpdate(Entity entity, float deltaTime)
    {
        // ❌ Жесткая связь между сущностями
        entity.GetComponent<HealthComponent>().CurrentHealth = 0;
    }
}

Интеграция с AssetBundle

Entity и Component поддерживают сериализацию в AssetBundle:

var bundle = new AssetBundle();
var player = new Entity { Name = "Player" };
player.AddComponent(new HealthComponent { MaxHealth = 100, CurrentHealth = 100 });

bundle.Serialize(player);

// Сохранить
var serializer = new AssetBundleJsonSerializer(prettyPrint: true);
byte[] data = serializer.Serialize(bundle);
await File.WriteAllBytesAsync("player.json", data);

Проект: Igdrasil Engine
Автор: Alexander Izmailov
Собственность: Igdrasil Project
Версия: 1.0
Лицензия: Proprietary License

© 2026 Alexander Izmailov. Все права защищены.
Этот программный продукт является собственностью студии Igdrasil Project.