Entrada

Entrada

Resumen: Entrada a partir del mouse y teclado. Clase Input.

Hay dos maneras de manejar las entradas. Una es usando directamente los métodos de la clase Input y la otra es usando escuchadores (listeners) al estilo clásico de Java.

Ambas alternativas son útiles. Comenzaremos por la primera.

Clase Input.

La clase Input proporciona varios métodos para el manejo y recepción de datos desde el teclado y desde el mouse. Para usar esos métodos debemos obtener una instancia de la clase Input. Para obtener una instancia de la clase Input, le pedimos al GameContainer que nos la dé. Para ello debemos emplear el siguiente código:

private Input entrada; // Atributo entrada.

// ... otros métodos...

@Override
public void init(GameContainer container) throws SlickException {
   entrada = container.getInput();
}

//... Más métodos...

Observe la línea resaltada que es la más importante. Se llama al método getInput() de la clase GameContainer y obtenemos así una instancia de la clase Input. Una vez realizado esto pasamos a llamar a los métodos, de los cuales, existen 2 importantes para el manejo del teclado (también existen otros 2 para el mouse que lo veremos luego):

  • boolean isKeyDown (int código_de_tecla)
  • boolean isKeyPressed (int código_de_tecla)

El primer método (isKeyDown()) devuelve true si se está presionando la tecla, pero ¿qué tecla?... Bien, la tecla que se especifica mediante el código que le pasamos como parámetro (código_de_tecla). Cada tecla tiene asociado un código, entonces por ejemplo, si quisiéramos mostrar un mensaje mientras se esté presionando la tecla ESPACIO podríamos escribir lo siguiente:

@Override
public void update(GameContainer container, int delta) throws SlickException {
   if(entrada.isKeyDown(CODIGO_DE_LA_TECLA_ESPACIO)){
      System.out.println("Se está presionando la tecla ESPACIO.");
   }
}

Note que hemos escrito el código dentro del método update() pues esto es debido a que este método se llama continuamente hasta el fin del programa. También podría haberse escrito en el método render() pero conceptualmente no corresponde ya que éste último sólo se debería usar para dibujar.
Finalmente, falta determinar el código correspondiente de la tecla ESPACIO. Por suerte, la clase Input define un montón de constantes, una para cada tecla. Lo anterior se escribiría:

@Override
public void update(GameContainer container, int delta) throws SlickException {
   if(entrada.isKeyDown(Input.KEY_SPACE)){
      System.out.println("Se está presionando la tecla ESPACIO.");
   }
}

El segundo método (isKeyPressed()) devuelve true cada vez que se presiona la tecla pero solo cuando es presionada y NO cuando se la mantiene.

Ejemplo.

Ahora veremos un ejemplo para aclarar cualquier tipo de dudas. Vamos a desarrollar un programa que permita el manejo de un círculo mediante las flechas del teclado. Es decir, si mantenemos presionada la tecla FLECHA IZQUIERDA entonces el círculo se moverá para la izquierda tanto tiempo como la tecla estuvo presionada.
Antes de seguir es preciso recordar algunos aspectos básicos de cinemática.

La ecuación típica de la velocidad de una partícula es:

v = x / t

Donde x es la distancia y t el tiempo, es decir, que la distancia dividido el tiempo es igual a la velocidad de la partícula. En nuestro caso, es útil definir la ecuación en forma diferencial:

v = dx / dt

Recordar que el diferencial implica pequeños cambios, es decir, la velocidad se define como el cambio en x con respecto a t.
Luego de esto, despejamos dx:

dx = v * dt

Esta última fórmula es la que vamos a usar más adelante en nuestro código.

Aquí vamos a dar una pausa. Usted puede preguntarse para qué usamos esas ecuaciones si podríamos resolver el problema de la siguiente manera: si se presiona la FLECHA IZQUIERDA entonces muevo el círculo un poquito a la izquierda, si se presiona la FLECHA DERECHA entonces muevo el círculo un poquito a la derecha y así. Si codificamos esto en el método update() entonces el problema estaría resuelto. Esto parece ser una buena idea (y muy intuitiva). Sin embargo, existe un problema. El mismo está en que el método update() se llama continuamente y cada cierto tiempo (en milisegundos). El tiempo entre llamadas viene dado en el parámetro delta y varía de computadora en computadora. Entonces, podría darse el caso que, en una computadora, el método update() se llame cada 2 ms mientras que en otra computadora se llame cada 10 ms. Entonces supongamos, luego de 20 ms, la primer computadora movíó al círculo 10 veces mientras que la segunda sólo lo movío 2 veces. Por lo tanto, da la sensación que la velocidad del círculo en la primer computadora es mayor que en la segunda.
Y no sólo termina aquí el problema. Además, el tiempo entre llamadas puede variar dentro de la misma computadora, por lo que en un momento el círculo puede moverse a gran velocidad mientras que en otro momento no.

