在基于组件的系统中上电


29

我才刚刚真正开始着手基于组件的设计。我不知道这样做的“正确”方法是什么。

这是场景。玩家可以装备盾牌。盾牌被绘制为气泡围绕玩家,它具有单独的碰撞形状,并减少了玩家从区域效果中受到的伤害。

在基于组件的游戏中如何设计这样的盾牌?

让我感到困惑的是,屏蔽罩显然具有三个与之关联的组件。

  • 减少伤害/过滤
  • 一个精灵
  • 对撞机。

更糟的是,不同的防护罩变体可能具有更多的行为,所有这些行为都可能是以下因素:

  • 提高玩家的最大健康度
  • 健康再生
  • 弹丸变形
  • 等等

  1. 我在想这个吗?盾牌应该只是超级组件吗?
    我真的认为这是错误的答案。因此,如果您认为这是要走的路,请解释一下。

  2. 盾牌应该是自己的实体来跟踪玩家的位置吗?
    这可能使实施损害过滤变得困难。这也模糊了附加组件和实体之间的界线。

  3. 屏蔽是否应该是容纳其他组件的组件?
    我从未见过或听到过这样的消息,但也许很常见,但我还不够深入。

  4. 盾牌应该只是添加到播放器中的一组组件吗?
    可能还有一个额外的组件来管理其他组件,例如,这样它们就可以作为一个组全部删除。(偶然地留下减少伤害的部分,这很有趣)。

  5. 对于具有更多组件经验的人来说,还有其他明显的东西吗?


我采取了使您的标题更加具体的自由。
四分

Answers:


11

盾牌应该是自己的实体,可以追踪玩家的位置吗?这可能使实施损害过滤变得困难。这也模糊了附加组件和实体之间的界线。

编辑:我认为没有足够的“自主行为”为一个单独的实体。在这种特定情况下,防护罩会跟随目标,对目标起作用并且不会超过目标。尽管我倾向于同意“屏蔽对象”的概念没有错,但是在这种情况下,我们正在处理行为,它适合于组件。但是我还是纯粹逻辑实体的提倡者(与可以在其中找到“变换”和“渲染”组件的成熟的实体系统相反)。

屏蔽是否应该是容纳其他组件的组件?我从未见过或听到过这样的消息,但也许很常见,但我还不够深入。

从不同的角度看待它;添加组件也会添加其他组件,并且在删除后,其他组件也将消失。

盾牌应该只是添加到播放器中的一组组件吗?可能还有一个额外的组件来管理其他组件,例如,这样它们就可以作为一个组全部删除。(偶然地留下减少伤害的部分,这很有趣)。

这可能是一个解决方案,它将促进重用,但是它也更容易出错(例如,您提到的问题)。不一定坏。您可能会发现带有反复试验的新咒语组合:)

对于具有更多组件经验的人来说,还有其他明显的东西吗?

我将详细说明。

我相信您已经注意到,无论何时将某些组件添加到实体中,它们都应具有优先级(这也会回答您的其他问题)。

我还要假设我们正在使用基于消息的通信(为了便于讨论,目前这只是对方法调用的抽象)。

每当“安装”屏蔽组件时,屏蔽组件消息处理程序都以特定(较高)的顺序链接。

Handler Stage    Handler Level     Handler Priority
In               Pre               System High
Out              Invariant         High
                 Post              AboveNormal
                                   Normal
                                   BelowNormal
                                   Low
                                   System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)

“状态”组件在In / Invariant / Normal索引处安装“损坏”消息处理程序。每次收到“损坏”消息时,将HP减少其“值”数量。

相当标准的行为(具有某种自然抵抗力和/或种族特质,无论如何)。

防护组件在In / Pre / High索引处安装“损坏”消息处理程序。

Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
    stats
        stats.hp -= damage.value

