Manuseio do sistema de entrada do teclado

13

Nota: Preciso pesquisar, em vez de fazer retornos de chamada devido às limitações da API (SFML). Também peço desculpas pela falta de um título "decente".

Eu acho que tenho duas perguntas aqui; como registrar a entrada que estou recebendo e o que fazer com ela.

Manipulação de entrada

Estou falando após o fato de você ter registrado que a tecla 'A' foi pressionada, por exemplo, e como fazê-lo a partir daí.

Eu já vi uma variedade de todo o teclado, algo como:

bool keyboard[256]; //And each input loop check the state of every key on the keyboard

Mas isso parece ineficiente. Você não apenas está acoplando a tecla 'A' ao 'jogador que se move para a esquerda', por exemplo, mas também verifica todas as teclas, 30 a 60 vezes por segundo.

Então tentei outro sistema que apenas procurava as chaves que queria.

std::map< unsigned char, Key> keyMap; //Key stores the keycode, and whether it's been pressed. Then, I declare a load of const unsigned char called 'Quit' or 'PlayerLeft'.
input->BindKey(Keys::PlayerLeft, KeyCode::A); //so now you can check if PlayerLeft, rather than if A.

No entanto, o problema é que agora não posso digitar um nome, por exemplo, sem precisar vincular todas as chaves.

Então, eu tenho o segundo problema, pelo qual não consigo realmente pensar em uma boa solução para:

Entrada de envio

Agora eu sei que a tecla A foi pressionada ou que playerLeft é verdadeiro. Mas como eu vou daqui?

