LIBGDX Xestion Eventos GestureListener

De MediaWiki
Saltar a: navegación, buscar

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).