viernes, 15 de noviembre de 2013

Procesador de entradas y de gestos.



Saludos de nuevo! ya hemos aprendido a interaccionar con actores, clicarlos, moverlos, lanzarlos etc pero siempre dentro de un actor, voy a explicar en una pequeña entrada como implementar en nuestro juego un procesador de entradas y un procesador de gestos.






InputProcessor

El "InputProcessor" es una interfaz que tiene varios métodos, de los cuales en la entrada anterior hemos hablado ya en los actores, la interfaz la podemos implementar en cualquier clase, en una Screen, en la logica, y una vez implementada la tenemos que "seleccionar" en el procesador de entradas de libgdx con el método Gdx.input.setInputProcessor(Classe con la interfaz);
Escribo una classe cualquiera creada de nuevo donde implemento la interfaz.

package com.Firedark.libgdxspain;

import com.badlogic.gdx.InputProcessor;

public class MyInputProcessor implements InputProcessor{

    @Override
    public boolean keyDown(int keycode) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean keyUp(int keycode) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean keyTyped(char character) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        // TODO Auto-generated method stub
        return false;
    }

}

Una vez creada la clase, se instancia en la clase principal o en la que queraís y la seleccionais con el método Gdx.input.setInputProcessor(claseimplementada); Una vez lo hagaís y toqueís por el ejemplo la pantalla, se llamará al método touchDown de ésta clase, así pasará cuando movaís el raton (solo en desktop) cuando toqueis una tecla del teclado keyDown(), keyUp() etc.

Ésta interfaz la podemos instanciar en cualquier parte, junto a la interfaz Screen, directamente en la clase principal, en un actor de lógica, sólo teneís que acordaros de usar el método Gdx.input.setInputProcessor(claseimplementada);

GestureListener

package com.Firedark.libgdxspain;

import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.math.Vector2;

public class MyGestureDetector implements GestureListener {

    @Override
    public boolean touchDown(float x, float y, int pointer, int button) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean tap(float x, float y, int count, int button) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean longPress(float x, float y) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean fling(float velocityX, float velocityY, int button) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean pan(float x, float y, float deltaX, float deltaY) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean zoom(float initialDistance, float distance) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
            Vector2 pointer1, Vector2 pointer2) {
        // TODO Auto-generated method stub
        return false;
    }

}

Funciona exactamente igual que el InputProcessor, pero ésta interfaz nos detecta los gestos que hagas en la pantalla. Aqui va mi clase con la interfaz. 

Debeís seleccionarla con el método Gdx.input.setInputProcessor(clase), pero no lo podeís hacer directamente, ya que el GestureListener en si no es un InputProcessor, para ello debeís crear una clase GestureDetector y pasarle nuestro GestureListener, se haría así:

Gdx.input.setInputProcessor(new GestureDetector(miobjetogesturelistener));

También la podeís implementar en cualquier clase.

InputMultiplexer

Ahora se nos plantea un problema, si yo selecciono que mi procesador de entradas sea el del Stage de mi scenes2d no puedo tener mi procesador de entradas a la vez, y tampoco podré gestionar gestos y entradas juntas, pero para ello tenemos la clase InputMultiplexer, que nos permitirá tener seleccionados varios procesadores de entradas. Es muy sencilla de usar, escribo un claro ejemplo de su uso y os dejo que investigueís un poco:

        InputMultiplexer multi = new InputMultiplexer();
        multi.addProcessor(game.myinputprocessor);
        multi.addProcessor(new GestureDetector(game.mygesturelistener));
        multi.addProcessor(stage);
        Gdx.input.setInputProcessor(multi);


Con ésto funcionarán los actores, las teclas y los gestos que queramos.

Ésto a sido todo ésta semana y para la semana que viene seguimos. Gracias a tod@s y hasta otra.

jueves, 14 de noviembre de 2013

Actores: Listeners

