LIBGDX Xestion Eventos GestureListener

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

UNIDADE 3: Xestión de Eventos: GestureListener

Introdución

Nota: Esta explicación está relacionada coa sección de Interfaces para capturar eventos.

Información na wiki: https://github.com/libgdx/libgdx/wiki/Gesture-detection

Clases utilizadas:


O obxectivo deste punto é ver como podemos capturar outro tipo de eventos diferentes dos que nos permite a interface InputListener xa vista nun punto anterior.

Tamén veremos como nese caso necesitaremos capturar eventos de dúas interfaces diferentes e como temos que facer para que isto sexa posible.


Interface GestureListener

Ata o de agora, para controlar os eventos engadimos a interface InputProcessor, co que controlamos os eventos de pulsar sobre a pantalla.

Pero temos a posibilidade de controlar outro tipo de eventos, coma son os de doble pulsación (evento tap), o clásico movemento con dous dedos para facer un zoom da pantalla (evento zoom)....

Todos estes eventos se atopan noutra interface denominada GestureListener.


  • Para usala, temos que implementar dita interface.
1 public class EventosGestureListener extends ApplicationAdapter implements GestureListener{
E implentar os métodos que veñen coa interface (situarse enriba da clase e escoller a opción Add unImplemmented Methods).
 1 	@Override
 2 	public boolean touchDown(float x, float y, int pointer, int button) {
 3 		// TODO Auto-generated method stub
 4 		return false;
 5 	}
 6 
 7 	@Override
 8 	public boolean tap(float x, float y, int count, int button) {
 9 		// TODO Auto-generated method stub
10 		return false;
11 	}
12 
13 	@Override
14 	public boolean longPress(float x, float y) {
15 		// TODO Auto-generated method stub
16 		return false;
17 	}
18 
19 	@Override
20 	public boolean fling(float velocityX, float velocityY, int button) {
21 		// TODO Auto-generated method stub
22 		return false;
23 	}
24 
25 	@Override
26 	public boolean pan(float x, float y, float deltaX, float deltaY) {
27 		// TODO Auto-generated method stub
28 		return false;
29 	}
30 
31 	@Override
32 	public boolean panStop(float x, float y, int pointer, int button) {
33 		// TODO Auto-generated method stub
34 		return false;
35 	}
36 
37 	@Override
38 	public boolean zoom(float initialDistance, float distance) {
39 		// TODO Auto-generated method stub
40 		return false;
41 	}
42 
43 	@Override
44 	public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
45 			Vector2 pointer1, Vector2 pointer2) {
46 		// TODO Auto-generated method stub
47 		return false;
48 	}



  • Unha vez a temos e engadidos os métodos da interface á nosa clase, temos que dicirlle á clase que use dita interface.

Có control de eventos anteriores (InputProcessor) facíamos isto no evento show da clase Screen:

1         public void show() {
2 		........
3 		Gdx.input.setInputProcessor(this);
4 	}

E no evento hide:

1 	@Override
2 	public void hide() {
3 		Gdx.input.setInputProcessor(null);
4 	}


Agora cambia por isto:

  • Creamos un obxecto da clase GestureDetector:
1 private GestureDetector gd;


  • No método show creamos dito obxecto, tendo que pasarlle como parámetro un obxecto dunha clase que implemente a interface GestureListener. No noso caso é a propia pantalla, por iso poñemos this.

Despois facemos coma no caso anterior, pero pasándolle o obxecto GestureDetector.

1 @Override
2 	public void show() {
3 		// TODO Auto-generated method stub
4 		gd = new GestureDetector(this);
5 		
6 		Gdx.input.setInputProcessor(gd);		
7 	}
  • O método hide queda igual:
1         @Override
2 	public void hide() {
3 		// TODO Auto-generated method stub
4 		Gdx.input.setInputProcessor(null);		
5 	}


Unha vez feito isto, xa controlamos os eventos da interface nos respectivos métodos.

Vexamos algúns dos métodos novos.

Exemplo de código


Deberedes de cambiar a clase co que inician as diferentes plataformas pola seguinte:

  • Deberedes copiar o gráfico seguinte ó cartafol assets do proxecto Android:
LIBGDX fondoscroll.png
  • Crear unha nova clase á que chamen as diferentes versións.

