Técnicas de gerenciamento de entrada em jogos grandes

16

Existe uma técnica padrão para gerenciar entradas em jogos grandes. Atualmente, no meu projeto, toda a manipulação de entrada é feita no loop do jogo, da seguinte forma:

while(SDL_PollEvent(&event)){
            switch(event.type){
                case SDL_QUIT:
                    exit = 1;
                    break;
                case SDL_KEYDOWN:
                    switch(event.key.keysym.sym){
                        case SDLK_c:
                            //do stuff
                            break;
                    }
                    break;
                case SDL_MOUSEBUTTONDOWN:
                    switch(event.button.button){
                        case SDL_BUTTON_MIDDLE:
                                //do stuff
                                break;
                            }
                    }
                    break;
            }

(Estou usando SDL, mas espero que a prática principal aplique também bibliotecas e estruturas). Para um projeto grande, essa não parece a melhor solução. Talvez eu tenha vários objetos, todos querendo saber o que o usuário pressionou, por isso faria mais sentido para esses objetos manipular a entrada. No entanto, nem todos podem manipular a entrada, pois, depois que um recebe um evento, ele é empurrado para fora do buffer do evento, para que outro objeto não receba essa entrada. Qual método é mais comumente usado para combater isso?

w4etwetewtwet
fonte
Com um gerente de eventos, você pode disparar um evento como entrada e deixar que todas as outras partes do seu jogo se registrem para eles.
Danijar
@danijar, o que exatamente você quer dizer com gerente de eventos, é possível fornecer um pseudo-código esqueleto para mostrar que tipo de coisa você está falando?
W4etwetewtwet
1
Escrevi uma resposta para elaborar sobre os gerenciadores de eventos, que são o caminho a percorrer para o tratamento de entradas para mim.
31713 danijar

Respostas:

12

Desde que solicitado pelo iniciador de tópicos, eu explico sobre os gerenciadores de eventos. Eu acho que essa é uma boa maneira de lidar com a entrada em um jogo.

Um gerenciador de eventos é uma classe global que permite registrar funções de retorno de chamada em chaves e acionar esses retornos. O gerenciador de eventos armazena funções registradas em uma lista particular agrupada por sua chave. Cada vez que uma tecla é acionada, todos os retornos de chamada registrados são executados.

Os retornos de chamada podem ser std::functionobjetos que podem conter lambdas. As chaves podem ser cadeias de caracteres. Como o gerente é global, os componentes do seu aplicativo podem se registrar nas chaves acionadas por outros componentes.

// in character controller
// at initialization time
Events->Register("Jump", [=]{
    // perform the movement
});

// in input controller
// inside the game loop
// note that I took the code structure from the question
case SDL_KEYDOWN:
    switch(event.key.keysym.sym) {
    case SDLK_c:
        Events->Fire("Jump");
        break;
    }
    break;

Você pode até estender esse gerenciador de eventos para permitir a passagem de valores como argumentos adicionais. Modelos C ++ são ótimos para isso. Você pode usar esse sistema para, digamos, que um "WindowResize"evento passe o tamanho da nova janela, para que os componentes de escuta não precisem buscá-lo. Isso pode reduzir um pouco as dependências de código.

Events->Register<int>("LevelUp", [=](int NewLevel){ ... });

Eu implementei uma manjedoura de evento para o meu jogo. Se você estiver interessado, postarei o link no código aqui.

Usando um gerenciador de eventos, você pode transmitir com facilidade informações de entrada no seu aplicativo. Além disso, isso permite uma ótima maneira de permitir que o usuário personalize as combinações de teclas. Os componentes ouvem eventos semânticos em vez de chaves diretamente (em "PlayerJump"vez de "KeyPressedSpace"). Em seguida, você pode ter um componente de mapeamento de entrada que escute "KeyPressedSpace"e desencadeie qualquer ação que o usuário vincule a essa chave.