Saludos a tod@s, ésta semana vamos a dedicarla a los listeneres de los actores, intentaré explicarlo de la manera mas fácil posible para que lo entendamos todos.

 Listeners. 

Un Listener como su traducción indica es un oyente, un "escuchador" y captura "eventos" un evento es una notificación de alguien, en terminos de programación y en nuestro caso, el actor dispara eventos "me han tocado", "me han soltado", "me mueven", etc. Pongo una pequeña imagen de explicación que aunque parezca chorra nos ayudará a aclarar términos.

IMPORTANTE:
 Como vereís en la imagen un actor tiene unos llamados "bounds" límites, los límites de un actor se ajustan con varios de los métodos que hemos usado, setX(), setY(),setWidth(),setHeight(), setBounds(x,y,ancho,alto), etc

Un Actor no puede notificar un evento si no se toca sus límites, podemos crear un actor y colocarlo en cualquier sitio ajustando sus coordenadas con SetPosition(x,y), pero si no le ajustamos su ancho y alto sólo será un punto cualquiera y será imposible clickarlo, independientemente de que dibujemos en su método draw una textura, sprite, etc, vereís la textura, pero no os devolverá nada.

Bien vamos a ver como insertar el Listener en nuestro actor:


package com.Firedark.libgdxspain;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.actions.MoveByAction;
import com.badlogic.gdx.scenes.scene2d.actions.ParallelAction;
import com.badlogic.gdx.scenes.scene2d.actions.RepeatAction;
import com.badlogic.gdx.scenes.scene2d.actions.RotateByAction;

public class Pez extends Actor {
    LibgdxSpain game;
    TextureRegion RegionPez;
    Texture pez;
    RotateByAction rotate;
    MoveByAction move;
    RepeatAction repeat;
    ParallelAction paralel;
   
    public Pez(LibgdxSpain game){
       
        this.game = game;
   
        setPosition(50,50);
        setHeight(64);
        setWidth(64);
        setOriginX(getWidth()/2);
        setOriginY(getHeight()/2);
   
       
        move = new MoveByAction();
        move.setAmount(700, 300);
        move.setDuration(10f);
        rotate = new RotateByAction();
        rotate.setAmount(400);
        rotate.setDuration(1f);
        //añadir acciones
        repeat = new RepeatAction();
        repeat.setCount(repeat.FOREVER);
        repeat.setAction(rotate);
       
        paralel = new ParallelAction();
        paralel.addAction(move);
        paralel.addAction(repeat);
       
        this.addAction(paralel);

        pez = new Texture(Gdx.files.internal("data/Images/pez.png"));
       
        RegionPez = new TextureRegion(pez);
        //Añadimos un lister, y creamos el listener dentro del método,
        addListener(new InputListener() {
           
           
              public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                            //Se ejecuta cuando se hace click sobre el actor.
                      return true;
              }
             
              public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                    //Se ejecuta cuando se levanta el dedo del actor, se ejecuta sólo cuando el touchDown es true.
              }
             
              public void touchDragged (InputEvent event, float x, float y, int pointer) {
                  //Se ejecuta cuando touchdown = true y se arrastra el actor.
            }

       
            public boolean mouseMoved (InputEvent event, float x, float y) {
                //Se ejuta "solo en desktop" ya que es cuando el raton pasa por encima del actor.
                return true;
            }

           
            public void enter (InputEvent event, float x, float y, int pointer, Actor fromActor) {
                //Se ejuta "solo en desktop" ya que es cuando el raton entra al actor.
            }

       
            public void exit (InputEvent event, float x, float y, int pointer, Actor toActor) {
                //Se ejuta "solo en desktop" ya que es cuando el raton sale del actor.
            }
   
           
             
        });
   
    }
   
   
    public void draw(SpriteBatch batch,float parentAlpha){
       

       
        batch.draw(RegionPez, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
       
       
    }

}


Como veís teneís por parámetro las coordenadas de los puntos donde se pulsa y el "pointer" es para el multitáctil, 0 el primer dedo, 1 el 2º, etc etc.