Código da clase EventosGestureListener
Obxectivo: Amosar como funciona a interface GestureListener.

  1 import com.badlogic.gdx.ApplicationAdapter;
  2 import com.badlogic.gdx.Gdx;
  3 import com.badlogic.gdx.graphics.GL20;
  4 import com.badlogic.gdx.graphics.OrthographicCamera;
  5 import com.badlogic.gdx.graphics.Texture;
  6 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
  7 import com.badlogic.gdx.input.GestureDetector.GestureListener;
  8 import com.badlogic.gdx.math.Vector2;
  9 
 10 public class EventosGestureListener extends ApplicationAdapter implements GestureListener{
 11 	private SpriteBatch batch;
 12 	private Texture img;
 13 	private OrthographicCamera _camera;
 14    
 15 	private float ANCHO_MUNDO_METROS = 100;
 16 	private float ALTO_MUNDO_METROS = 100;
 17 	
 18 	@Override
 19 	public void create () {
 20 		batch = new SpriteBatch();
 21 		img = new Texture("LIBGDX_fondoscroll.png");
 22 
 23 		_camera = new OrthographicCamera();
 24 		_camera.setToOrtho(false, 15, 15);
 25 		_camera.update();
 26 		
 27 		batch.setProjectionMatrix(_camera.combined);
 28 
 29 	}
 30 
 31 	@Override
 32 	public void render() {
 33 		Gdx.gl.glClearColor(1, 0, 0, 1);
 34 		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 35 		batch.begin();
 36 		batch.draw(img, 
 37 	               -ANCHO_MUNDO_METROS/2f, 
 38 	               -ALTO_MUNDO_METROS/2f, 
 39 	               ANCHO_MUNDO_METROS, 
 40 	               ALTO_MUNDO_METROS);
 41 		batch.end();
 42 		
 43 	}
 44 	
 45 	@Override
 46 	public void dispose() {
 47 		img.dispose();
 48 		batch.dispose();
 49 	}
 50 
 51 	@Override
 52 	public boolean touchDown(float x, float y, int pointer, int button) {
 53 		// TODO Auto-generated method stub
 54 		return false;
 55 	}
 56 
 57 	@Override
 58 	public boolean tap(float x, float y, int count, int button) {
 59 		// TODO Auto-generated method stub
 60 		return false;
 61 	}
 62 
 63 	@Override
 64 	public boolean longPress(float x, float y) {
 65 		// TODO Auto-generated method stub
 66 		return false;
 67 	}
 68 
 69 	@Override
 70 	public boolean fling(float velocityX, float velocityY, int button) {
 71 		// TODO Auto-generated method stub
 72 		return false;
 73 	}
 74 
 75 	@Override
 76 	public boolean pan(float x, float y, float deltaX, float deltaY) {
 77 		// TODO Auto-generated method stub
 78 		return false;
 79 	}
 80 
 81 	@Override
 82 	public boolean panStop(float x, float y, int pointer, int button) {
 83 		// TODO Auto-generated method stub
 84 		return false;
 85 	}
 86 
 87 	@Override
 88 	public boolean zoom(float initialDistance, float distance) {
 89 		// TODO Auto-generated method stub
 90 		return false;
 91 	}
 92 
 93 	@Override
 94 	public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
 95 			Vector2 pointer1, Vector2 pointer2) {
 96 		// TODO Auto-generated method stub
 97 		return false;
 98 	}
 99 
100 
101 }


Se executamos teremos isto:

LIBGDX UD3 InterfacesGesture 1.jpg


Analicemos parte do código:

1 		_camera = new OrthographicCamera();
2 		_camera.setToOrtho(false, 15, 15);
3 		_camera.update();

No método setToOrtho o primeiro parámetro indica se a coordenada 'Y' (posición 0) comeza na parte de arriba (valor true) ou se empeza na parte de abaixo (valor false):

LIBGDX UD3 InterfacesGesture 2.jpg


O mesmo tempo que lle damos un tamaño á cámara, a estamos posicionando na coordenada 15/2 e 15/2 = (7.5,7.5). A esta coordenada apunta o centro da cámara.

Se queremos posicionala noutro punto do noso mundo, teríamos que utilizar a propiedade 'position'

