3
如何在基于组件的实体系统中正确实现消息处理?
我正在实现一个具有以下内容的实体系统变体: 一个实体类是多一点的ID结合部件一起 一堆没有“组件逻辑” 的组件类,只有数据 一堆系统类(又名“子系统”,“管理器”)。这些完成所有实体逻辑处理。在大多数基本情况下,系统只是遍历它们感兴趣的实体列表并对其进行操作 甲MessageChannel类对象被所有的游戏系统共享。每个系统都可以订阅特定类型的消息以进行收听,还可以使用该频道将消息广播到其他系统 系统消息处理的初始变体是这样的: 依次在每个游戏系统上运行更新 如果系统对某个组件执行了某些操作,而其他系统可能对此操作感兴趣,则该系统会发送一条适当的消息(例如,系统调用 messageChannel.Broadcast(new EntityMovedMessage(entity, oldPosition, newPosition)) 每当实体移动时) 订阅特定消息的每个系统都会得到其消息处理方法,称为 如果系统正在处理事件,并且事件处理逻辑要求广播另一条消息,则立即广播该消息,并调用另一条消息处理方法链 在我开始优化碰撞检测系统之前,这个变体是可以的(随着实体数量的增加,它变慢了)。首先,它只是使用简单的蛮力算法迭代每个实体对。然后,我添加了一个“空间索引”,该索引具有一个单元格网格,该网格存储特定单元格区域内的实体,因此仅允许对相邻单元格中的实体进行检查。 实体每次移动时,碰撞系统都会检查该实体是否与新位置的物体发生碰撞。如果是,则检测到冲突。并且,如果两个碰撞的实体都是“物理对象”(它们都具有RigidBody分量,并且打算彼此推开以免占据相同的空间),则专用的刚体分离系统会要求运动系统将实体移动到某个将他们分开的特定职位。这进而导致运动系统发送消息,通知已更改的实体位置。碰撞检测系统旨在做出反应,因为它需要更新其空间索引。 在某些情况下,这会引起问题,因为单元的内容(C#中的Entity对象的通用列表)在进行迭代时会被修改,从而导致迭代器抛出异常。 那么... 如何防止碰撞系统在检查碰撞时被打断? 当然,我可以添加一些“聪明” /“棘手”逻辑来确保正确地迭代单元格内容,但是我认为问题不在于碰撞系统本身(在其他系统中也存在类似问题),而是消息在系统之间传播时得到处理。我需要某种方式来确保特定的事件处理方法能够正常工作,而不会造成任何干扰。 我尝试过的 传入消息队列。每当某个系统广播消息时,该消息就会被添加到对该消息感兴趣的系统的消息队列中。当每帧调用系统更新时,将处理这些消息。问题是:如果系统A将消息添加到系统B的队列中,则如果系统B的更新要晚于系统A(在同一游戏框架中),则它工作良好;否则,它将导致该消息处理下一个游戏框架(某些系统不希望使用) 传出邮件队列。当系统处理事件时,它将广播的所有消息添加到传出消息队列中。消息无需等待系统更新被处理:在初始消息处理程序完成工作后,它们将立即得到处理。如果对消息的处理导致其他消息被广播,它们也将添加到传出队列中,因此所有消息都在同一帧中进行处理。问题:如果实体生存期系统(我用一个系统实现了实体生存期管理)创建了一个实体,它将通知某些系统A和B。当系统A处理该消息时,它将导致一系列消息,这些消息最终导致所创建的实体被破坏(例如,在与某个障碍物碰撞的位置处创建了一个项目符号实体,这会导致项目符号自毁)。在解析消息链时,系统B没有获得实体创建消息。因此,如果系统B也对实体破坏消息感兴趣,它将得到它,并且只有在“链”完成解析之后,它才会得到初始实体创建消息。这将导致销毁消息被忽略,创建消息被“接受”, 编辑-回答问题,评论: 碰撞系统迭代时,谁修改了单元格的内容? 当碰撞系统正在对某个实体及其邻居进行碰撞检查时,可能会检测到碰撞,并且该实体系统将发送一条消息,其他系统立即对此进行响应。对消息的反应可能导致其他消息被创建并立即处理。因此,即使先前的碰撞检查尚未完成,其他一些系统也可能会创建一条消息,表明碰撞系统随后需要立即进行处理(例如,移动了一个实体,因此碰撞系统需要更新其空间索引)。 您不能使用全局传出消息队列吗? 我最近尝试了一个全局队列。它引起新的问题。问题:我将储罐实体移动到墙实体(储罐由键盘控制)。然后我决定改变坦克的方向。为了将水箱和墙壁的每个框架分开,CollidingRigidBodySeparationSystem将水箱从墙壁上移开的最小可能量。分离方向应与水箱的移动方向相反(游戏图纸开始时,水箱应看起来好像从未移入墙壁)。但是方向变成了与新方向相反的方向,因此将储罐移动到与最初不同的另一侧。发生问题的原因:这是现在处理消息的方式(简化代码): public void Update(int deltaTime) { m_messageQueue.Enqueue(new TimePassedMessage(deltaTime)); while (m_messageQueue.Count > 0) { Message message = m_messageQueue.Dequeue(); this.Broadcast(message); } } …