Para resolver el anterior problema entonces debemos aplicar la ecuación vista.

Retomando el ejemplo, vamos a definir exactamente lo qué va a hacer nuestro programa:

  • Crear un círculo que se pueda manejar con las teclas cursoras.
  • Declarar e inicializar la VELOCIDAD a la cual se va a mover la bola.
  • Calcular los límites, es decir, hasta dónde se puede desplazar el círculo. Existirán 4 límites: el superior, el derecho, el inferior y el izquierdo.

Una vez dejado en claro lo qué va a hacer nuestro programa pasaré a explicar cómo lo vamos a hacer.
Como era de esperar vamos a declarar algunos atributos necesarios:

import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Circle;

public class MiJuego extends BasicGame {

private float limiteIzquierdo, limiteSuperior, limiteDerecho,
limiteInferior;

private final float VELOCIDAD = 0.3f;

private Circle circulo;
private float x, y;

private Input entrada;
private AppGameContainer contenedor;

public MiJuego() throws SlickException {
   super("Mover círculo.");
   contenedor = new AppGameContainer(this);
   contenedor.setShowFPS(false);
   contenedor.setDisplayMode(800, 600, false);
   contenedor.start();
}
@Override
public void init(GameContainer container) throws SlickException {
}

@Override
public void update(GameContainer container, int delta) throws SlickException {
}

@Override
public void render(GameContainer container, Graphics g) throws SlickException {
}
}

Concéntrese en los atributos. Lo primero que declaramos fueron los límites. Todavía no lo hemos inicializado, pues eso lo haremos después. Luego hemos declarado e inicializado la velocidad a un valor de 0.3 píxeles por milisegundo (note las unidades). Lo siguiente fue declarar el círculo y la posición (x, y) del mismo. Por último, declaramos la entrada y el contenedor.
En el constructor no hay nada nuevo.
El método init() está vacío por el momento, pero acá es dónde debemos:

  • Obtener la instancia de la clase Input (entrada).
  • Crear el círculo.
  • Crear los límites.

El método se muestra a continuación:

@Override
public void init(GameContainer container) throws SlickException {
   // Obtenemos la entrada.
   entrada = container.getInput();

   // Creamos el círculo.
   this.x = 100;
   this.y = 100;
   circulo = new Circle(this.x, this.y, 20);

   // Calcular los límites.
   calcularLimites();
}

Observe que se llama a un método para que realice el cálculo de los límites (calcularLimites()). Esto sirve para evitar que la bola salga de la ventana. Por el momento solo declararemos el método:

public void calcularLimites() {
}

Ahora pasaremos a codificar el método update(). Recuerde que en este método se debe manejar las entradas del teclado. Lo primero que debemos hacer es agregar algunas sentencias condicionales (if) para determinar qué tecla está presionada y de acuerdo a cuál sea actuar en consecuencia.

@Override
public void update(GameContainer container, int delta) throws SlickException {
   if (entrada.isKeyDown(Input.KEY_LEFT)) {
      // Mover el círculo a la izquierda.
      circulo.setCenterX(circulo.getCenterX() - delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_RIGHT)) {
      // Mover el círculo a la derecha.
      circulo.setCenterX(circulo.getCenterX() + delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_DOWN)) {
      // Mover el círculo hacia abajo.
      circulo.setCenterY(circulo.getCenterY() + delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_UP)) {
      // Mover el círculo hacia arriba.
      circulo.setCenterY(circulo.getCenterY() - delta * VELOCIDAD);
   }
}

Note que hay 4 condiciones pero no están anidadas. Cada una es independiente de la otra, pues esto permitirá que el círculo pueda moverse en diagonal.
Si la tecla presionada es la FLECHA IZQUIERDA entonces el círculo se moverá hacia la izquierda una pequeña distancia, pero ¿qué distancia?. Ahora es momento de recordar la fórmula de la distancia:

dx = v * dt

Es decir, una pequeña distancia va a estar dada por la velocidad (en nuestro caso es 0.3 píx / ms) por el diferencial del tiempo (delta). Recordar que el valor de delta está dado en milisegundos.
Entonces, la nueva posición del círculo consta de la posición actual más un pequeño incremento de distancia. Observe que esta pequeña distancia que recorre va a depender del tiempo (delta), obviamente de la velocidad y del sentido de movimiento (es decir, de la FLECHA PRESIONADA). Si se pretende que el círculo suba entonces deberíamos restar una pequeña distancia para que el mismo se traslade hacia arriba. Recuerde que el origen de las coordenadas es en el extremo izquierdo superior. Por esta razón, el restar en el eje y, implica que el círculo suba. Puede verse inmediatamente que sumar en el eje y, implica que baje el círculo. Y por último sumar en el eje x, implica traslado hacia la derecha, y restar implica traslado hacia la izquierda.

