将系统与事件连接

时间:2019-01-27 14:30:23

标签: c# entity-component-system

使用Entity-Component-System模式,我想将某些系统与事件连接起来。因此,某些系统不应循环运行,而应按需运行。

Health 系统为例, Death 系统仅应在组件的健康状况低于1时运行。

我考虑过拥有两种类型的系统。第一种是周期系统。每帧运行一次,例如 Render Movement 系统。另一种类型是基于事件的系统。如前所述,健康死亡之间的联系。

首先,我创建了两种系统类型都使用的基本接口。

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

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

2 个答案:

答案 0 :(得分:3)

我不是这种设计模式的专家,但是我读了一些东西,我的建议是:不要忘记这种模式的真正目的。这次,我发现article on Wikipedia真的很有趣。 基本上是说(至少是我所了解的),这种模式是“设计”的,以避免产生过多的依赖关系,从而避免了去耦。这是我从文章中摘录的一个示例:

  

假设有一个绘图功能。这将是一个“系统”   遍历所有既有物理对象又有可见对象的实体   组件,并绘制它们。可见组件通常可以具有   有关实体外观的一些信息(例如,人,怪物,   火花飞来飞去,并飞向箭头),并使用物理成分   知道在哪里画。另一个系统可能是碰撞检测。它   将遍历具有物理组件的所有实体,如   它不会在乎如何绘制实体。然后,该系统将   例如,检测与怪物碰撞的箭头,并生成一个   事件发生时。它不需要了解什么   箭头是什么,以及当另一个对象被箭头击中时的含义。   另一个组件可能是健康数据,以及一个管理   健康。健康成分将附着在人类和怪物身上   实体,但不指向箭头实体。健康管理系统   会订阅冲突产生的事件并进行更新   健康相应。该系统也可以不时地进行迭代   通过具有健康组件的所有实体,并重新生成健康。

我认为您使体系结构过于复杂,失去了该模式可以为您带来的优势。

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

  

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

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

entityManager.AddSystem(new Movement(entityManager));

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

解决此问题后,问题是:如何与ISystem进行“交流”? 同样,答案在以下文章中:观察者模式。本质上,每个组件都具有一组连接的系统,每次执行特定操作时都会通知这些系统。

答案 1 :(得分:1)

通过此操作,您想要重复一次,每个刻度类型事件与一年一次的类型事件(夸大但清晰)一次,您可以使用委托回调函数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));
    }
}

您可以将其实现到接口中,然后存储对象类,并且仅访问所需的内容(例如事件等)。但是tbh我不太了解您要寻找的内容,因此,如果您可以详细说明确切的问题或需要解决的问题,将不胜感激!