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