LIBGDX As interfaces para capturar eventos
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).