danijar
fonte
4
Ótima resposta, obrigado. Embora eu adorasse ver o código, não quero copiá-lo, portanto, não vou pedir para você publicá-lo até que eu tenha implementado o meu, pois aprenderei mais dessa maneira.
W4etwetewtwet
Eu só pensei em algo, posso passar qualquer função membro como esta, ou a função de registro não terá que tomar AClass :: func, limitando-o a funções membro aulas particulares
w4etwetewtwet
Essa é a grande coisa sobre expressões lambda em C ++. Você pode especificar uma cláusula de captura [=]e as referências a todas as variáveis ​​locais acessadas a partir do lambda serão copiadas. Portanto, você não precisa passar por um ponteiro ou algo assim. Mas observe que você não pode armazenar lambdas com cláusula de captura em ponteiros de função C antigos . No entanto, o C ++ std::functionfunciona bem.
31513 danijar
std :: função é muito lenta
TheStatehz
22

Divida isso em várias camadas.

Na camada mais baixa, você tem eventos de entrada brutos do sistema operacional. Entrada de teclado SDL, entrada de mouse, entrada de joystick, etc. Você pode ter várias plataformas (SDL é um denominador menos comum, sem várias formas de entrada, por exemplo, com as quais você pode se interessar mais tarde).

Você pode abstraí-los com um tipo de evento personalizado de nível muito baixo, como "botão do teclado pressionado" ou algo semelhante. Quando sua camada de plataforma (loop de jogo SDL) recebe entrada, ela deve criar esses eventos de baixo nível e encaminhá-los para um gerenciador de entrada. Isso pode ser feito com chamadas de método simples, funções de retorno de chamada, um sistema de eventos complicado, o que você mais gosta.

O sistema de entrada agora tem a tarefa de converter entradas de baixo nível em eventos lógicos de alto nível. A lógica do jogo nem liga para o fato de o ESPAÇO ter sido pressionado. Importa-se que JUMP tenha sido pressionado. O trabalho do gerente de entrada é coletar esses eventos de baixo nível e gerar eventos de alto nível. É responsável por saber que a barra de espaço e o botão 'A' do gamepad são mapeados para o comando lógico Jump. Ele lida com controles de aparência de gamepad e mouse e assim por diante. Ele emite eventos lógicos de alto nível os mais abstratos possíveis dos controles de baixo nível (existem algumas limitações aqui, mas você pode abstrair completamente as coisas no caso comum).

Seu controlador de personagem recebe esses eventos e processa esses eventos de entrada de alto nível para realmente responder. A camada da plataforma enviou o evento "Barra de espaço para baixo". O sistema de entrada recebeu isso, analisa suas tabelas de mapeamento / lógica e envia o evento "Salto pressionado". O controlador de lógica / personagem do jogo recebe esse evento, verifica se o jogador está realmente autorizado a pular e, em seguida, emite o evento "O jogador pulou" (ou apenas provoca um salto diretamente), que o restante da lógica do jogo usa para fazer o que quer .

Qualquer coisa dependente da lógica do jogo entra no controle do jogador. Qualquer coisa dependente do SO entra na camada da plataforma. Todo o resto vai para a camada de gerenciamento de entrada.

Aqui está uma arte ASCII amadora para descrever isso:

-----------------------------------------------------------------------
Platform Abstraction | Collect and forward OS input events
-----------------------------------------------------------------------
                          | |
                          | |
                         \   /
                          \_/
-----------------------------------------------------------------------
    Input Manager    | Translate OS input events into logical events
-----------------------------------------------------------------------
                          | |
                          | |
                         \   /
                          \_/
-----------------------------------------------------------------------
Character Controller | React to logical events and affect game play
-----------------------------------------------------------------------
                          | |
                          | |
                         \   /
                          \_/
-----------------------------------------------------------------------
      Game Logic     | React to player actions and provides feedback
-----------------------------------------------------------------------
Sean Middleditch
fonte
Arte ASCII legal, mas não tão necessária, me desculpe. Sugiro usar uma lista numerada. Enfim, uma boa resposta!
danijar 23/07/2013
1
@danijar: Eh, eu estava experimentando, nunca tinha tentado extrair uma resposta antes. Mais trabalho do que valeu a pena, mas muito menos trabalho do que lidar com um programa de pintura. :)
Sean Middleditch
Tudo bem, compreensível :-)
danijar
8
Pessoalmente, prefiro a arte ASCII muito mais do que uma lista numerada e chata.
precisa
@JesseEmond Ei, aqui para arte?
31513 danijar