Manuseio de entrada no design baseado em componentes

12

Sei que essa pergunta foi feita várias vezes, mas ainda não sei como implementar a manipulação de entrada em um mecanismo baseado em componente.

O design baseado em componentes que usei foi baseado na série de blogs da T = Machine e na Artemis, na qual as Entidades são apenas identificações.

Existem três idéias principais que tenho na implementação do tratamento de entradas:

  1. O componente de entrada conterá eventos de seu interesse. O sistema de entrada converterá os eventos de chave e mouse em eventos do jogo e percorrerá as entidades com o componente de entrada e, se estiverem interessados ​​no evento, uma ação apropriada será executada pelo sistema de entrada. Essa ação seria codificada no sistema de entrada.
  2. Nenhum componente de entrada. Você registraria entidades com eventos específicos no sistema de entrada. O sistema de entrada enviaria mensagens (com o ID da entidade e o tipo de evento) para outros sistemas, para que eles possam executar a ação apropriada. Ou, como no primeiro caso, as ações seriam codificadas no sistema de entrada.
  3. Semelhante ao primeiro método, mas, em vez de codificar a ação no sistema de entrada, o componente conteria um mapa de eventos para funções (ie std::map<std::function>) que seriam chamadas pelo sistema de entrada. Isso tem o efeito adicional de poder acoplar o mesmo evento a ações diferentes.

Você recomendaria algum dos métodos acima ou tem alguma sugestão que me ajudasse a implementar um sistema flexível de manipulação de entradas? Além disso, ainda não estou familiarizado com o multi-threading, mas todas as sugestões que tornem a implementação compatível com threads também são bem-vindas.

Nota: Um requisito adicional que eu gostaria que a implementação cumprisse é que eu seria capaz de passar a mesma entrada para muitas entidades, como, por exemplo, mover uma entidade de câmera e o player ao mesmo tempo.

Grieverheart
fonte
2
Normalmente (quando a câmera segue o player), você não deseja receber informações da câmera, mas faz com que a câmera verifique a posição do player e siga-a.
Lucas B.
1
Realmente não importa conceitualmente se a câmera segue o player ou "ela mesma". No entanto, não tenho certeza de como sua sugestão seria implementada em um design baseado em componentes sem violar os princípios de design.
Grieverheart 11/11
1
@ Lucas B .: Depois de pensar um pouco, vejo que você também pode criar a câmera como uma classe separada, levando um ponteiro para uma entidade a seguir.
Grieverheart 11/02

Respostas:

8

Eu acho que, assim como minha resposta sobre materiais em um sistema de componentes , você está enfrentando um problema em que está tentando colocar tudo em um "componente". Você não precisa fazer isso e, ao fazer isso, provavelmente está criando uma interface realmente complicada tentando encaixar um monte de pinos quadrados em orifícios redondos.

Parece que você já possui um sistema que lida com a aquisição de entrada do player. Eu optaria por uma abordagem que depois traduz essa entrada em ações ("avançar" ou "retroceder") ou eventos e as envia para as partes interessadas. No passado, não permiti que componentes se registrassem para esses eventos, preferindo uma abordagem em que o sistema de nível superior selecionasse explicitamente a "entidade controlada". Mas poderia funcionar dessa outra maneira, se você preferir, especialmente se você vai reutilizar as mesmas mensagens para executar ações que não foram estimuladas diretamente pela entrada.

Eu não sugeriria necessariamente implementar o comportamento de seguir a câmera, fazendo com que a entidade da câmera e a entidade reprodutora respondessem à mensagem "seguir em frente" (etc). Isso cria uma conexão extremamente rígida entre os dois objetos que provavelmente não será boa para o jogador, e também torna um pouco mais complicado lidar com coisas como ter a câmera orbitando o jogador quando o jogador gira para a esquerda ou para a direita: você tem uma entidade responder a "girar para a esquerda" assumindo que é escravo do jogador, mas isso significa que ele não pode responder corretamente se alguma vez foi escravo ... a menos que você introduza esse conceito como algum estado que possa verificar. E se você fizer isso, também poderá implementar um sistema adequado para escravizar dois objetos físicos juntos, com ajustes de elasticidade apropriados e assim por diante.

