ATopeCode
Blog sobre Desarrollo de Software

miércoles, 9 de mayo de 2012

Controlando el teclado en XNA

Hace poco me di cuenta que en el menú principal de "Crazy Energy" la pulsación de las teclas iba muy deprisa. Cuando lo estaba programando se me ocurrió la idea de hacer que las teclas tuviesen una especie de "delay" o retardo igual que tienen los frames de una animación, pero como no era muy exagerada la velocidad de las teclas lo dejé para otro momento, que por fin ha llegado XD.

La idea es que cuando se pulse una tecla, ésta debe de estar pulsada un determinado nº de ciclos de juego para que su pulsación sea efectiva, es decir, que no dependa directamente de los frames por segundo del juego. Con lo cual, una tecla puede estar pulsada físicamente, pero que su pulsación no sea efectiva si en ese ciclo de juego no superó el retardo (delay).
La pulsación se hace efectiva cuando la tecla está pulsada físicamente y además se supera el nº de ciclos de retardo, con lo cual la pulsación se hace efectiva para ese único ciclo. Si el retardo de una tecla es 0, tan pronto como la tecla sea pulsada físicamente, se producirá la pulsación efectiva, que es lo que pasa normalmente.

Si en el juego solo controlamos si la tecla está pulsada o no, cuanto más rápido vaya el juego (fps), más comprobaciones se harán por segundo y la tecla hará más pulsaciones efectivas por segundo. Para entenderlo mejor, lo que me pasaba en el menú del Crazy Energy es que con solo darle un toquecito a la tecla del cursor me avanzaba por todas las opciones del menú directamente, no me daba tiempo a poner el cursor en la opción que estaba en el medio. Para que esto no suceda podía bajar los fps del juego (mala idea XD), o utilizar un retardo (delay) para las teclas.

Ya que estoy con lo del retardo de las teclas, de paso voy a hacer también que una tecla sea de "única pulsación". Con "única pulsación" me refiero a que la tecla se pulsa, mientras esté pulsada se esperan los ciclos indicados por su delay (retardo) y cuando su pulsación se haga efectiva, aunque la tecla quede pulsada, no volverá a ser efectiva hasta que se suelte y se vuelva a pulsar (y vuelva a transcurrir el retardo).
El caso contrario a una tecla de "única pulsación" es que la tecla se pulsa, mientras esté pulsada se espera el retardo de ciclos, su pulsación se hace efectiva al acabar el retardo y si la tecla sigue pulsada, se vuelve a esperar el retardo de ciclos y se vuelve a hacer su pulsación efectiva sucesivamente hasta que se suelte la tecla.

Un ejemplo de una tecla de "única pulsación" puede ser una tecla para disparar en un juego. Que si se pulsa la tecla nuestro personaje lance un fogonazo, pero solo uno, y para lanzar otro hay que soltar la tecla y volver a pulsarla. Si la tecla no fuera de "única  pulsación", es como si el personaje llevase una ametralladora y durante todo el tiempo que la tecla esté pulsada estará disparando.

Descargar Librería para teclado

USO DE LA LIBRERÍA:


Primero hay que añadir la librería a nuestro proyecto, yo lo hago en Visual Studio 2010 pero en la mayoría de IDEs se hace de la misma manera.
Para ello en el explorador de soluciones hay que hacer click derecho sobre "References", seleccionamos la
opción "Agregar Referencia" y en la pestaña "Examinar" buscamos la ruta donde tengamos copiada nuestra librería. El nombre del archivo con la libería es "LibTecladoXNA.dll". Una vez agregada la librería a nuestras referencias, el visual studio crea una copia en la carpeta "bin\Debug" de nuestro proyecto para usarla siempre que se ejecute el juego.


La librería está dentro del namespaces "VGLib", para que sea más cómodo acceder al contenido de la librería es mejor poner:
using VGLib;
en cada clase donde vayamos a usar la librería.

La libería está formada por 3 clases:

Teclado: Es una clase contenedora de objetos Tecla_Delay y Tecla_One_Hit. Si en nuestro juego usamos
muchas teclas, en vez de tener un objeto por tecla y tener que actualizarlas una por una, es mejor meterlas
dentro de un objeto teclado y dejar que él las actualice y compruebe su estado por nosotros.

Tecla_Delay: Esta clase define un objeto para una tecla que tiene retardo, pero no es de una única pulsación.
Para hacer que la tecla no tenga retardo se pone el campo "delay" a 0.

Tecla_One_Hit: Esta clase define un objeto para una tecla que además de tener retardo, es de única pulsación.

En la clase principal del juego declaramos los siguientes campos:

public class Game1 : Microsoft.Xna.Framework.Game
{
   GraphicsDeviceManager graphics;
   SpriteBatch spriteBatch;