damage -> shield -> stats
    shield
        if(shield.energy) {
            remove_me();
            return;
        }
        damage.value -= shield.energyquantum
        shield.energy -= shield.energyquantum;

     stats
        stats.hp -= damage.value

您可以看到这是非常灵活的,尽管在设计组件交互时需要仔细计划,因为您将不得不确定在消息处理管道的哪一部分中安装了组件消息事件处理程序。

说得通?让我知道是否可以添加更多细节。

编辑:关于多个组件实例(两个装甲组件)。您可以只跟踪一个实体实例中的实例总数(但是这会杀死每个组件的状态),而只是继续添加消息事件处理程序,或者确保组件容器事先允许重复的组件类型。


您没有给出任何理由就第一个问题回答“否”。教别人是关于帮助他们理解任何决定背后的原因。IMO,在RL中,力场将是与您自己的身体分离的“物理实体”,这一事实足以使它成为代码中的分离实体。您能提出充分的理由来说明为什么这条路不好吗?
工程师

@Nick,我决不是要教任何人任何东西,而是要分享我在该主题上的知识。但是,我要在“否”后面添加一个理由,希望可以消除不愉快的否决意见:(
Raine

您的自治点很有意义。但是您注意到:“在这种情况下,我们正在处理行为”。正确-行为涉及完全独立的物理对象(屏蔽碰撞形状)。对我而言,一个实体绑定到一个物理物体(或一组通过例如关节连接的复合物体)。您如何调和这一点?就我而言,添加一个“虚拟”物理固定装置会感到不舒服,该固定装置只有在玩家碰巧使用盾牌时才会激活。国际海事组织缺乏灵活性,很难在所有实体之间进行维护。也考虑一下这样一种游戏,即使死亡后,盾牌皮带仍会保持盾牌(Dune)。
工程师

@Nick,大多数实体系统都具有逻辑和图形相关的组件,因此,在这种情况下,拥有一个用于屏蔽的实体是绝对合理的。在纯逻辑实体系统中,“自治”是对象的复杂程度,其依赖关系和生存期的乘积。最后,需求为王-考虑到实体系统是什么并没有真正的共识,因此有大量的项目定制解决方案空间:)
Raine

@deft_code,请告诉我是否可以改善答案。
Raine

4

1)我对此有没有想过?盾牌应该只是超级组件吗?

也许取决于您希望代码的可重用性以及它是否有意义。

2)盾牌应该是自己的实体来跟踪玩家的位置吗?

除非这个盾牌是某种可以在某个阶段独立走动的生物,否则不会。

3)屏蔽层应该是容纳其他组件的组件吗?

这听起来很像实体,所以答案是否定的。

4)防护罩是否应该是添加到播放器中的一组组件?

这有可能。

“减损/过滤”

  • 核心屏蔽组件功能。

“一个精灵”

  • 是否有原因不能将另一个SpriteComponent添加到角色实体中(换句话说,每个实体不止一个特定类型的组件)?

“对撞机”

  • 您确定需要另一个吗?这取决于您的物理引擎。您可以将消息发送到角色实体的ColliderComponent并要求其更改形状吗?

“增强球员的最大健康,恢复健康,弹丸变形等。”

  • 其他文物可能能够做到这一点(剑,靴子,戒指,咒语/药水/参拜神社等),因此它们应该是组成部分。

3

盾牌,作为一个物理实体,与任何其他物理实体没有什么不同,例如,一架环绕您的无人机(实际上,它本身可能就是一种盾牌!)。因此,使屏蔽成为单独的逻辑实体(从而允许其保留自己的组件)。

给盾牌几个组成部分:一个物理/空间组件,表示它的碰撞形式;一个DamageAffector组件,该组件持有对某个实体的引用,该实体每次实体都会施加增加或减少的伤害(例如,玩家角色)持有DamageAffector会造成伤害。因此,您的玩家“通过代理”受到了损害。

