LIBGDX As interfaces para capturar eventos

De MediaWiki
Ir a la navegación Ir a la búsqueda

UNIDADE 2: Interface de xestión de eventos

As interfaces

Información na wiki: https://github.com/libgdx/libgdx/wiki/Event-handling

O motor libgdx ten dúas interfaces para xestionar todos os eventos:

  • InputProcessor: Xestiona os eventos de pulsación de teclas, pulsación sobre a pantalla (móbil), e arrastre do dedo pola pantalla (móbil)...
  • GestureListener: Xestiona outro tipo de eventos coma son os de dobre pulsación (evento tap), o clásico movemento con dous dedos para facer un zoom da pantalla (evento zoom)....Esta interface está explicada na Unidade 3: Xestión de Eventos: GestureListener.


Para engadir unha interface temos que utilizar a palabra implements na definición da clase igual que fixemos coa interface Screen. Ó facelo vos dará un erro sobre o nome da clase. Se situades o rato enriba do erro aparecerá unha opción de Add unimplimented methods que debemos escoller.

Código da clase PantallaXogo
Obxectivo: engadir unha interface de xestión de eventos.

 1 public class PantallaXogo implements Screen,InputProcessor {
 2         ...............
 3 	@Override
 4 	public boolean keyDown(int keycode) {
 5 		// TODO Auto-generated method stub
 6 		return false;
 7 	}
 8 
 9 	@Override
10 	public boolean keyUp(int keycode) {
11 		// TODO Auto-generated method stub
12 		return false;
13 	}
14 
15 	@Override
16 	public boolean keyTyped(char character) {
17 		// TODO Auto-generated method stub
18 		return false;
19 	}
20 
21 	@Override
22 	public boolean touchDown(int screenX, int screenY, int pointer, int button) {
23 		// TODO Auto-generated method stub
24 		return false;
25 	}
26 
27 	@Override
28 	public boolean touchUp(int screenX, int screenY, int pointer, int button) {
29 		// TODO Auto-generated method stub
30 		return false;
31 	}
32 
33 	@Override
34 	public boolean touchDragged(int screenX, int screenY, int pointer) {
35 		// TODO Auto-generated method stub
36 		return false;
37 	}
38 
39 	@Override
40 	public boolean mouseMoved(int screenX, int screenY) {
41 		// TODO Auto-generated method stub
42 		return false;
43 	}
44 
45 	@Override
46 	public boolean scrolled(int amount) {
47 		// TODO Auto-generated method stub
48 		return false;
49 	}
50 
51         ...............
52 }


Os eventos que controlamos con esta interface son:

  • keyDown(): Tecla premida.
  • keyUp(): Tecla liberada.
  • keyTyped(): Tecla premida e xera un carácter unidade.
  • touchDown(): O dedo prema a pantalla (móbil) ou o botón do rato é premido.
  • touchUp(): O dedo deixa de premer a pantalla ou o botón do rato deixa de ser premido.
  • touchDragged(): Dedo ou rato é arrastrado pola pantalla. No caso do rato o botón debe estar presionado.
  • mouseMoved(): O rato se move pola pantalla e non se pulsa ningún botón.
  • scrolled(): Cando se preme a roda de scroll do rato.

Como vemos todos os método devolven un boolean. Este é usado no caso de que teñamos diferentes interfaces de xestión de eventos no mesmo xogo e non queremos que o evento se traslade á seguinte interface (poñeríase return true). Normalmente o deixaremos por defecto.

Unha vez temos a interface temos que informar á nosa clase que os eventos teñen que ir a dita interface.

Isto o logramos chamando ó método Gdx.input.setInputProcessor(obxecto) indicando como obxecto o obxecto da clase que ten implementada a interface. No noso caso this:

  • Gdx.input.setInputProcessor(this)

Onde temos que poñer dita orde ?

  • O poñeremos nos métodos show e resume.

Cando acabamos de xestionar os eventos chamaremos ó mesmo método pero enviando null como obxecto:

  • Gdx.input.setInputProcessor(null)

Onde temos que poñer dita orde ?

  • O poñeremos nos métodos pause, hide e dispose (pode ser que non sexa necesario poñelo en hide se programamos que cando cambiemos de pantalla chamemos ó método dispose).