_camera.position.x = valor _camera.position.y = valor _camera.position.z = valor


O que facemos despois e renderizar a textura carga previamente. Lembrar que o tamaño do mundo é de 100x100 unidades.

 1 	@Override
 2 	public void render() {
 3 		Gdx.gl.glClearColor(1, 0, 0, 1);
 4 		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 5 		batch.begin();
 6 		batch.draw(img, 
 7 	               -ANCHO_MUNDO_METROS/2f, 
 8 	               -ALTO_MUNDO_METROS/2f, 
 9 	               ANCHO_MUNDO_METROS, 
10 	               ALTO_MUNDO_METROS);
11 		batch.end();
12 		
13 	}

Polo tanto debuxamos TODO O FONDO (de tamaño 1024x1024) dentro dun rectángulo de tamaño 100x100, empezando nas coordenadas (-50,-50). Fixarse que as coordenadas son negativas.

O que aquí amosamos é a visión da cámara, por tanto as coordenadas son as da cámara:

LIBGDX UD3 InterfacesGesture 3.jpg


So usamos o obxecto ShapeRenderer para visualizar un rectángulo que represente a cámara, facendo que o fondo ocupe toda a pantalla, teríamos este resultado:

LIBGDX UD3 InterfacesGesture 4.jpg


Nota: Como podemos observar, podemos modificar o 'zoom' da cámara en función do tamaño do seu viewport, anque isto tamén o podemos cambiar chamando o método zoom da cámara.



IDEA: O ter un tamaño fixo do viewport da cámara (15x15) vai suceder que cando aumente a resolución, esta siga sendo 15x15, chegando a ter un aumento demasiado grande.

Unha forma de evitar isto é agrandando o tamaño da cámara en función da resolución. Para isto imos crear unha constante nova na interface de constantes:

float PIXELS_PER_METER = 32;

Esta constante vai determinar cantos píxeles hai dentro dunha das unidades creadas por nos (lembrade que o noso mundo é de 100x100 unidades=metros por dicir algo).

De tal forma que se a resolución aumenta, tamén aumentará o tamaño do viewport se poñemos isto:

1 	@Override
2 	public void resize(int width, int height) {
3 		// TODO Auto-generated method stub
4 		   _camera.setToOrtho(false,
5 		                      width/PIXELS_PER_METER,
6 		                      height/PIXELS_PER_METER);
7 		   _camera.update();
8 	}

O facer isto teríamos o seguinte en diferentes resolucións (o viewport aumenta o tamaño para ter un zoom menor).

LIBGDX UD3 InterfacesGesture 5.jpg
LIBGDX UD3 InterfacesGesture 6.jpg


  • Método tap: Para facer que cando o usuario preme dúas veces a pantalla, a cámara se mova a posición indicada.

Xa temos engadidos os métodos da interface no paso inicial.

1         @Override
2 	public boolean tap(float x, float y, int count, int button) {
3 		// TODO Auto-generated method stub
4 
5 		if (count!=2) return false;
6 
7 		return false;
8 	}

Co return false indicamos que o evento sexa enviado pola xerarquñía de elementos gráficos por se queremos capturar dito evento. Se poñemos true xa non se envía a ningún outro elemento. Veremos despois cando xestionamos máis dunha interface para que usalo.

O parámetro count sirve para saber cantas veces se pulsa o pantalla de forma seguida. Se pode configurar o tempo que hai que pasar entre pulsacións para que 'sume' as pulsacións e o tamaño do cadrado no que se considera que está pulsando na mesma área. Isto se fai no evento show configurando o obxecto GestureDetection:

gd.setTapCountInterval(x)
gd.setTapSquareSize(x)

Seguimos co método tap.

  • Cando pulsamos na pantalla, o que nos devolve é a posición real da pantalla en píxeles. Pero nos queremos cambiar estes valores polos valores do noso mundo ficticio (vai dende -50x-50 a 50x50).