每隔一点,将盾牌实体的位置设置为玩家的位置。(编写一个执行此操作的可重用组件类:编写一次,使用多次。)

您将需要创建盾牌实体,例如。在收集能量。我使用一个称为“发射器”的通用概念,它是一种实体组件,它会产生新的实体(通常通过使用它引用的EntityFactory)。您决定放置发射器的位置取决于您-例如 将其置于开机状态并在收集开机状态时触发。


盾牌应该是自己的实体来跟踪玩家的位置吗?这可能使实施损害过滤变得困难。这也模糊了附加组件和实体之间的界线。

逻辑子组件(空间,AI,武器插槽,输入处理等)与物理子组件之间有一条细线。您需要确定自己站在哪一侧,因为这强烈地定义了您拥有哪种类型的实体系统。对我来说,我实体的Physics子组件处理物理-层级关系(例如身体的肢体-考虑场景图节点),而上述逻辑控制器通常由您的实体组件表示-而不是这些表示个人的“固定装置”。


3

屏蔽是否应该是容纳其他组件的组件?

也许不容纳其他组件,但控制子组件的寿命。因此,在一些粗略的伪代码中,您的客户端代码将添加此“屏蔽”组件。

class Shield : Component
{
    void Start() // happens when the component is added
    {
        sprite = entity.add_component<Sprite>( "shield" );
        collider = entity.add_component<Collider>( whatever );
        //etc
    }

    void OnDestroy() // when the component is removed
    {
        entity.remove_component( sprite );
        entity.remove_component( collider );
    }

    void Update() // every tick
    {
        if( ShouldRemoveSelf() ) // probably time based or something
            entity.remove_component( this );
    }
}

目前尚不清楚this您的答案意味着什么。是this指通过遮蔽部件还是你的意思是说是一个使用屏蔽的实体,其母公司?困惑可能是我的错。 “基于组件”有点含糊。在我的基于组件的实体版本中,实体只是具有自身最小功能(对象名称,标签,消息传递等)的组件容器。
deft_code 2011年

如果我用了gameObject什么的话,会减少混乱。它是对当前游戏对象/实体/拥有组件的所有对象的引用。
四分

0

如果您的组件系统允许编写脚本,那么shield组件可能几乎是一个超级组件,仅为其“ effect”参数调用脚本。这样一来,您可以保持单个组件的简单性,并且将其实际工作的所有逻辑转移到由实体定义馈送到屏蔽的自定义脚本文件中。

我对Moveable组件执行了类似的操作,它包含一个keyreaction脚本(引擎中脚本的子类)字段,该脚本定义了一个跟在我输入消息之后的方法。这样,我可以在我的临时定义文件中简单地做这样的事情

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

然后在消息注册期间在我的可移动组件中注册脚本Do方法(C#中的代码)

Owner.RegisterHandler<InputStateInformation>(MessageType.InputUpdate, kScript.Do);

当然,这依赖于我的Do方法遵循我的RegisterHandler采取的功能模式。在这种情况下,其(IComponent发送者,引用类型参数)

所以我的“脚本”(在我的情况下也是C#只是runime编译)定义了

 public class CameraMoveScript : KeyReactionScript
{
 public override void Do(IComponent pSender, ref InputStateInformation inputState)
 {
    //code here
 }
}

和我的基类KeyReactionScript

public class KeyReactionScript : Script
{
      public virtual void Do(IComponent pSender, ref InputStateInformation inputState);
}

然后稍后在输入组件发送类型为MessageTypes.InputUpdate的消息时

 InputStateInformation myInputState = InputSystem.GetInputState();
 SendMessage<InputStateInformation>(MessageTypes.InputUpdate, ref myInputState);

脚本中绑定到该消息和数据类型的方法将被触发并处理所有逻辑。

该代码非常特定于我的引擎,但是在任何情况下该逻辑都应该起作用。我对许多类型都执行此操作,以使组件结构简单灵活。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.