Código da clase PantallaXogo
Obxectivo: Informar á clase onde se atopa a xestión de eventos.

 1         ......................
 2 	@Override
 3 	public void show() {
 4 		// TODO Auto-generated method stub
 5 		Gdx.input.setInputProcessor(this);
 6 	}
 7 
 8 	@Override
 9 	public void hide() {
10 		// TODO Auto-generated method stub
11 		Gdx.input.setInputProcessor(null);
12 	}
13 
14 	@Override
15 	public void pause() {
16 		// TODO Auto-generated method stub
17 		Gdx.input.setInputProcessor(null);
18 		
19 	}
20 
21 	@Override
22 	public void resume() {
23 		// TODO Auto-generated method stub
24 		Gdx.input.setInputProcessor(this);
25 		
26 	}
27 
28 	@Override
29 	public void dispose() {
30 		// TODO Auto-generated method stub
31 		Gdx.input.setInputProcessor(null);
32 		rendererxogo.dispose();
33 	}


Agora xa temos todo preparado para xestionar os eventos. Empecemos coa parte máis sinxela (a versión PC) e faremos que cando prememos as teclas do cursor movamos algo.

Como estamos programando seguindo o MVC, teremos que informar á clase controladora que se pulsou unha tecla e actuar en consecuencia.

Como o facemos ?

Existen moitas alternativas. Eu vos vou expoñer a que aprendín.

Definimos na clase ControladorXogo un tipo de datos Enum cos diferentes controis que podemos ter sobre o noso xogo. Neste caso teremos as catro teclas dos cursores:

1 	public enum Keys {
2 		ESQUERDA,DEREITA,ARRIBA,ABAIXO
3 	}

Despois definimos un hashmap e o inicializamos a false (lembrar importar a clase coa combinación de teclas Control+Shift+O).

1 	HashMap<Keys, Boolean> keys = new HashMap<ControladorXogo.Keys, Boolean>();
2 	{
3 		keys.put(Keys.ESQUERDA, false);
4 		keys.put(Keys.DEREITA, false);
5 		keys.put(Keys.ARRIBA, false);	
6 		keys.put(Keys.ABAIXO, false);	
7 	};

E por último, para poder acceder a esta variable dende a clase PantallaXogo, faremos os método pulsarTecla e liberarTecla dentro desta clase ControladorXogo:

 1 	/**
 2 	 * Modifica o estado do mapa de teclas e pon a true 
 3 	 * @param tecla: tecla pulsada
 4 	 */
 5 	public void pulsarTecla(Keys tecla){
 6 		keys.put(tecla, true);
 7 	}
 8 	/**
 9 	 * Modifica o estado do mapa de teclas e pon a false
10 	 * @param tecla: tecla liberada
11 	 */
12 	public void liberarTecla(Keys tecla){
13 		keys.put(tecla, false);
14 	}


Agora só queda chamar a estes métodos dende a clase PantallaXogo...

Código da clase PantallaXogo
Obxectivo: Informar á clase controladora da tecla pulsada (lembrar importar o paquete Input coa combinación de teclas Contro+Shift+O).

 1 	@Override
 2 	public boolean keyDown(int keycode) {
 3 		// TODO Auto-generated method stub
 4 		// Liberamos as teclas para que se arrastramos o dedo a outro control sen soltar o anterior non xunte o efecto
 5 		controladorXogo.liberarTecla(ControladorXogo.Keys.ABAIXO);
 6 		controladorXogo.liberarTecla(ControladorXogo.Keys.ARRIBA);
 7 		controladorXogo.liberarTecla(ControladorXogo.Keys.ESQUERDA);
 8 		controladorXogo.liberarTecla(ControladorXogo.Keys.DEREITA);
 9 
10 		switch(keycode){
11 		case Input.Keys.UP:
12 			controladorXogo.pulsarTecla(ControladorXogo.Keys.ARRIBA);
13 			break;
14 		case Input.Keys.DOWN:
15 			controladorXogo.pulsarTecla(ControladorXogo.Keys.ABAIXO);
16 			break;
17 		case Input.Keys.LEFT:
18 			controladorXogo.pulsarTecla(ControladorXogo.Keys.ESQUERDA);
19 			break;
20 		case Input.Keys.RIGHT:
21 			controladorXogo.pulsarTecla(ControladorXogo.Keys.DEREITA);
22 			break;
23 		}
24 
25 			
26 		return false;
27 	}

Como vedes non ten moita complicación. Simplemente comprobamos que tecla se preme e informamos á clase controladora.


TAREFA 2.7 A FACER: Esta parte está asociada á realización dunha tarefa.