Em relação à multiencadeamento, não vejo realmente a necessidade de empregá-la aqui, pois provavelmente causaria mais complicações do que vale a pena, e você está lidando com um problema inerentemente serial, para que você envolva muita discussão primitivas de sincronização.

Comunidade
fonte
Eu jogo minha pergunta um pouco de pensamento e estava prestes a responder a mim mesmo. Eu também cheguei à conclusão de que deveria ser melhor dissociar o manuseio de entrada do sistema EC, para que seja agradável ver uma confirmação disso. Como pensei em fazer isso é pelo uso de sinais e pela capacidade de associar várias entidades a um tipo de evento. Também decidi desacoplar a câmera, embora isso não seja realmente necessário e tê-la como entidade seja igualmente viável. Eu acho que, quando ainda é um novato na EC, você realmente tem que pensar quais são os benefícios de tornar algo um componente ou entidade.
Grieverheart
4

Minha experiência pode ser tendenciosa, mas em projetos de várias plataformas os dispositivos de entrada não são diretamente expostos ao sistema da entidade.

Os dispositivos de entrada são manipulados por um sistema de nível inferior que recebe os eventos das teclas, botões, eixo, mouse, superfícies de toque, acelerômetros ...

Esses eventos são então enviados através de uma camada de geradores de intenção dependentes do contexto.

Cada gerador se registra para alterações de estado de componentes, entidades e sistemas que são relevantes para suas funções.

Esses geradores então enviam mensagens / intenções para roteamento para o sistema de intenção em que as entidades têm um componente ou diretamente para os componentes certos.

Dessa forma, você pode simplesmente confiar em "sempre" com a mesma entrada, por exemplo, JUMP_INTENT (1), JUMP_INTENT (0), AIM_INTENT (1) ...

E "todo" o trabalho de entrada dependente da plataforma suja permanece fora do sistema da sua entidade.


Em relação à câmera, se você quiser movê-la pelo player, ela pode registrar seu próprio componente de intenção e ouvir as intenções que você enviará.

Caso contrário, quando se segue o player, ele nunca deve ouvir as entradas destinadas ao player. Ele deve ouvir as alterações de estado emitidas pelo player (ENTITY_MOVED (transformação)) ... e se mover em conformidade. Se você usa um sistema de física, pode até conectar a câmera ao player usando uma das várias articulações.

Coiote
fonte
Coiote, obrigado pela sua resposta. Também li seu outro post aqui . Minha maior preocupação não é como abstrair a entrada. Eu já tenho uma construção de nível inferior que lida com pressionamentos de tecla e tal, e adicionar mais um nível de indireção não seria difícil. Meu problema está em lidar com os eventos gerados, por exemplo, pelo seu sistema de intenções. Se bem entendi, você não possui componentes de entrada no seu método. Como você sabe quais entidades precisam de entrada e como você lida com isso? Você poderia dar alguns exemplos mais concretos?
Grieverheart 11/02
2

Que benefício um InputComponent traz? Certamente, é prerrogativa do comando de entrada decidir em quais entidades ele está executando uma ação. O exemplo clássico é o de fazer o jogador pular. Em vez de ter um InputComponent em todas as entidades ouvindo eventos "Jump", por que não fazer com que o comando jump procure a entidade marcada como "player" e execute a própria lógica necessária?

Action jump = () =>
{
    entities["player"].Transform.Velocity.Y += 5;
};

Outro exemplo, do OP:

Action moveRight = () =>
{
    foreach (var entity in entities.Tagged("player", "camera"))
        entity.Transform.Position.X += 5;
};
AlexFoxGill
fonte