Pensei em apenas checar if(input->IsKeyDown(Key::PlayerLeft) { player.MoveLeft(); }
Isto acopla muito a entrada às entidades, e acho isso bastante confuso. Prefiro que o jogador lide com seu próprio movimento quando for atualizado. Eu pensei que algum tipo de sistema de eventos poderia funcionar, mas não sei como fazê-lo. (Ouvi sinais e slots eram bons para esse tipo de trabalho, mas aparentemente é muito lento e não consigo ver como se encaixaria).

Obrigado.

O Pato Comunista
fonte

Respostas:

7

O que eu faria é usar o padrão observador e ter uma classe de entrada que mantém uma lista de retornos de chamada ou objetos de manipulação de entrada. Outros objetos podem se registrar no sistema de entrada para serem notificados quando certas coisas acontecem. Você pode registrar diferentes tipos de retornos de chamada, com base no tipo de eventos de entrada sobre os quais os observadores gostariam de ser notificados. Pode ser um nível tão baixo quanto a 'tecla x está para baixo / para cima' ou mais específica do jogo, como 'aconteceu um evento de salto / tiro / movimento'.

Geralmente, os objetos de jogo têm um componente de manipulação de entrada que é responsável por se registrar na classe de entrada e fornecer métodos para manipular os eventos nos quais está interessado. Para movimento, eu tenho um componente de movimentação de entrada que possui um método de movimentação (x, y). Ele se registra no sistema de entrada e é notificado sobre eventos de movimento (onde x e y seriam -1 e 0 para significar movimento para a esquerda), o componente movedor muda suas coordenadas do objeto pai do jogo com base na direção do movimento e na velocidade do objeto.

Não sei se essa é uma boa solução, mas funciona para mim até agora. Em particular, ele permite que eu forneça diferentes tipos de sistema de entrada que mapeiam os mesmos eventos lógicos do jogo, para que o botão x do gamepad e o espaço do teclado gerem um evento de salto, por exemplo (é um pouco mais complicado que isso, permitindo que o usuário reatribua a tecla mapeamentos).

Firas Assaad
fonte
Isso definitivamente me interessa; o componente de entrada se registraria para determinados eventos (algo como playerInput registra 'left', 'right' etc.) ou todos os componentes de entrada registrados receberiam todas as mensagens?
O pato comunista
Realmente depende da sua exigência e de como você deseja implementá-la. No meu caso, os ouvintes se registram para eventos específicos e implementam a interface apropriada do manipulador de entrada. Ter todos os componentes de entrada recebendo todas as mensagens deve funcionar, mas o formato da mensagem precisa ser mais genérico do que o que tenho.
Firas Assaad 22/08/10
6

Eu acho que a discussão do OP está reunindo várias coisas e que o problema pode ser simplificado substancialmente, dividindo-o um pouco.

Minha sugestão:

Mantenha sua matriz de estados de chaves. Não há uma chamada de API para obter todo o estado do teclado de uma só vez? (A maior parte do meu trabalho é em consoles, e mesmo no Windows para um controlador conectado, você obtém todo o estado do controlador de uma só vez.) Os estados das teclas são um mapa "rígido" nas teclas do teclado. Não traduzido é o melhor, mas, no entanto, você obtém as APIs com mais facilidade.

Em seguida, mantenha algumas matrizes paralelas que indicam se a chave subiu nesse quadro, outra indicando que ela desceu e uma terceira para indicar simplesmente que a chave está "inativa". Talvez ative um cronômetro para que você possa acompanhar quanto tempo a chave está em seu estado atual.

Em seguida, crie um método para criar um mapeamento de chaves para ações. Você vai querer fazer isso algumas vezes. Uma vez para as coisas da interface do usuário (e tenha o cuidado de consultar os mapeamentos do Windows, porque você não pode assumir o scancode ao pressionar 'A' atribuirá um A no computador do usuário). Outro para cada "modo" no jogo. Estes são os mapeamentos de códigos de chave para ações. Você pode fazer isso de duas maneiras: uma seria manter uma enumeração usada pelo sistema receptor; outra seria um ponteiro de função indicando a função a ser chamada em uma mudança de estado. Talvez até um híbrido dos dois, dependendo de seus objetivos e necessidades. Com esses "modos", você pode empurrá-los e colocá-los em uma pilha do modo do controlador, com sinalizadores que permitem que entradas ignoradas continuem descendo pela pilha ou o que for.

Finalmente, lide com essas ações-chave de alguma forma. Para movimento, você pode querer fazer algo complicado, traduza 'W' para baixo para significar "avançar", mas não precisa ser uma solução binária; você pode fazer com que "W esteja inativo" signifique "aumentar a velocidade em X até um máximo de Y" e "W esteja ativo" para "diminuir a velocidade em Z até atingir zero". De um modo geral, você deseja que sua "interface de manipulação de controlador nos sistemas de jogo" seja bastante estreita; faça toda a tradução das chaves em um só lugar e use os resultados em qualquer outro lugar. Isso contrasta com a verificação direta para ver se a barra de espaço é pressionada em qualquer lugar aleatório do código, porque se estiver em algum local aleatório, provavelmente será atingido em algum momento aleatório quando você não quiser, e você simplesmente não não quero lidar com isso ...

Eu realmente detesto projetar padrões e acho que os componentes trazem mais custo do que bom desenvolvimento, por isso não mencionei nenhum deles. Os padrões surgirão se eles estiverem destinados, assim como os componentes, mas começar a fazê-lo desde o início apenas complicará sua vida.

traço-tom-bang
fonte
Você não acha que é meio sujo vincular eventos a quadros gráficos? Eu acho que isso deve ser separado.
Jo Então,
Quero dizer, eu entendo que em quase todos os casos o player será mais lento ao inserir botões do que a placa gráfica emitirá quadros, mas na verdade eles devem ser independentes e, na verdade, são para outros tipos de eventos, como os recolhidos na rede .
Jo
De fato; não há razão para que o polling do teclado (ou outro dispositivo de entrada) não possa ser executado a uma taxa diferente da tela. De fato, se você pesquisar entradas a 100Hz, poderá fornecer uma experiência de controle do usuário realmente consistente, independentemente da frequência de atualização do hardware de vídeo. Você terá que ser um pouco mais inteligente sobre como lida com seus dados (com relação à trava etc.), mas isso facilitará a consistência de coisas como gestos.
dash-tom-bang
3

No SFML, você tem as teclas na ordem em que foram pressionadas com o tipo de evento KeyPressed. Você processa cada chave por um tempo (getNextEvent ()).

Portanto, se a tecla pressionada estiver no seu mapa Key-> Action, execute a ação correspondente e, se não estiver, encaminhe-a para qualquer widget / material que possa precisar.

Em relação à sua segunda pergunta, eu recomendo que você continue assim, pois facilita o ajuste na jogabilidade. Se você usar sinais ou outros, precisará fazer um slot para o "RunKeyDown" e outro para o "JumpKeySlot". Mas o que acontece se no seu jogo uma ação especial é realizada quando os dois são pressionados?

Se você quiser correlacionar entradas e entidades, você pode transformar sua entrada em um Estado (RunKey = true, FireKey = false, ...) e enviá-la ao jogador como você envia qualquer outro evento para suas IAs.

Calvin1602
fonte
Não estou lidando com eventos de teclado com sf :: Event, e sim sf :: Input, porque é a detecção em tempo real IIRC. Tentei mantê-lo o mais independente possível da API. : P (A questão, que é)
A Comunista pato
1
Mas acho que o argumento de Calvin1602 é que sua afirmação original na pergunta "Preciso pesquisar, em vez de fazer retornos de chamada devido a limitações da API (SFML)" não é verdadeira; você tem eventos, para emitir um retorno de chamada ao lidar com um evento.
Kylotan
3

A solução correta geralmente é uma combinação dos dois métodos que você discute:

Para a funcionalidade de jogo com um conjunto predefinido de chaves, a pesquisa de todos os quadros é completamente apropriada e você deve pesquisar apenas as chaves que estão realmente ligadas a alguma coisa. Na verdade, isso é análogo ao modo como você consultaria a entrada do controlador em um jogo de console; ele será inteiramente pesquisado, especialmente quando se trata de dispositivos de entrada analógica. Durante o jogo em geral, você provavelmente deseja ignorar os eventos de pressionamento de tecla e usar a pesquisa.

Quando se trata de digitar entradas para coisas como nomes, você deve mudar o jogo para um modo diferente de manipulação de entradas. Por exemplo, assim que o prompt "digite seu nome" aparecer, você poderá modificar o que o código de entrada faz. Ele pode escutar eventos de pressionamento de tecla por meio de alguma interface de retorno de chamada ou pode iniciar a busca de cada tecla, se necessário. Acontece que, normalmente, durante a digitação de eventos, você não se importa tanto com o desempenho, de modo que a pesquisa não será tão ruim.

Portanto, você deseja usar a pesquisa seletiva para entrada no jogo e a manipulação de eventos para entradas de digitação de nome (ou todas as pesquisas de teclado, se a manipulação de eventos não estiver disponível).

Ben Zeigler
fonte