Exercicio proposto: Agora é necesario mover o alien. Isto o facemos na clase ControladorXogo modificando a súa velocidade en función da tecla pulsada. Vos atrevedes a facelo ?

Pistas:

  • O alien ten unha mesma velocidade (non varía, non ten aceleración) pero pode moverse nos dous eixes (x/y). Teredes que crear na clase Alien os métodos necesarios para modificar dita velocidade en cada un dous eixes e obter dita velocidade.
  • O método update terá que ter en conta a velocidade nos dous eixes. Neste xogo non permitimos que se mova en diagonal xa que cando prememos unha tecla desactivamos as anteriores.
  • Lembrar que na clase ControladorXogo debemos modificar a velocidade cando se preme a tecla correspondente e para de moverse cando soltamos a tecla.
  • Lembrar chamar ó método update do alien dende a clase ControladorXogo.


Posible solución:

Código da clase Alien
Obxectivo: Definimos os métodos para xestionar a velocidade nos eixes x/y e modificamos o método update.

 1 public class Alien extends Personaxe{
 2 
 3 	private Vector2 velocidade;
 4 	
 5 	public Alien(Vector2 posicion, Vector2 tamano, float velocidade_max) {
 6 		super(posicion, tamano, velocidade_max);
 7 		
 8 		velocidade = new Vector2(0,0);
 9 
10 	}
11 	
12 	public float getVelocidadeX(){
13 		return velocidade.x;
14 	}
15 	public float getVelocidadeY(){
16 		return velocidade.y;
17 	}
18 
19 	public void setVelocidadeX(float x){
20 		velocidade.x = x;
21 
22 	}
23 	public void setVelocidadeY(float y){
24 		velocidade.y = y;
25 	}
26 
27 	@Override
28 	public void update(float delta) {
29 		// TODO Auto-generated method stub
30 
31 		setPosicion(getPosicion().x+velocidade.x*delta, getPosicion().y+velocidade.y*delta);
32 	
33 	}
34 
35 }

Como vemos usamos un Vector2 para gardar as dúas velocidades. Cando prememos unha tecla faremos que dita velocidade valga a velocidade máxima (a que lle mandamos no constructor cando instanciamos o obxecto alien)

