将系统与事件连接起来

connect systems with events

使用实体-组件-系统模式,我想将一些系统与事件连接起来。所以有些系统不应该 运行 在一个循环中,它们应该 运行 按需。

生命值系统为例,死亡系统应该只运行当一个组件的生命值低于1时。

我想过有两种类型的系统。第一类是周期系统。这 运行 每帧一次,例如 RenderMovement 系统。另一种类型是基于事件的系统。如前所述,健康死亡之间存在联系。

首先,我创建了一个可供两种系统类型使用的基本界面。

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();
}

但如果不需要,我不想 运行 系统。

有两种类型的事件,ChangeEventExecuteEvent。当组件的值发生变化时,第一个被触发。当应该对特定实体执行某些操作时会触发第二个。

如果您需要或想要,可以查看 EntityManager

https://pastebin.com/NnfBc0N9

ComponentRequirements

https://pastebin.com/xt3YGVSv

以及ECS的使用

https://pastebin.com/Yuze72xf

一个示例系统是这样的

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 
    }
}

如何为不同的系统创建 ChangeEventsExecuteEvents


编辑

有没有一种方法可以将组件的事件委托添加到 运行 这个实体的特定系统中,如果更改事件正在侦听,或者如果执行事件正在侦听,则按需更改?

提到 ChangeEventExecuteEvent 我只是指事件代表。

目前我可以做这样的事情

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);
        }
    }
}

但我希望通过使用事件委托使系统相互通信和共享数据来实现更好的架构。

根据我的理解,你想要有一个重复的,每个 tick 类型的事件和一年一次的类型事件(夸大但清楚),你可以使用委托回调函数 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));
    }
}

您可以将其实现到接口中,然后存储对象 class 并仅访问所需的内容,例如事件等。但是我不太明白你在找什么,所以如果你能详细说明你需要解决的确切问题或事情,那将不胜感激!

我不是这个设计模式的专家,但我读了一些关于它的东西,我的建议是:尽量不要忘记这个模式的真正目的。这次我发现 article on Wikipedia 真的很有趣。 它基本上是说(至少我是这么理解的)这种模式已经 "designed" 以避免创建太多的依赖关系,失去解耦。这是我从文章中摘录的一个例子:

Suppose there is a drawing function. This would be a "System" that iterates through all entities that have both a physical and a visible component, and draws them. The visible component could typically have some information about how an entity should look (e.g. human, monster, sparks flying around, flying arrow), and use the physical component to know where to draw it. Another system could be collision detection. It would iterate through all entities that have a physical component, as it would not care how the entity is drawn. This system would then, for instance, detect arrows that collide with monsters, and generate an event when that happens. It should not need to understand what an arrow is, and what it means when another object is hit by an arrow. Yet another component could be health data, and a system that manages health. Health components would be attached to the human and monster entities, but not to arrow entities. The health management system would subscribe to the event generated from collisions and update health accordingly. This system could also now and then iterate through all entities with the health component, and regenerate health.

我认为您使架构过于复杂,失去了这种模式可以给您带来的优势。

首先:为什么需要EntityManager?我再次引用:

The ECS architecture handles dependencies in a very safe and simple way. Since components are simple data buckets, they have no dependencies.

相反,您的组件是使用注入的 EntityManager 依赖项构建的:

entityManager.AddSystem(new Movement(entityManager));

结果是一个相对复杂的内部结构来存储实体和相关组件。

解决这个问题后,问题是:你怎么能 "communicate" 和 ISystem 呢? 同样,答案在文章中:观察者模式。基本上每个组件都有一组附加系统,每次发生特定操作时都会通知这些系统。