Hay 2 tipos de listeners dentro de un actor, el puesto en el código anterior y el gestor de gestos:


 package com.Firedark.libgdxspain;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.actions.MoveByAction;
import com.badlogic.gdx.scenes.scene2d.actions.ParallelAction;
import com.badlogic.gdx.scenes.scene2d.actions.RepeatAction;
import com.badlogic.gdx.scenes.scene2d.actions.RotateByAction;
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;

public class Pez extends Actor {
    LibgdxSpain game;
    TextureRegion RegionPez;
    Texture pez;
    RotateByAction rotate;
    MoveByAction move;
    RepeatAction repeat;
    ParallelAction paralel;
  
    public Pez(LibgdxSpain game){
      
        this.game = game;
        setPosition(50,50);
        setHeight(64);
        setWidth(64);
        setOriginX(getWidth()/2);
        setOriginY(getHeight()/2);

        move = new MoveByAction();
        move.setAmount(700, 300);
        move.setDuration(10f);
        rotate = new RotateByAction();
        rotate.setAmount(400);
        rotate.setDuration(1f);
        repeat = new RepeatAction();
        repeat.setCount(repeat.FOREVER);
        repeat.setAction(rotate);
      
        paralel = new ParallelAction();
        paralel.addAction(move);
        paralel.addAction(repeat);
      
        this.addAction(paralel);

        pez = new Texture(Gdx.files.internal("data/Images/pez.png"));
      
        RegionPez = new TextureRegion(pez);
      
        addListener(new ActorGestureListener(){
          
            public void touchDown (InputEvent event, float x, float y, int pointer, int button) {
                //Mismo caso que en InputListener, al pulsar
            }

            public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
                //Mismo caso que en InputListener, al soltar
            }

            public void tap (InputEvent event, float x, float y, int count, int button) {
                //Al tocar y soltar, seria como al hacer click.
            }

            public boolean longPress (Actor actor, float x, float y) {
                //al mantener pulsado un rato sin mover el dedo
          
                return false;
            }

            public void fling (InputEvent event, float velocityX, float velocityY, int button) {
                //seria un gesto como para lanzar un objeto, lo tocamos y lo movemos hacia un lado
                //soltandolo
              
            }

      
            public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {
               //Es como un arrastrar
            }

            public void zoom (InputEvent event, float initialDistance, float distance) {
                //Como su nombre indica, es como cuando intentamos aumentar el zoom de la pantalla con
                //los dos dedos
            }

            public void pinch (InputEvent event, Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) {
                //Este es como el Zoom pero mas complicado, con capacidad de rotación, como cuando en
                //el google maps hacemos grande el mapa a la vez que lo giramos.
            }
          
        });
  
    }
  
  
    public void draw(SpriteBatch batch,float parentAlpha){
      

      
        batch.draw(RegionPez, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
      
      
    }

}


 IMPORTANTE 2.0

Hace 2 tutoriales escribí esto al crear la screen y añadir el stage dentro.

 @Override
    public void show() {

        //Ésto es necesario para procesar entradas dentro del stage, para listeners y demás.
        Gdx.input.setInputProcessor(stage);

        //Instancio el actor LogicalSplash anterior
        ls = new LogicalSplash(game,this);
        //Se lo añado al Stage.
        stage.addActor(ls);
    }

Es imposible que si os dejaís esa linea el stage sea capaz de capturar algún gesto,  para que se entienda, el stage tiene incluida la interfaz InputProcessor, la cual se pasa al processador de entradas de libgdx para que pueda "procesarlas" y devolvernos el método que toque.

Ya teneís varias maneras de interactuar con los actores, mañana explicaré como implementar el procesador de entradas y el procesador de gestos sin tener que usar actores. Espero que se haya entendido bien ya que para los que no hayan escuchado el tema de los eventos y listeneres puede ser complicado.

Muchas gracias a todos y hasta mañana.