GameEvent — Система событий
Обзор
GameEvent — система событий, которая позволяет компонентам взаимодействовать без прямых ссылок друг на друга.
Поддерживает от 0 до 10 параметров с полной типобезопасностью.
Базовые события
GameEvent (без параметров)
public class PlayerComponent : Component
{
public GameEvent OnDeath { get; } = new();
public GameEvent OnRevive { get; } = new();
}
// Подписка
var player = entity.GetComponent<PlayerComponent>();
player.OnDeath += () => Console.WriteLine("Игрок умер");
// Вызов
player.OnDeath.Invoke();
// Оператор подписки
player.OnDeath += () => Console.WriteLine("Еще один обработчик");
GameEvent (один параметр)
public class DamageComponent : Component
{
public GameEvent<float> OnDamageReceived { get; } = new();
}
// Подписка
var damage = entity.GetComponent<DamageComponent>();
damage.OnDamageReceived += (amount) =>
Console.WriteLine($"Получен урон: {amount}");
// Вызов
damage.OnDamageReceived.Invoke(25.5f);
GameEvent<T0, T1> (два параметра)
public class CollisionComponent : Component
{
public GameEvent<Entity, string> OnCollisionEnter { get; } = new();
}
// Подписка
var collision = entity.GetComponent<CollisionComponent>();
collision.OnCollisionEnter += (other, tag) =>
Console.WriteLine($"Столкновение с {other.Name} ({tag})");
// Вызов
collision.OnCollisionEnter.Invoke(otherEntity, "Wall");
Все типы GameEvent
Доступны GameEvent<T0> до GameEvent<T0...T13> для 1-14 параметров:
// 1 параметр
public class Event1 : Component
{
public GameEvent<int> OnEvent { get; } = new();
}
// 2 параметра
public class Event2 : Component
{
public GameEvent<int, string> OnEvent { get; } = new();
}
// 3 параметра
public class Event3 : Component
{
public GameEvent<int, string, float> OnEvent { get; } = new();
}
// ... до 10 параметров
// 10 параметров
public class Event10 : Component
{
public GameEvent<int, string, float, bool, int, int, int, int, int, int> OnEvent { get; } = new();
}
Операторы и методы
Использование операторов
var onDamage = new GameEvent<float>();
// Оператор +=
onDamage += (amount) => Console.WriteLine($"Урон: {amount}");
// Оператор -=
onDamage -= (amount) => Console.WriteLine($"Урон: {amount}");
// Invoke()
onDamage.Invoke(50);
Метод Add() и Remove()
var onEvent = new GameEvent<string>();
// Добавить обработчик
onEvent.Add((message) => Console.WriteLine(message));
// Удалить обработчик (если известна функция)
void Handler(string msg) => Console.WriteLine(msg);
onEvent.Remove(Handler);
Цепочка вызовов
var onEvent = new GameEvent<int>();
// Методы Add() и Remove() возвращают этот объект
onEvent
.Add((val) => Console.WriteLine($"Первый: {val}"))
.Add((val) => Console.WriteLine($"Второй: {val}"));
onEvent.Invoke(42);
Сложные примеры
Система урона и исцеления
public class HealthComponent : Component
{
[SerializableField]
public float MaxHealth { get; set; } = 100;
[SerializableField]
public float CurrentHealth { get; set; } = 100;
[SerializableField]
public GameEvent<float> OnHealthChanged { get; } = new();
[SerializableField]
public GameEvent<float, string> OnDamage { get; } = new();
public void TakeDamage(float amount, string source = "Unknown")
{
CurrentHealth = Math.Max(0, CurrentHealth - amount);
OnDamage.Invoke(amount, source);
OnHealthChanged.Invoke(CurrentHealth);
}
public void Heal(float amount)
{
CurrentHealth = Math.Min(MaxHealth, CurrentHealth + amount);
OnHealthChanged.Invoke(CurrentHealth);
}
}
// Использование
var health = entity.GetComponent<HealthComponent>();
health.OnHealthChanged += (newHealth) =>
{
Console.WriteLine($"Здоровье: {newHealth}/{health.MaxHealth}");
};
health.OnDamage += (amount, source) =>
{
Console.WriteLine($"Получен урон {amount} от {source}");
};
health.TakeDamage(25, "Enemy"); // Здоровье: 75/100, Получен урон 25 от Enemy
health.Heal(10); // Здоровье: 85/100
Система инвентаря
public class InventoryComponent : Component
{
[SerializableField]
public GameEvent<string> OnItemAdded { get; } = new();
[SerializableField]
public GameEvent<string> OnItemRemoved { get; } = new();
[SerializableField]
public GameEvent<string, int> OnItemQuantityChanged { get; } = new();
private Dictionary<string, int> _items = new();
public void AddItem(string itemName, int quantity = 1)
{
if (_items.ContainsKey(itemName))
_items[itemName] += quantity;
else
_items[itemName] = quantity;
OnItemAdded.Invoke(itemName);
OnItemQuantityChanged.Invoke(itemName, _items[itemName]);
}
public void RemoveItem(string itemName)
{
if (_items.Remove(itemName))
OnItemRemoved.Invoke(itemName);
}
}
Система достижений
public class AchievementComponent : Component
{
[SerializableField]
public GameEvent<string, string> OnAchievementUnlocked { get; } = new();
[SerializableField]
public GameEvent<string, int> OnProgressUpdated { get; } = new();
}
public class AchievementSystem : System
{
private Dictionary<string, int> _progress = new()
{
{ "Enemies_Killed", 0 },
{ "Distance_Traveled", 0 }
};
public override void OnUpdate(Entity entity, float deltaTime)
{
var achievement = entity.GetComponent<AchievementComponent>();
if (achievement == null) return;
// Проверка достижений
foreach (var (key, value) in _progress)
{
OnProgressUpdated?.Invoke(key, value);
if (key == "Enemies_Killed" && value >= 100)
achievement.OnAchievementUnlocked.Invoke("Killer", "Убийте 100 врагов");
if (key == "Distance_Traveled" && value >= 10000)
achievement.OnAchievementUnlocked.Invoke("Traveler", "Пройдите 10000 м");
}
}
}
Обработка ошибок
public class SafeGameEvent
{
private GameEvent<float> _onEvent = new();
public void Subscribe(Action<float> handler)
{
try
{
_onEvent.Add(handler);
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка подписки: {ex.Message}");
}
}
public void TriggerSafe(float value)
{
try
{
_onEvent.Invoke(value);
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка вызова события: {ex.Message}");
}
}
}
Интеграция с системами
public class InputComponent : Component
{
public GameEvent OnJumpPressed { get; } = new();
public GameEvent OnAttackPressed { get; } = new();
public GameEvent<float> OnMovement { get; } = new();
}
public class MovementSystem : System
{
public override void OnUpdate(Entity entity, float deltaTime)
{
var input = entity.GetComponent<InputComponent>();
var transform = entity.GetComponent<TransformComponent>();
if (input == null || transform == null) return;
// Прыжок при нажатии кнопки
input.OnJumpPressed += () =>
{
var velocity = entity.GetComponent<VelocityComponent>();
if (velocity != null)
velocity.VelocityY = 10;
};
// Движение при вводе
input.OnMovement += (direction) =>
{
transform.X += direction * 100 * deltaTime;
};
}
}
Лучшие практики
✅ Правильно
// События в компонентах
public class MyComponent : Component
{
public GameEvent<int> OnValueChanged { get; } = new();
}
// Слабая связь между компонентами
input.OnAttack += () => weapon.Fire();
// Цепочка событий
health.OnHealthChanged += (h) =>
{
ui.OnHealthUpdated.Invoke(h);
};
❌ Неправильно
// Не вызывайте события в других компонентах
component1.myEvent.Invoke(); // ❌ Событие должно быть приватным для другого класса
// Не игнорируйте исключения в обработчиках
onEvent += (value) => throw new Exception("Ошибка!"); // ❌ Может сломать систему
// Не создавайте циклические зависимости
eventA.Add(() => eventB.Invoke()); // ❌ Может привести к бесконечной петле
eventB.Add(() => eventA.Invoke());
Производительность
- Вызов события — O(n) где n — количество обработчиков
- Подписка — O(1)
- Отписка — O(n)
- Минимизируйте количество подписчиков для часто вызываемых событий
© 2026 Igdrasil Project. Все права защищены.