用事件连接系统

tot*_*ner 7 c# entity-component-system

使用Entity-Component-System模式我想要将一些系统与事件连接起来.所以有些系统不应该在循环中运行,它们应该只是按需运行.

鉴于Health系统的示例,死亡系统应仅在组件低于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
}
Run Code Online (Sandbox Code Playgroud)

我进一步创建了接口

internal interface IReactiveSystem : ISystem
{
// event based
}
Run Code Online (Sandbox Code Playgroud)

internal interface IPeriodicSystem : ISystem
{
// runs in a loop
}
Run Code Online (Sandbox Code Playgroud)

但我不确定他们是否有必要.使用没有问题

foreach (ISystem system in entityManager.Systems)
{
    system.UpdateCacheEntities();
}
Run Code Online (Sandbox Code Playgroud)

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

有两种类型的事件,a ChangeEvent和a ExecuteEvent.当组件中的值发生更改时,会触发第一个.当应该使用特定实体执行某些操作时,会触发第二个触发器.

如果你需要或想要你可以看一下 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 
    }
}
Run Code Online (Sandbox Code Playgroud)

我如何创建ChangeEventsExecuteEvents使用不同的系统?


编辑

有没有办法将组件事件委托添加到组件中,以便在更改事件正在侦听时为此实体运行特定系统,或者在执行事件正在侦听时按需运行?

通过提及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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是我希望通过使用事件委托来实现系统之间的通信和共享数据,从而实现更好的架构.

Mar*_*ara 2

我不是这种设计模式的专家,但我读过一些相关内容,我的建议是:尽量不要忘记这种模式的真正目的。这次我发现维基百科上的文章非常有趣。基本上是说(至少是我所理解的)这种模式的“设计”是为了避免创建太多的依赖关系,从而失去解耦。这是我从文章中摘取的一个例子:

假设有一个绘图函数。这将是一个“系统”,它迭代具有物理和可见组件的所有实体,并绘制它们。可见组件通常可以具有一些有关实体外观的信息(例如,人类、怪物、四处飞舞的火花、飞翔的箭头),并使用物理组件来知道在哪里绘制它。另一个系统可能是碰撞检测。它将迭代具有物理组件的所有实体,因为它不关心实体是如何绘制的。例如,该系统将检测与怪物碰撞的箭头,并在发生这种情况时生成一个事件。它不需要理解什么是箭,以及当另一个物体被箭击中时意味着什么。另一个组成部分可能是健康数据和管理健康的系统。健康组件将附加到人类和怪物实体,但不附加到箭头实体。健康管理系统将订阅碰撞产生的事件并相应地更新健康状况。该系统还可以不时地遍历具有健康组件的所有实体,并重新生成健康状况。

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

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

ECS架构以非常安全且简单的方式处理依赖关系。由于组件是简单的数据桶,因此它们没有依赖性。

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

entityManager.AddSystem(new Movement(entityManager));
Run Code Online (Sandbox Code Playgroud)

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

解决这个问题后,问题是:如何与ISystems“沟通”?同样,答案就在文章中:观察者模式。本质上,每个组件都有一组附加系统,每次发生特定操作时都会通知这些系统。