Связать системы с событиями
Используя шаблон Entity-Component-System, я хочу связать некоторые системы с событиями. Поэтому некоторые системы не должны работать в цикле, они должны работать только по требованию.
Учитывая пример системы Здоровья, система Смерти должна запускаться только тогда, когда компонент получает здоровье ниже 1.
Я думал о наличии двух типов систем. Первый тип - это периодическая система. Это выполняется один раз за кадр, например, система рендеринга или перемещения. Другой тип - это система, основанная на событиях. Как упоминалось ранее, связь между здоровьем и смертью.
Сначала я создал базовый интерфейс, используемый обоими типами систем.
internal interface ISystem
{
List<Guid> EntityCache { get; } // Only relevant entities get stored in there
ComponentRequirements ComponentRequirements { get; } // the required components for this system
void InitComponentRequirements();
void InitComponentPools(EntityManager entityManager);
void UpdateCacheEntities(); // update all entities from the cache
void UpdateCacheEntity(Guid cacheEntityId); // update a single entity from the cache
}
Далее я создал интерфейсы
internal interface IReactiveSystem : ISystem
{
// event based
}
а также
internal interface IPeriodicSystem : ISystem
{
// runs in a loop
}
но я не уверен, что они будут необходимы. Там нет проблем с использованием
foreach (ISystem system in entityManager.Systems)
{
system.UpdateCacheEntities();
}
но я не хочу запускать систему, если она не нужна.
Есть два типа событий, ChangeEvent
и ExecuteEvent
, Первый срабатывает при изменении значения компонента. Второй срабатывает, когда что-то должно быть сделано с определенной сущностью.
Если вам нужно или хотите, вы можете взглянуть на EntityManager
ComponentRequirements
и использование ECS
Пример системы будет что-то вроде этого
internal class HealthSystem : IReactiveSystem
{
public HealthSystem(EntityManager entityManager)
{
InitComponentRequirements();
InitComponentPools(entityManager);
}
private Dictionary<Guid, HealthComponent> healthComponentPool;
public List<Guid> EntityCache { get; } = new List<Guid>();
public ComponentRequirements ComponentRequirements { get; } = new ComponentRequirements();
public void InitComponentRequirements()
{
ComponentRequirements.AddRequiredType<HealthComponent>();
}
public void InitComponentPools(EntityManager entityManager)
{
healthComponentPool = entityManager.GetComponentPoolByType<HealthComponent>();
}
public void UpdateCacheEntities()
{
for (int i = 0; i < EntityCache.Count; i++)
{
UpdateCacheEntity(EntityCache[i]);
}
}
public void UpdateCacheEntity(Guid cacheEntityId)
{
Health healthComponent = healthComponentPool[cacheEntityId];
healthComponent.Value += 10; // just some tests
// update UI
}
}
Как я могу создать ChangeEvents
а также ExecuteEvents
для разных систем?
РЕДАКТИРОВАТЬ
Есть ли способ добавить делегаты событий в компоненты для запуска конкретной системы для этого объекта при изменении, если событие изменения прослушивает, или по запросу, если прослушивается событие выполнения?
Упоминая ChangeEvent
а также ExecuteEvent
Я имею в виду делегатов мероприятия.
В настоящее время я мог бы сделать что-то вроде этого
internal class HealthSystem : IReactiveSystem
{
//… other stuff
IReactiveSystem deathSystem = entityManager.GetSystem<Death>(); // Get a system by its type
public void UpdateCacheEntity(Guid cacheEntityId)
{
// Change Health component
// Update UI
if(currentHealth < 1) // call the death system if the entity will be dead
{
deathSystem.UpdateCacheEntity(cacheEntityId);
}
}
}
Но я надеялся создать лучшую архитектуру, используя делегатов событий, чтобы системы взаимодействовали и обменивались данными друг с другом.
2 ответа
Я не эксперт по этому шаблону проектирования, но я кое-что прочитал, и мой совет: постарайтесь не забывать истинное назначение этого шаблона. На этот раз я нашел статью в Википедии действительно интересной. Это в основном говорит (по крайней мере, это то, что я понял), что этот шаблон был "разработан", чтобы избежать создания слишком большого количества зависимостей, потеряв развязку. Вот пример, который я взял из статьи:
Предположим, есть функция рисования. Это будет "Система", которая перебирает все объекты, которые имеют как физический, так и видимый компоненты, и рисует их. Видимый компонент может обычно иметь некоторую информацию о том, как должен выглядеть объект (например, человек, монстр, искры, летающие стрелы) и использовать физический компонент, чтобы знать, где его нарисовать. Другая система может быть обнаружение столкновений. Он будет перебирать все сущности, которые имеют физический компонент, так как ему будет все равно, как рисуется сущность. Эта система будет, например, обнаруживать стрелы, которые сталкиваются с монстрами, и генерировать событие, когда это происходит. Он не должен понимать, что такое стрелка, и что это значит, когда стрелка поражает другой объект. Еще одним компонентом могут быть данные о здоровье и система, которая управляет здоровьем. Компоненты здоровья будут прикреплены к сущностям человека и монстра, но не к сущностям стрелок. Система управления работоспособностью будет подписываться на событие, генерируемое в результате столкновений, и соответствующим образом обновлять работоспособность. Эта система также может время от времени проходить через все объекты с компонентом здоровья и восстанавливать здоровье.
Я думаю, что вы слишком усложнили свою архитектуру, потеряв преимущества, которые этот шаблон может дать вам.
Прежде всего: зачем вам EntityManager
? Я цитирую снова:
Архитектура ECS обрабатывает зависимости очень безопасным и простым способом. Поскольку компоненты представляют собой простые блоки данных, они не имеют зависимостей.
Вместо этого ваши компоненты построены с EntityManager
зависимость вводится:
entityManager.AddSystem(new Movement(entityManager));
Результатом является относительно сложная внутренняя структура для хранения сущностей и связанных с ними компонентов.
После исправления этого вопроса возникает вопрос: как вы можете "общаться" с ISystem
s? Опять же, ответ в статье: Шаблон наблюдателя. По сути, каждый компонент имеет набор подключенных систем, которые уведомляются каждый раз, когда происходит определенное действие.
К чему я клоню, вы хотите иметь повторяющееся, одноразовое событие типа тика вместе с событием типа раз в год (преувеличенное, но ясное), вы можете сделать это с помощью функции обратного вызова делегата IE:
public delegate void Event(object Sender, EventType Type, object EventData);
public event Event OnDeath;
public event Event OnMove;
public void TakeDamage(int a)
{
Health-=a;
if(Health<1)
OnDeath?.Invoke(this,EventType.PlayerDeath,null);
}
public void ThreadedMovementFunction()
{
while(true)
{
int x,y;
(x,y) = GetMovementDirection();
if(x!=0||y!=0)
OnMove?.Invoke(this,EventType.PlayerMove,(x,y));
}
}
Вы можете реализовать это в интерфейсе, а затем сохранить класс объекта и получить доступ только к необходимым вещам, таким как события и так далее. но я не совсем понимаю, что вы ищете, поэтому, если бы вы могли уточнить точную проблему или то, что вам нужно решить, это было бы очень признательно!