IgdrasilECS — Entity Component System
Обзор
IgdrasilECS — модуль, реализующий архитектурный паттерн Entity Component System (ECS) для игрового движка Igdrasil.
ECS разделяет игровую логику на три основные части:
- Entity (Сущность) — контейнер для компонентов
- Component (Компонент) — хранилище данных
- System (Система) — обработчик логики
Архитектура 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.