LIBGDX Xestion Eventos GestureListener
UNIDADE 3: Xestión de Eventos: GestureListener
Sumario
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:
- 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:
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):
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:
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:
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).
- 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:
- 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 -- (2014).