Para obtelas temos que facer uso do método unproyect do obxecto cámara.

 1 	@Override
 2 	public boolean tap(float x, float y, int count, int button) {
 3 		// TODO Auto-generated method stub
 4 		
 5 		if(count==2){
 6 			Vector3 coordreais = new Vector3(x,y,0);
 7 			_camera.unproject(coordreais);
 8 		}
 9 		
10 		return false;
11 	}
  • Agora dentro do método tap calculamos a distancia:
 1 	private Vector3 distanciaCamara = new Vector3(0f,0f,0f);  //Distancia da cámara ata o dedo
 2 	@Override
 3 	public boolean tap(float x, float y, int count, int button) {
 4 		// TODO Auto-generated method stub
 5 		
 6 		if(count==2){
 7 			Vector3 coordreais = new Vector3(x,y,0);
 8 			_camera.unproject(coordreais);
 9 			
10 			Vector3 poscam = _camera.position.cpy();
11 			distanciaCamara = poscam.sub(coordreais);
12 
13 		}
14 		
15 		return false;
16 	}


  • Agora necesitamos mover a cámara.

Definimos a nivel global un Vector2 para asinarlle a velocidade:

   private Vector2 movementoCamara = new Vector2(0f,0f);  // Velocidade da cámara

Para poder modificar a velocidade de cámara cambiando o valor dunha constante definimos a nivel global a velocidade da mesma:

   private final Vector3 VELOCIDADE_CAMARA = new Vector3(0.1f,0.1f,0f);


  • Volvemos agora ó método tap para asinar a velocidade:
 1 	@Override
 2 	public boolean tap(float x, float y, int count, int button) {
 3 		// TODO Auto-generated method stub
 4 		
 5 		if(count==2){
 6 			Vector3 coordreais = new Vector3(x,y,0);
 7 			_camera.unproject(coordreais);
 8 			
 9 			Vector3 poscam = _camera.position.cpy();
10 			distanciaCamara = poscam.sub(coordreais);
11 
12 			if (distanciaCamara.x>0) {  // Pulsado dedo na parte esquerda da pantalla dende o centro
13 				movementoCamara.x=-VELOCIDADE_CAMARA.x;
14 			}
15 			else {
16 				movementoCamara.x=VELOCIDADE_CAMARA.x;
17 			}
18 			if (movementoCamara.y>0) {  
19 				movementoCamara.y=-VELOCIDADE_CAMARA.y;
20 			}
21 			else {
22 				movementoCamara.y=VELOCIDADE_CAMARA.y;
23 			}
24 		}
25 		
26 		return false;
27 	}
  • Pero claro, con isto facemos que a cámara se mova, pero non vai parar. É necesario gardar a distancia que ten que percorrer.
 1 	@Override
 2 	public boolean tap(float x, float y, int count, int button) {
 3 		// TODO Auto-generated method stub
 4 		
 5 		if(count==2){
 6 			Vector3 coordreais = new Vector3(x,y,0);
 7 			_camera.unproject(coordreais);
 8 			
 9 			Vector3 poscam = _camera.position.cpy();
10 			distanciaCamara = poscam.sub(coordreais);
11 
12 			if (distanciaCamara.x>0) {  // Pulsado dedo na parte esquerda da pantalla dende o centro
13 				movementoCamara.x=-VELOCIDADE_CAMARA.x;
14 			}
15 			else {
16 				movementoCamara.x=VELOCIDADE_CAMARA.x;
17 			}
18 			if (distanciaCamara.y>0) {  
19 				movementoCamara.y=-VELOCIDADE_CAMARA.y;
20 			}
21 			else {
22 				movementoCamara.y=VELOCIDADE_CAMARA.y;
23 			}
24 
25 			distanciaCamara.x = Math.abs(distanciaCamara.x);
26 			distanciaCamara.y = Math.abs(distanciaCamara.y);
27 		}
28 		return false;
29 	}
  • Agora no método render temos que chamar a un método para que mova a cámara e actualice:
 1 	private void actualizarCamara(){
 2 		
 3 	}
 4 	
 5 	@Override
 6 	public void render() {
 7 		Gdx.gl.glClearColor(1, 0, 0, 1);
 8 		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 9 		
10 		actualizarCamara();
11 		
12 		batch.begin();
13 		batch.draw(img, 
14 	               -ANCHO_MUNDO_METROS/2f, 
15 	               -ALTO_MUNDO_METROS/2f, 
16 	               ANCHO_MUNDO_METROS, 
17 	               ALTO_MUNDO_METROS);
18 		batch.end();
19 		
20 	}
  • Para mover a cámara usamos o método translate da cámara, que move a cámara o nº de unidades indicadas. Imos diminuíndo a distancia en cada iteración, restando á distancia á velocidade da cámara.
 1 	private void actualizarCamara(){
 2 		if ((distanciaCamara.x>0) || (distanciaCamara.y>0)){
 3 			distanciaCamara.sub(VELOCIDADE_CAMARA);
 4 			if (distanciaCamara.x <=0)// Paramos o movemento
 5 				movementoCamara.x=0;
 6 			if (distanciaCamara.y <=0) // Paramos o movemento
 7 				movementoCamara.y=0;
 8 			
 9 			_camera.translate(movementoCamara);
10 			_camera.update();
11 			batch.setProjectionMatrix(_camera.combined);
12 		}
13 	}


  • Método zoom: Facer que a cámara se achegue ou afaste en función de se facemos na pantalla o movemento de abrir ou pechar dous dedos sobre a pantalla.
 1 	@Override
 2 	public boolean zoom(float initialDistance, float distance) {
 3 		// TODO Auto-generated method stub
 4 		
 5 		if (initialDistance > distance)
 6 			_camera.zoom+=0.05f;
 7 		else
 8 			_camera.zoom-=0.05f;
 9 	
10 		if (_camera.zoom>5) _camera.zoom=5;
11 		if (_camera.zoom<1) _camera.zoom=1;
12 		
13 		_camera.update();
14 		batch.setProjectionMatrix(_camera.combined);
15 		
16 		return false;
17 	}


