Tecniche di gestione dell'input in giochi di grandi dimensioni


16

Esiste una tecnica standard per la gestione dell'input nei giochi di grandi dimensioni. Attualmente, nel mio progetto, tutta la gestione degli input viene eseguita nel loop di gioco, in questo modo:

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;
            }

(Sto usando SDL, ma mi aspetto che la pratica principale applichi anche librerie e framework). Per un grande progetto questa non sembra la soluzione migliore. Potrei avere diversi oggetti che vogliono tutti sapere cosa ha premuto l'utente, quindi avrebbe più senso per quegli oggetti gestire l'input. Tuttavia, non tutti possono gestire l'input, poiché dopo che uno ha ricevuto un evento, verrà rimosso dal buffer degli eventi, quindi un altro oggetto non riceverà quell'input. Quale metodo è più comunemente usato per contrastare questo?


Con un gestore di eventi, puoi lanciare l'evento in input e lasciare che tutte le altre parti del tuo gioco si registrino su di loro.
danijar,

@danijar cosa intendi esattamente per gestore di un evento, è possibile fornire uno pseudo codice scheletro per mostrare di che tipo di cosa stai parlando?
w4etwetewtwet,


1
Ho scritto una risposta per approfondire i gestori di eventi, che sono la strada da percorrere per la gestione degli input per me.
danijar,

Risposte:


12

Da quando è stato chiesto dall'avviatore del thread, approfondisco i gestori di eventi. Penso che questo sia un buon modo per gestire l'input in un gioco.

Un gestore eventi è una classe globale che consente sia di registrare le funzioni di callback sui tasti sia di attivare tali callback. Il gestore eventi memorizza le funzioni registrate in un elenco privato raggruppato per chiave. Ogni volta che viene attivato un tasto, vengono eseguiti tutti i callback registrati.

I callback potrebbero essere std::functionoggetti che possono contenere lambda. Le chiavi potrebbero essere stringhe. Poiché il gestore è globale, i componenti dell'applicazione possono essere registrati su chiavi attivate da altri componenti.

// 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;

È anche possibile estendere questo gestore eventi per consentire il passaggio di valori come argomenti aggiuntivi. I modelli C ++ sono fantastici per questo. Potresti usare un tale sistema per, per esempio, per un "WindowResize"evento passare la nuova dimensione della finestra, in modo che i componenti di ascolto non debbano recuperarli da soli. Ciò può ridurre un po 'le dipendenze del codice.

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

Ho implementato un tale gestore di eventi per il mio gioco. Se sei interessato, posterò il link al codice qui.

Utilizzando un gestore eventi, è possibile trasmettere facilmente informazioni di input all'interno dell'applicazione. Inoltre, questo consente un modo piacevole per consentire all'utente di personalizzare le combinazioni di tasti. I componenti ascoltano gli eventi semantici invece delle chiavi direttamente ( "PlayerJump"anziché "KeyPressedSpace"). Quindi puoi avere un componente di mappatura di input che ascolta "KeyPressedSpace"e attiva qualsiasi azione l'utente ha associato a quella chiave.


4
Ottima risposta, grazie. Anche se mi piacerebbe vedere il codice, non voglio copiarlo, quindi non ti chiederò di pubblicarlo fino a quando non avrò implementato il mio, poiché imparerò di più in questo modo.
w4etwetewtwet,

Ho appena pensato a qualcosa, posso passare qualsiasi funzione membro in questo modo, o la funzione register non deve prendere AClass :: func, limitandolo a funzioni membro di una classe
w4etwetewtwet

Questa è la cosa grandiosa delle espressioni lambda in C ++, è possibile specificare una clausola di acquisizione [=]e i riferimenti a tutte le variabili locali a cui si accede da lambda verranno copiati. Quindi non devi passare un puntatore o qualcosa del genere. Ma nota che non è possibile memorizzare lambda con clausola di cattura nei vecchi puntatori a funzione C . Tuttavia, il C ++ std::functionfunziona bene.
danijar,

std :: function è molto lenta
TheStatehz,

22

Dividi questo in più livelli.

Al livello più basso si hanno eventi di input non elaborati dal sistema operativo. Input da tastiera SDL, input da mouse, input da joystick, ecc. Potresti avere diverse piattaforme (SDL è un minimo comune denominatore privo di diverse forme di input, ad esempio, di cui potresti preoccuparti in seguito).

Puoi astrarli con un tipo di evento personalizzato di livello molto basso, come "pulsante della tastiera in basso" o simili. Quando il livello della piattaforma (loop di gioco SDL) riceve input, dovrebbe creare questi eventi di basso livello e quindi inoltrarli a un gestore input. Può farlo con semplici chiamate di metodo, funzioni di callback, un sistema di eventi complicato, qualunque cosa ti piaccia di più.

Il sistema di input ha ora il compito di tradurre input di basso livello in eventi logici di alto livello. La logica di gioco non si preoccupa affatto che SPACE sia stato premuto. Si preoccupa che JUMP sia stato premuto. Il compito del gestore dell'input è raccogliere questi eventi di input di basso livello e generare eventi di input di alto livello. È responsabile di sapere che la barra spaziatrice e il pulsante del gamepad "A" sono entrambi associati al comando logico Jump. Si occupa di controlli del gamepad vs del mouse e così via. Emette eventi logici di alto livello che sono il più astratti possibile dai controlli di basso livello (ci sono alcune limitazioni qui, ma puoi astrarre completamente le cose nel caso comune).

Il controller del personaggio quindi riceve questi eventi ed elabora questi eventi di input di alto livello per rispondere effettivamente. Il livello della piattaforma ha inviato l'evento "Key down spacebar". Il sistema di input lo ha ricevuto, guarda le sue tabelle / logiche di mappatura e quindi invia l'evento "Pressed jump". La logica di gioco / controller del personaggio riceve quell'evento, verifica che il giocatore sia effettivamente autorizzato a saltare e quindi emette l'evento "Player jumped" (o fa semplicemente accadere direttamente un salto), che il resto della logica di gioco usa per fare qualunque cosa .

Tutto ciò che dipende dalla logica del gioco va nel controller del giocatore. Tutto ciò che dipende dal sistema operativo va a livello di piattaforma. Tutto il resto va nel livello di gestione dell'input.

Ecco alcune opere amatoriali ASCII per descriverlo:

-----------------------------------------------------------------------
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
-----------------------------------------------------------------------

Fantastica arte ASCII, ma non così necessaria, mi dispiace. Suggerisco invece di utilizzare un elenco numerato. Comunque una buona risposta!
danijar,

1
@danijar: Eh, stavo sperimentando, non avevo mai provato a trovare una risposta prima. Più lavoro di quanto valesse, ma molto meno lavoro rispetto a un programma di pittura. :)
Sean Middleditch il

Va bene, comprensibile :-)
danijar il

8
Personalmente, preferisco l'arte ASCII molto più di un noioso elenco numerato.
Jesse Emond,

@JesseEmond Ehi, qui per l'arte?
danijar,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.