Código da clase ControladorXogo
Obxectivo: Modificamos a velocidade do alien en función da tecla pulsada.

 1 public class ControladorXogo {
 2 
 3 	private Mundo meuMundo;
 4 	private Alien alien;
 5         ....................
 6 	public ControladorXogo(Mundo mundo) {
 7 		this.meuMundo = mundo;
 8 		alien = meuMundo.getAlien();
 9 	}
10         ....................
11 
12 	private void controlarAlien(float delta){
13 		
14 		// Actualiza Alien
15 		alien.update(delta);
16 	}
17 
18 	/**
19 	 * Vai chamar a todos os métodos para mover e controlar os personaxes Tamén
20 	 * xestionará os eventos producidos polo usuario e que veñen dende a clase
21 	 * PantallaXogo
22 	 * 
23 	 * @param delta
24 	 */
25 	public void update(float delta) {
26 
27 		controlarCoches(delta);
28 		controlarRochas(delta);
29 		controlarTroncos(delta);
30 		
31 		controlarNave(delta);
32 		controlarAlien(delta);
33 		
34 		procesarEntradas();
35 
36 	}
37 	private void procesarEntradas(){
38 		
39 		if (keys.get(Keys.DEREITA))
40 			alien.setVelocidadeX(alien.velocidade_max);
41 		if (keys.get(Keys.ESQUERDA))
42 			alien.setVelocidadeX(-alien.velocidade_max);
43 		if (!(keys.get(Keys.ESQUERDA)) && (!(keys.get(Keys.DEREITA))))
44 			alien.setVelocidadeX(0);
45 			
46 		if (keys.get(Keys.ARRIBA))
47 			alien.setVelocidadeY(alien.velocidade_max);
48 		if (keys.get(Keys.ABAIXO))
49 			alien.setVelocidadeY(-alien.velocidade_max);
50 		if (!(keys.get(Keys.ARRIBA)) && (!(keys.get(Keys.ABAIXO))))
51 			alien.setVelocidadeY(0);
52 
53 	}

Nota: Modificar a orde de chamada do método debuxarAlien na clase RendererXogo para que sexa a última. Desta forma o alien vaise debuxar por enriba de todos os demais elementos.



Agora imos facer unha pequena modificación no xogo para que a parte baixa da pantalla conte cunha banda negra onde van ir as vidas salvadas e o crono do xogo.

Isto xa deberiades ser capaces de facelo. Só temos que debuxar a textura baseada no gráfico LIBGDX_puntonegro.jpg que temos feito da tarefa 2.3. Como imos ter que gardar o tamaño desta banda negra para poder debuxalo dende a clase RendererXogo e facer que o Alien non poida baixar dende a clase ControladorXogo, imos definir o tamaño nunha clase de nome Controis que a definiremos no paquete principal.

Código da clase Controis
Obxectivo: Gardamos o tamaño dos controis utilizados no xogo. Neste caso o tamaño da banda negra inferior.

1 public class Controis {
2 
3 	public final static Rectangle FONDO_NEGRO = new Rectangle(0,0,Mundo.TAMANO_MUNDO_ANCHO,12);
4 }

Agora debuxamos dito control na clase RendererXogo:

Código da clase RendererXogo
Obxectivo: Debuxar a banda inferior negra.

 1         ..............
 2 	private void debuxarControis(){
 3 		
 4 		// Fondo negro
 5 		spritebatch.draw(AssetsXogo.texturePuntoNegro, Controis.FONDO_NEGRO.x,Controis.FONDO_NEGRO.y,Controis.FONDO_NEGRO.width,Controis.FONDO_NEGRO.height);
 6 
 7 	}
 8 
 9     public void render(float delta) {
10 		Gdx.gl.glClearColor(0, 0, 0, 1);
11 		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
12 
13 		spritebatch.begin();
14 		
15 			debuxarFondo();
16 		
17 			debuxarNave();
18 			
19 			debuxarCoches();
20 			debuxarRochas();
21 			debuxarTroncos();
22 			
23 			debuxarAlien();
24 
25 			debuxarControis();
26 		spritebatch.end();
27 		
28 		if (debugger) {
29 			debugger();
30 		}
31     }




Exercicio proposto: Facer que o alien non saia da pantalla, é dicir, que se mova nos límites do noso mundo e que non poda ir máis abaixo da banda inferior negra.


Posible solución:
Código da clase ControladorXogo
Obxectivo: Facer que o Alien se mova dentro dos límites da pantalla.

 1         ....................
 2 	private void controlarAlien(float delta){
 3 		
 4 		// Actualiza Alien
 5 		alien.update(delta);
 6 		
 7 		// Impide que se mova fora dos límites da pantalla
 8 		if (alien.getPosicion().x <=0){
 9 			alien.setPosicion(0, alien.getPosicion().y);
10 		}
11 		else {
12 			if (alien.getPosicion().x >= Mundo.TAMANO_MUNDO_ANCHO-alien.getTamano().x){
13 				alien.setPosicion(Mundo.TAMANO_MUNDO_ANCHO-alien.getTamano().x, alien.getPosicion().y);
14 			}
15 			
16 		}
17 		
18 		if (alien.getPosicion().y <=Controis.FONDO_NEGRO.height){
19 			alien.setPosicion(alien.getPosicion().x,Controis.FONDO_NEGRO.height);
20 		}
21 		else {
22 			if (alien.getPosicion().y >= Mundo.TAMANO_MUNDO_ALTO-alien.getTamano().y){
23 				alien.setPosicion(alien.getPosicion().x, Mundo.TAMANO_MUNDO_ALTO-alien.getTamano().y);
24 			}
25 			
26 		}
27 
28 		
29 	}




Como temos definido o noso xogo (MVC) agora é moi sinxelo engadir novas formas de control (acelerómetro, gráfico, touchpad,...) xa que o único que temos que facer é controlar na clase que ten a interface cando se preme o control e chamar ó método presionarTecla coa 'tecla' pulsada.




TAREFA 2.8.A A FACER: Esta parte está asociada á realización dunha tarefa.



Tarefas avanzadas



TAREFA AVANZADA OPTATIVA: Esta parte está asociada á realización dunha tarefa avanzada: Acelerómetro/Compás.




TAREFA AVANZADA OPTATIVA: Esta parte está asociada á realización dunha tarefa avanzada: Interface GestureListener.




TAREFA AVANZADA OPTATIVA: Esta parte está asociada á realización dunha tarefa avanzada: TochPad (joystick).





-- Ángel D. Fernández González -- (2015).