Fazendo upgrades em um sistema baseado em componente

29

Estou começando a entender o design baseado em componentes. Não sei qual é a maneira "certa" de fazer isso.

Aqui está o cenário. O jogador pode equipar um escudo. O escudo é desenhado como uma bolha ao redor do jogador, tem uma forma de colisão separada e reduz o dano que o jogador recebe dos efeitos da área.

Como esse escudo é arquitetado em um jogo baseado em componentes?

O que me deixa confuso é que o escudo obviamente tem três componentes associados a ele.

  • Redução / filtragem de danos
  • Um sprite
  • Um colisor.

Para piorar, diferentes variações de blindagem podem ter ainda mais comportamentos, todos componentes:

  • melhorar a saúde máxima do jogador
  • recuperação de Saúde
  • desvio de projétil
  • etc

  1. Estou pensando demais nisso? O escudo deveria ser apenas um super componente?
    Eu realmente acho que essa é uma resposta errada. Então, se você acha que esse é o caminho a seguir, explique.

  2. O escudo deve ser sua própria entidade que rastreia a localização do jogador?
    Isso pode dificultar a implementação da filtragem de danos. Também meio que desfoca as linhas entre componentes e entidades anexados.

  3. O escudo deve ser um componente que abriga outros componentes?
    Eu nunca vi ou ouvi falar de algo assim, mas talvez seja comum e ainda não sou profundo o suficiente.

  4. O escudo deve ser apenas um conjunto de componentes que são adicionados ao player?
    Possivelmente com um componente extra para gerenciar os outros, por exemplo, para que todos possam ser removidos como um grupo. (deixe acidentalmente para trás o componente de redução de danos, agora isso seria divertido).

  5. Outra coisa óbvia para alguém com mais experiência em componentes?

deft_code
fonte
Tomei a liberdade de tornar seu título mais específico.
Tétrada

Respostas:

11

O escudo deve ser sua própria entidade que rastreia a localização do jogador? Isso pode dificultar a implementação da filtragem de danos. Também meio que desfoca as linhas entre componentes e entidades anexados.

Edit: Eu acho que não há "comportamento autônomo" suficiente para uma entidade separada. Nesse caso específico, um escudo segue o alvo, trabalha para o alvo e não sobrevive ao alvo. Embora eu concorde que não há nada errado com o conceito de "objeto de escudo", neste caso, estamos lidando com comportamento, que se encaixa perfeitamente em um componente. Mas também sou um defensor de entidades puramente lógicas (em oposição a sistemas de entidades completos nos quais você pode encontrar componentes de transformação e renderização).

O escudo deve ser um componente que abriga outros componentes? Eu nunca vi ou ouvi falar de algo assim, mas talvez seja comum e ainda não sou profundo o suficiente.

Veja isso em uma perspectiva diferente; adicionar um componente também adiciona outros componentes e, após a remoção, os componentes adicionais também desaparecem.

O escudo deve ser apenas um conjunto de componentes que são adicionados ao player? Possivelmente com um componente extra para gerenciar os outros, por exemplo, para que todos possam ser removidos como um grupo. (deixe acidentalmente para trás o componente de redução de danos, agora isso seria divertido).

Essa poderia ser uma solução, promoveria a reutilização, mas também é mais suscetível a erros (para o problema que você mencionou, por exemplo). Não é necessariamente ruim. Você pode descobrir novas combinações de feitiços com tentativa e erro :)

Outra coisa óbvia para alguém com mais experiência em componentes?

Eu vou elaborar um pouco.

Acredito que você tenha notado como alguns componentes devem ter prioridade, independentemente de serem adicionados a uma entidade (isso também responderia a sua outra pergunta).

Também vou assumir que estamos usando a comunicação baseada em mensagens (por uma questão de discussão, é apenas uma abstração sobre uma chamada de método no momento).

Sempre que um componente de blindagem é "instalado", os manipuladores de mensagens dos componentes de blindagem são encadeados com uma ordem específica (superior).

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)

O componente "stats" instala um manipulador de mensagens "dano" no índice In / Invariant / Normal. Sempre que uma mensagem de "dano" for recebida, diminua o HP pelo valor "valor".

