【问题标题】:connect systems with events将系统与事件联系起来
【发布时间】:2019-06-20 16:49:44
【问题描述】:

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

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

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

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

如何为不同的系统创建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);
        }
    }
}

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

【问题讨论】:

  • 您的项目中是否使用了 IoC 容器?
  • 对不起,什么是 IoC?但是不,我没有:)
  • IoC 是控制反转,它是一种编程范式,负责从您的程序中删除依赖项。在这里广泛描述:stackoverflow.com/questions/3058/what-is-inversion-of-control :)
  • 这个问题应该因为过于宽泛而被关闭。你能把它缩小到一个特定的问题吗? “我如何设计我的应用程序”不是一个可以回答的问题。
  • @totalBeginner 不,它没有,因为我不知道(a)你想做什么,(b)你尝试了什么,(c)什么不起作用和(d)您遇到的确切错误消息或问题。简单地说你想使用事件委托是不够的。将它们用于什么,在什么情况下,实现什么?

标签: c# entity-component-system


【解决方案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));
    }
}

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

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

    entityManager.AddSystem(new Movement(entityManager));
    

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

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

    【讨论】:

      猜你喜欢
      • 2019-05-30
      • 1970-01-01
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多