Luego de actualizar la posición del círculo pasamos a dibujarlo en el método render():

@Override
public void render(GameContainer container, Graphics g) throws SlickException {
   g.fill(circulo);
}

Podemos decir que casi terminamos. Nos falta definir los límites para que la bola no pueda desplazarse tanto que se vaya de nuestra vista. El siguiente método calcula esos límites:


public void calcularLimites() {
   this.limiteIzquierdo = circulo.getRadius();
   this.limiteSuperior = circulo.getRadius();
   this.limiteDerecho = contenedor.getWidth() - circulo.getRadius();
   this.limiteInferior = contenedor.getHeight() circulo.getRadius();
}

Analice usted mismo el método anterior. Luego en el método update() agregamos:

@Override
public void update(GameContainer container, int delta) throws SlickException {
   if (entrada.isKeyDown(Input.KEY_LEFT)) {
      // Mover el círculo a la izquierda.
      circulo.setCenterX(circulo.getCenterX() - delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_RIGHT)) {
      // Mover el círculo a la derecha.
      circulo.setCenterX(circulo.getCenterX() + delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_DOWN)) {
      // Mover el círculo hacia abajo.
      circulo.setCenterY(circulo.getCenterY() + delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_UP)) {
      // Mover el círculo hacia arriba.
      circulo.setCenterY(circulo.getCenterY() - delta * VELOCIDAD);
   }

   if (circulo.getCenterX() < limiteIzquierdo) {
      circulo.setCenterX(limiteIzquierdo);
   }
   if (circulo.getCenterX() > limiteDerecho) {
      circulo.setCenterX(limiteDerecho);
   }
   if (circulo.getCenterY() > limiteInferior) {
      circulo.setCenterY(limiteInferior);
   }
   if (circulo.getCenterY() < limiteSuperior) {
      circulo.setCenterY(limiteSuperior);
   }
}

A continuación se brinda el código completo:

import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Circle;
public class MiJuego extends BasicGame {

private float limiteIzquierdo, limiteSuperior, limiteDerecho,
limiteInferior;

private final float VELOCIDAD = 0.3f;

private Circle circulo;
private float x, y;

private Input entrada;
private AppGameContainer contenedor;

public MiJuego() throws SlickException {
   super("Mover círculo.");
   contenedor = new AppGameContainer(this);
   contenedor.setShowFPS(false);
   contenedor.setDisplayMode(800, 600, false);
   contenedor.start();
}

public void calcularLimites() {
   this.limiteIzquierdo = circulo.getRadius();
   this.limiteSuperior = circulo.getRadius();
   this.limiteDerecho = contenedor.getWidth() -circulo.getRadius();
   this.limiteInferior = contenedor.getHeight() - circulo.getRadius();
}

@Override
public void init(GameContainer container) throws SlickException {
   entrada = container.getInput();
   this.x = 100;
   this.y = 100;
   circulo = new Circle(this.x, this.y, 20);
   container.getGraphics().setBackground(Color.gray);
   calcularLimites();
}

@Override
public void update(GameContainer container, int delta) throws SlickException {
   if (entrada.isKeyDown(Input.KEY_LEFT)) {
      circulo.setCenterX(circulo.getCenterX() - delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_RIGHT)) {
      circulo.setCenterX(circulo.getCenterX() + delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_DOWN)) {
      circulo.setCenterY(circulo.getCenterY() + delta * VELOCIDAD);
   }
   if (entrada.isKeyDown(Input.KEY_UP)) {
      circulo.setCenterY(circulo.getCenterY() - delta * VELOCIDAD);
   }

   if (circulo.getCenterX() < limiteIzquierdo) {
      circulo.setCenterX(limiteIzquierdo);
   }
   if (circulo.getCenterX() > limiteDerecho) {
      circulo.setCenterX(limiteDerecho);
   }
   if (circulo.getCenterY() > limiteInferior) {
      circulo.setCenterY(limiteInferior);
   }
   if (circulo.getCenterY() < limiteSuperior) {
      circulo.setCenterY(limiteSuperior);
   }
}

@Override
public void render(GameContainer container, Graphics g) throws SlickException {
   g.fill(circulo);
}

public static void main(String arg[]){
   try{
      new MiJuego();
   }catch(SlickException e){
   }
}

}

No hay comentarios:

Publicar un comentario