Comportamento bastante padrão (coloque alguma resistência a danos naturais e / ou características raciais, qualquer que seja).

O componente blindado instala um manipulador de mensagens "danificado" no índice 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

Você pode ver que isso é bastante flexível, apesar de exigir um planejamento cuidadoso ao projetar a interação do componente, pois você precisará determinar em que parte dos manipuladores de eventos de componentes do pipeline de manipulação de mensagens estão instalados.

Faz sentido? Deixe-me saber se posso adicionar mais detalhes.

Editar: sobre instâncias de múltiplos componentes (dois componentes de armadura). Você pode acompanhar a contagem total de instâncias em apenas uma instância da entidade (no entanto, isso mata o estado por componente) e continuar adicionando manipuladores de eventos de mensagem ou garantir que seus contêineres de componentes permitam a duplicação de tipos de componentes com antecedência.

Raine
fonte
Você respondeu "Não" à primeira pergunta sem fornecer qualquer motivo. Ensinar os outros é ajudá-los a entender o raciocínio por trás de qualquer decisão. Na IMO, o fato de que na RL um campo de força seria uma "entidade física" separada do seu próprio corpo é suficiente para permitir que seja uma entidade separada no código. Você pode sugerir boas razões para sugerir por que seguir esse caminho é ruim?
Engenheiro
@ Nick, não estou tentando ensinar nada a ninguém, mas sim compartilhando o que sei sobre o assunto. Mas estou indo para adicionar uma lógica por trás desse "não" que espero venha a remover essa downvote desagradável :(
Raine
Seu ponto de autonomia faz sentido. Mas você observa: "neste caso, estamos lidando com comportamento". Verdadeiro - comportamento envolvendo um objeto físico totalmente separado (a forma de colisão do escudo). Para mim, uma entidade se liga a um corpo físico (ou conjunto composto de corpos conectados, por exemplo, por juntas). Como você reconcilia isso? Da minha parte, eu me sentiria desconfortável em adicionar um acessório físico "fictício" que seria ativado apenas se o jogador usasse um escudo. IMO inflexível, difícil de manter em todas as entidades. Considere também um jogo em que os cintos de proteção mantêm os escudos mesmo após a morte (Dune).
Engenheiro
@ Nick, a maioria dos sistemas de entidades possui componentes lógicos e gráficos, portanto, nesse caso, ter uma entidade para um escudo é absolutamente razoável. Em sistemas de entidade puramente lógicos, "autonomia" é o produto de quão complexo é um objeto, suas dependências e vida útil. No final, a exigência é rei - e considerando que não há consenso real sobre o que um sistema entidade é, há muito espaço para soluções adaptadas ao projeto :)
Raine
@deft_code, informe-me se posso melhorar minha resposta.
Raine
4

1) Estou pensando demais nisso? O escudo deveria ser apenas um super componente?

Talvez, depende de quão reutilizável você deseja que seu código seja e se faz sentido.

2) O escudo deve ser sua própria entidade que rastreia a localização do jogador?

A menos que esse escudo seja algum tipo de criatura que possa andar independentemente em algum momento.

3) A blindagem deve ser um componente que abriga outros componentes?

Isso parece muito com entidade, então a resposta é não.

4) O escudo deve ser apenas um conjunto de componentes que são adicionados ao player?

É provável.

"Redução / filtragem de danos"

  • funcionalidade do componente blindagem principal.

"Um sprite"

  • existe um motivo para você não poder adicionar outro SpriteComponent à sua entidade de personagem (em outras palavras, mais de um componente de determinado tipo por entidade)?

"Um colisor"

  • tem certeza de que precisa de outro? Isso depende do seu mecanismo de física. Você pode enviar uma mensagem ao ColliderComponent da entidade de caractere e solicitar que ele mude de forma?

"Aumenta a saúde máxima do jogador, a regeneração de vida, a deflexão de projéteis etc."

  • outros artefatos podem fazer isso (espadas, botas, anéis, feitiços / poções / santuários visitantes etc.), portanto esses devem ser componentes.
Den
fonte
3

Um escudo, como uma entidade física , não é diferente de qualquer outra entidade física , por exemplo, um zangão que circula ao seu redor (e que, de fato, poderia ser um tipo de escudo!). Portanto, faça do shield uma entidade lógica separada (permitindo que ele mantenha seus próprios componentes).

