Técnicas de manejo de entrada en juegos grandes

16

¿Existe una técnica estándar para gestionar la entrada en juegos grandes? Actualmente, en mi proyecto, todo el manejo de entrada se realiza en el ciclo del juego, así:

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

(Estoy usando SDL, pero espero que la práctica principal también aplique bibliotecas y marcos). Para un proyecto grande, esta no parece la mejor solución. Es posible que tenga varios objetos que quieran saber qué ha presionado el usuario, por lo que tendría más sentido que esos objetos manejen la entrada. Sin embargo, no todos pueden estar manejando la entrada, ya que después de que uno obtiene un evento, será expulsado del búfer de eventos, por lo que otro objeto no recibirá esa entrada. ¿Qué método se usa más comúnmente para contrarrestar esto?

w4etwetewtwet
fuente
Con un administrador de eventos, puedes disparar eventos en la entrada y dejar que todas las otras partes de tu juego se registren en ellos.
danijar
@danijar, ¿qué quiere decir exactamente con un administrador de eventos? ¿Es posible que pueda proporcionar un pseudocódigo esqueleto para mostrar de qué tipo de cosas está hablando?
w4etwetewtwet
1
Escribí una respuesta para elaborar sobre los administradores de eventos, que son el camino a seguir para el manejo de entradas para mí.
danijar

Respuestas:

12

Desde que me lo preguntó el iniciador de hilos, me explayé sobre los gestores de eventos. Creo que esta es una buena manera de manejar la entrada en un juego.

Un administrador de eventos es una clase global que permite registrar funciones de devolución de llamada en las teclas y activar esas devoluciones de llamada. El administrador de eventos almacena las funciones registradas en una lista privada agrupada por su clave. Cada vez que se dispara una tecla, se ejecutan todas las devoluciones de llamada registradas.

Las devoluciones de llamada podrían ser std::function objetos que pueden contener lambdas. Las llaves pueden ser cuerdas. Como el administrador es global, los componentes de su aplicación pueden registrarse en claves activadas desde otros 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;

Incluso podría extender este administrador de eventos para permitir pasar valores como argumentos adicionales. Las plantillas de C ++ son excelentes para esto. Podría usar dicho sistema para, por ejemplo, que un "WindowResize"evento pase el nuevo tamaño de la ventana, de modo que los componentes de escucha no necesiten recuperarlo ellos mismos. Esto puede reducir bastante las dependencias de código.

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

He implementado un administrador de eventos para mi juego. Si está interesado, publicaré el enlace al código aquí.

Con un administrador de eventos, puede transmitir fácilmente información de entrada dentro de su aplicación. Además, esto permite una buena manera de permitir que el usuario personalice las asociaciones de teclas. Los componentes escuchan eventos semánticos en lugar de claves directamente (en "PlayerJump"lugar de "KeyPressedSpace"). Entonces puede tener un componente de mapeo de entrada que escuche "KeyPressedSpace"y active cualquier acción que el usuario vincule a esa tecla.

danijar
fuente
44
Gran respuesta, gracias. Aunque me encantaría ver el código, no quiero copiarlo, por lo que no le pediré que lo publique hasta que haya implementado el mío, ya que aprenderé más de esa manera.
w4etwetewtwet
Me acaba de ocurrir algo, puedo pasar cualquier función miembro como este, o no quiere la función de registro que tomar AClass :: func, limitándola a las funciones miembro clases individuales
w4etwetewtwet
Eso es lo mejor de las expresiones lambda en C ++, puede especificar una cláusula de captura [=]y las referencias a todas las variables locales a las que se accede desde lambda se copiarán. Por lo tanto, no tiene que pasar este puntero o algo así. Pero tenga en cuenta que no puede almacenar lambdas con cláusula de captura en punteros de función C antiguos . Sin embargo, el C ++ std::functionfunciona bien.
danijar
std :: la función es muy lenta
TheStatehz
22

Divide esto en varias capas.

En la capa más baja tiene eventos de entrada sin procesar del sistema operativo. Entrada de teclado SDL, entrada de mouse, entrada de joystick, etc. Es posible que tenga varias plataformas (SDL es un denominador menos común que carece de varias formas de entrada, por ejemplo, que más tarde podría interesarle).

Puede abstraerlos con un tipo de evento personalizado de muy bajo nivel, como "botón de teclado abajo" o similar. Cuando su capa de plataforma (bucle de juego SDL) recibe información, debe crear estos eventos de bajo nivel y luego reenviarlos a un administrador de entrada. Puede hacer esto con llamadas a métodos simples, funciones de devolución de llamada, un sistema de eventos complicado, lo que más le guste.

El sistema de entrada ahora tiene el trabajo de traducir la entrada de bajo nivel en eventos lógicos de alto nivel. La lógica del juego no importa en absoluto que se haya presionado el ESPACIO. Le importa que se haya presionado JUMP. El trabajo del administrador de entrada es recopilar estos eventos de entrada de bajo nivel y generar eventos de entrada de alto nivel. Es responsable de saber que la barra espaciadora y el botón 'A' del gamepad se asignan al comando lógico Jump. Se trata de controles de aspecto de gamepad vs mouse, etc. Emite eventos lógicos de alto nivel que son lo más abstractos posible de los controles de bajo nivel (aquí hay algunas limitaciones, pero puede abstraer las cosas por completo en el caso común).

Su controlador de personaje luego recibe estos eventos y procesa estos eventos de entrada de alto nivel para responder realmente. La capa de plataforma envió el evento "Tecla abajo de la barra espaciadora". El sistema de entrada lo recibió, mira sus tablas / lógica de mapeo y luego envía el evento "Salto presionado". El controlador de lógica / personaje del juego recibe ese evento, verifica que el jugador realmente pueda saltar y luego emite el evento "El jugador saltó" (o simplemente provoca que ocurra un salto), que el resto de la lógica del juego usa para hacer lo que sea .

Todo lo que dependa de la lógica del juego va al controlador del jugador. Todo lo que depende del sistema operativo va en la capa de plataforma. Todo lo demás va a la capa de gestión de entrada.

Aquí hay un poco de arte amateur ASCII para describir esto:

-----------------------------------------------------------------------
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
fuente
Arte ASCII genial, pero no es necesario, lo siento. Sugiero usar una lista numerada en su lugar. De todos modos, una buena respuesta!
danijar
1
@danijar: Eh, estaba experimentando, nunca había intentado sacar una respuesta antes. Más trabajo de lo que valía, pero mucho menos trabajo que lidiar con un programa de pintura. :)
Sean Middleditch
Muy
8
Personalmente, prefiero el arte ASCII más que una aburrida lista numerada.
Jesse Emond el
@JesseEmond Hola, ¿aquí por el arte?
danijar