   Teclado teclado=new Teclado(); //Objeto para gestionar la teclas.
   KeyboardState teclado_state; //Estructura para almacenar estado del teclado.

En el método Load_Content() de nuestro juego XNA creamos el objeto para el teclado y le añadimos las teclas que queremos que controle:

protected override void LoadContent()
{
   // Create a new SpriteBatch, which can be used to draw textures.
   spriteBatch = new SpriteBatch(GraphicsDevice);

   //Añadimos la tecla "Enter" al teclado, no es tecla de única pulsación y
   //se le pone un delay/retardo de 15 ciclos de juego:
   teclado.AddKey(Keys.Enter, false, 15);

   //Ahora muestro otra forma de añadir teclas,creándolas previamente como objeto
   //Creo un objeto de tecla de "única pulsación" para la tecla "Enter" 
   //y añado el objeto al teclado. Como la tecla "Enter" ya está en el objeto
   //teclado, lo que pasa es que se borra la anterior y se almacena la actual.
   //Por lo tanto la tecla "Enter" quedará controlada como un tecla de única 
   //pulsación y sin retardo (delay=0):
   Tecla_One_Hit otra_tecla = new Tecla_One_Hit(Keys.Enter);
   teclado.AddKey(otra_tecla);
 
   //Añado la tecla "Space" como un tecla que no es de única pulsación
   //(solo delay) con un delay de 10 ciclos de juego. Y la tecla "A" 
   //de única pulsación con un delay de 5 ciclos.
   teclado.AddKey(Keys.Space, false, 10);
   teclado.AddKey(Keys.A, true, 5);

   //Para ver las teclas que gestiona el objeto teclado.
   teclado.Console_List(); 
}

Hay que tener en cuenta que siempre que se añada una tecla al teclado que éste ya tiene, la nueva tecla sustituye a la anterior. En el ejemplo se introduce 2 veces la tecla "Enter", la primera es una Tecla_Delay y la segunda una Tecla_One_Hit (única pulsación),  pero el teclado solo almacenará la configuración para la última introducida sustituyendo a la primera tecla "Enter".

Los valores que identifica a cada tecla son los mismos usa XNA por defecto, son valores
de la enumeración "Keys".

El método "Console_List()" muestra el contenido del objeto "teclado" pero lo hace por consola, al estar en modo ventana nuestro programa, el texto se visualizará en la pestaña "Resultados" que suele estar en la parte inferior del editor del Visual Studio 2010.



El motor de XNA en cada ciclo de juego llama al método "Update()" de nuestra clase "Game1", por lo tanto ahí es donde se debe actualizar el estado de las teclas.

protected override void Update(GameTime gameTime)
{
     //Actualizo el estado del teclado y de las teclas:
     teclado_state = Keyboard.GetState();
     teclado.Update(ref teclado_state);
           
     base.Update(gameTime);
}

"Teclado_state" es el campo que declaramos al inicio de nuestra clase "Game1" y se le pasa por referencia al método "Update()" del teclado ya que al ser una estructura consumirá menos tiempo de proceso al pasarla por referencia que por valor. Podría haberme ahorrado el campo "teclado_state" y escribir todo en una línea, pero creo que así se ve mejor como funciona todo.


Por último ya solo nos queda el método "Draw()" de nuestra clase "Game1", en el que para comprobar como funciona cada tecla hago cambiar el fondo de la pantalla en cada pulsación efectiva de las teclas. Así que cuando nuestras teclas lleguen a su retardo mientras están pulsadas, la pantalla cambiará de color durante 1 ciclo de juego para indicarnos que en ese ciclo la tecla se daba por pulsada.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);
           
    if(teclado.Pulsada(Keys.Enter) || teclado.Pulsada(Keys.Space) || teclado.Pulsada(Keys.A))
    {//Si alguna de las teclas da pulsación efectiva cambio el fondo durante este ciclo.
       GraphicsDevice.Clear(Color.AliceBlue);
    }

    base.Draw(gameTime);
}

Aunque en el código no lo tengo en cuenta, el método "Pulsada()" del objeto "teclado" lanza una excepción "TeclaNoEncontradaException" si preguntamos por una tecla que no está añadida en nuestro objeto "teclado". Cuando las teclas que controlemos en el juego sean un número considerable es buena opción capturar la excepción con un "try" "catch" por si acaso se nos va la pinza entre tanta tecla.

Pues esto es todo, espero que a alguien le sea de utilidad la librería. Basicamente es como usar el teclado de forma normal en XNA pero con la peculiaridad añadida de poder meterle retardo a las teclas y hacer que sean de única pulsación si nos hace falta.

No hay comentarios:

Publicar un comentario