Dê ao seu escudo alguns componentes: um componente Físico / Espacial para representar sua forma de colisão, e um componente DamageAffector que mantém uma referência a alguma entidade à qual ele aplicará dano aumentado ou reduzido (por exemplo, seu personagem do jogador) toda vez que a entidade segurar o DamageAffector sofre danos. Assim, seu jogador está recebendo dano "por procuração".

Defina a posição da entidade de escudo para a posição do jogador a cada tick. (Escreva uma classe de componente reutilizável que faça isso: escreva uma vez, use várias vezes.)

Você precisará criar a entidade de escudo, por exemplo. em coletar um powerup. Eu uso um conceito genérico chamado Emissor, que é um tipo de componente de entidade que gera novas entidades (geralmente através do uso de uma EntityFactory à qual ele se refere). Onde você decide localizar o emissor é com você - por exemplo. coloque-o em uma energização e faça-o disparar quando a energização for coletada.


O escudo deve ser sua própria entidade que rastreia a localização do jogador? Isso pode dificultar a implementação da filtragem de danos. Também meio que desfoca as linhas entre componentes e entidades anexados.

Existe uma linha tênue entre subcomponentes lógicos (espacial, AI, slots de armas, processamento de entrada etc. etc.) e subcomponentes físicos. Você precisa decidir de que lado você está, pois isso define fortemente que tipo de sistema de entidades você possui. Para mim, o subcomponente Física da minha Entidade lida com os relacionamentos hierárquicos da física (como membros em um corpo - pense nos nós dos cenários), enquanto os controladores lógicos mencionados acima são tipicamente o que são representados pelos componentes da sua entidade - e não pelos que representam "equipamentos" físicos individuais.

Engenheiro
fonte
3

O escudo deve ser um componente que abriga outros componentes?

Talvez não abrigue outros componentes, mas controla a vida útil dos subcomponentes. Portanto, em algum pseudo-código aproximado, o código do seu cliente adicionaria esse componente "shield".

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 );
    }
}
Tetrad
fonte
Não está claro o que thissignifica em sua resposta. Está thisse referindo ao componente Shield ou você quis dizer a entidade que está usando o shield, seu pai? A confusão pode ser minha culpa. "Baseado em componentes" é meio vago. Na minha versão de entidades baseadas em componentes, uma entidade é simplesmente um contêiner de componentes com alguma funcionalidade mínima própria (nome do objeto, tags, mensagens, etc.).
Deft_code 20/06/11
Seria menos confuso se eu usasse gameObjectou algo assim. É uma referência ao objeto / entidade do jogo atual / qualquer que seja o proprietário dos componentes.
Tétrada
0

Se o seu sistema de componentes permitir scripts, o componente de blindagem poderá ser quase um supercomponente que apenas chama um script para seu parâmetro "effect". Dessa forma, você mantém a simplicidade de um único componente para blindagens e transfere toda a lógica do que ele realmente faz para arquivos de script personalizados que são alimentados com blindagens pelas definições de sua entidade.

Eu faço algo semelhante para o meu componente Moveable, ele contém um campo que é do script de reação-chave (uma subclasse de script no meu mecanismo). Esse script define um método que segue minha mensagem de entrada. como tal, eu posso simplesmente fazer algo assim no meu arquivo de definição de tempalte

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

então no meu componente móvel durante o registro de mensagens, registro os scripts Do método (código em C #)

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

é claro que isso depende do meu método Do, seguindo o padrão de funções que meu RegisterHandler executa. Nesse caso, seu (remetente IComponent, argumento do tipo ref)

então meu "script" (no meu caso também C # apenas runime compilado) define

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

e minha classe base KeyReactionScript

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

depois, quando um componente de entrada enviar uma mensagem do tipo MessageTypes.InputUpdate com o tipo como tal

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

O método no script que foi vinculado a essa mensagem e tipo de dados será acionado e manipulará toda a lógica.

O código é bastante específico para o meu mecanismo, mas a lógica deve ser funcional em qualquer caso. Eu faço isso para muitos tipos, a fim de manter a estrutura do componente simples e flexível.

exnihilo1031
fonte