Xestionando múltiples interfaces de eventos


Pode suceder que necesitemos xestionar máis dunha interface de eventos. Veremos na sección de 3D avanzada que temos unha interface para xestionar a cámara en 3D, e do que levamos visto ata o de agora tamén temos a interface GestureListener e InputProcessor.

O proceso é o seguinte:

  • Modificamos a clase do exemplo para incorporar a interface InputProcessor:
1 public class EventosInputMultiplexer extends ApplicationAdapter implements GestureListener, InputProcessor{
  • Teremos dous métodos touchDown, un de cada Interface. Amosaremos unha mensaxe en cada un deles:
 1 	@Override
 2 	public boolean touchDown(float x, float y, int pointer, int button) {
 3 		// TODO Auto-generated method stub
 4 		
 5 		Gdx.app.log("MENSAXES","TOUCH DOWN DE GESTURELISTENER");
 6 		return false;
 7 	}
 8 
 9         ..........
10 	@Override
11 	public boolean touchDown(int screenX, int screenY, int pointer, int button) {
12 		// TODO Auto-generated method stub
13 		Gdx.app.log("MENSAXES","TOUCH DOWN DE InputProcessor");
14 		return false;
15 	}


  • Creamos un obxecto da clase InputMultiplexor e o instanciaremos no constructor:
1 		inputMultiplexer = new InputMultiplexer();


  • Agora chamaremos ó método addProcessor para engadir EN ORDEN as diferentes interfaces. A orde é importante xa que primeiro irá os eventos da interface engadida en primeiro lugar:
1 		gd = new GestureDetector(this);
2 
3 		inputMultiplexer.addProcessor(gd);
4 		inputMultiplexer.addProcessor(this);

Fixarse como no exemplo engadimos primeiro a interface GestureListener e despois a InputProcessor.

  • Indicamos ó framework que xestiona os eventos:
1         Gdx.input.setInputProcessor(inputMultiplexer);


Se agora executades o código e premedes unha vez sobre a pantalla recibiredes os dous avisos:

LIBGDX UD3 InterfacesGesture 7.jpg


  • Agora é cando podemos xogar con return dos eventos.

Se no primeiro touchDown (o do GestureListener) cambiamos a true, indicaremos que o evento xa non debe ir por máis controis nin por outra interface, polo que só recibiremos o aviso da interface GestureListener:

1 	@Override
2 	public boolean touchDown(float x, float y, int pointer, int button) {
3 		// TODO Auto-generated method stub
4 		
5 		Gdx.app.log("MENSAXES","TOUCH DOWN DE GESTURELISTENER");
6 		return true;
7 	}




TAREFA OPTATIVA A FACER



Modificade o xogo para que se poida facer Zoom.





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