¿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?
Respuestas:
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.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.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.fuente
[=]
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::function
funciona bien.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:
fuente