8

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

以Health系统为例,Death系统仅应在组件的生命值低于 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();
}

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

有两种类型的事件, aChangeEvent和 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 
    }
}

我怎样才能创建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);
        }
    }
}

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

4

2 回答 2

3

我不是这种设计模式的专家,但我读过一些关于它的东西,我的建议是:尽量不要忘记这种模式的真正目的。这次我发现维基百科上的文章非常有趣。它基本上是在说(至少这是我所理解的)这种模式已经“设计”以避免创建过多的依赖关系,从而失去解耦。这是我从文章中获取的示例:

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

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

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

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

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

entityManager.AddSystem(new Movement(entityManager));

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

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

于 2019-02-04T21:50:48.730 回答
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));
    }
}

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

于 2019-02-03T23:22:53.170 回答