martes, 1 de octubre de 2013

Rectángulos, entradas y colisiones.

Formas Geometricas, Entradas y Colisiones.

Saludos de nuevo, vamos a empezar con la parte interesante de la creación de juegos, la lógica del juego, para ello vamos a crear 2 modos para que al explicar resulte más sencillo.

Vamos a crear un modo Debug en el que dibujaremos en pantalla las formas geometricas, las moveremos, como si de un personaje, mob, pared, o cualquier objeto se tratara, y el modo normal, donde pintaremos las texturas con sus imágenes originales y veremos el juego tal y como debería ser.


ShapeRenderer


El ShapeRenderer es un objeto que se encarga de dibujar formas geometricas, es capaz de dibujar circulos, conos, lineas, puntos, rectangulos,triangulos, etc. Aunque nosotros para juegos 2D nos centraremos en su uso para circulos y rectangulos. 

Para "dibujar" una forma geométrica, deberemos crear el objeto como atributo en nuestra classe y instanciarlo en el método create, no tiene ningún tipo de parámetro y su constructor es únicamente 

new ShapeRenderer();

Para dibujar una forma debemos ir al metodo render y escribir algo parecido a ésto.

//En Forma podeéis escojer varias, rectangulo, circulo, rectangulo relleno (FilledRectangle), punto, linea,etc etc.

        shaperenderer.begin(ShapeType."Forma");
        shaperenderer.setColor(1,1,1,0); // Para el color podeis usar una constante Color.BLACK,etc o
//  o diferentes métodos para instanciar color, como el RGBA que estoy usando, 
// setColor.(Red,Green,Blue,Alpha);
        shaperenderer."Metodo de la forma"; // Hay varios métodos depende de la forma del método //begin, si ponemos por ejemplo ShapeType.Circle, deberemos llamar al método shaperenderer.circle, //y darle los parámetros que necesite, en caso del circulo, coordenadas X e Y y el radio del mismo.
        shaperenderer.end();


Aqui teneís la documentación de ShapeRenderer y la documentación para los colores.

http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/glutils/ShapeRenderer.html

http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/Color.html

Rectangulos

Toda lógica en un juego o casi toda está formada por rectángulos, si creamos un juego de plataformas, tu personaje será un rectangulo y si dispara, disparará rectángulos, cuando éstos  colisionen contra otro "Mob", "Bicho",  enemigo o rectangulo pasará algo, ganarás puntos, perderás una vida o simplemente se acabará el juego, eso depende del programa. Lo mismo puede suceder en un RPG, un juego de arcade, un juego Board etc...

Se puede crear un rectángulo fácilmente desde libgdx, bastará con crearlo como atributo, y instanciarlo, tiene varios contructores:

new Rectangle(); // Si creamos un rectángulo sin parámetros debemos darselos a continuación, ya que //se crea con los parámetros a 0. (rect.x, rect.y, rect.width y rect.height)
new Rectangle(float x, float y, float width, float height); // Esto ya crea el rectángulo parametrizado.
new Rectangle(Rectangle rect); // Crea un rectángulo con otro rectángulo de referencia.

 Un ejemplo rápido de rectángulo:

        rec= new Rectangle();
        rec.height = 64;
        rec.width = 64;
        rec.x = 400;
        rec.y = 400;


http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/math/Rectangle.html


Entradas: táctil, accelerómetro y teclado.

No voy a explicar todo el paquete "Gdx.input" sólo deciros que es el encargado de gestionar los datos de entrada, no obstante comentaré 3 métodos básicos para empezar a mover cosas.

Gdx.input.isTouched()   - Devuelve True si se ésta pulsando la pantalla.
Gdx.input.getX() y Gdx.input.getY() , nos devuelve la coordenada X e Y de donde pulsemos.

Gdx.input.getAccelerometerX()
Gdx.input.getAccelerometerY()
Gdx.input.getAccelerometerZ()

Éstos tres métodos devuelven de -10 a 10 los valores de los ejes del accelerometro.

 Gdx.input.isKeyPressed(int key);    - Devuelve True si se pulsa la tecla del número especificado en el paramétro, no hace falta saber los números, si escribís la constante de Input.Keys sólo teneís que elegir la letra o teclas que quereís, os dejó el link a la documentación y varios ejemplos.

http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/Input.Keys.html

 Gdx.input.isKeyPressed(Input.Keys.H); //Letra H del teclado.
 Gdx.input.isKeyPressed(Input.Keys.ENTER); //Letra Enter del teclado.

 Colisiones

 A lo sencillo, las colisiones se dan cuando un rectángulo se toca con otro, simplemente deberemos usar el método del objeto Rectangle llamado , rectangulo1.overlaps(rectangulo2) , nos devolverá true en caso de que colisionen los dos rectángulos.


Tutorial Práctico: El pez y la Gamba.


Éste mini tutorial enseña a crear 2 rectángulos, una gamba y un pez, el pez será manejado con el táctil y la gamba ira del borde derecho de la pantalla al izquierdo continuamente, si el pez choca con la gamba, el pez volverá a su posición original.

Paso 1: Descargar Assets ( podeís usar lo que queraís, el pez y la gamba o simples rectángulos sin cargarle imagenes, o cualquier textura que queraís.)

Paso 2. Añadir a la classe el ShapeRenderer, 2 rectángulos ( yo los he llamado recP, y recG, rectángulo Pez y rectángulo Gamba respectivamente).Añadir un valor booleano para activar él modo debug, para alternar si queremos o no ver los rectángulos dibujados.

Paso 3. Instanciar y crear los 2 rectángulos.

Paso 4. Utilizar el Gdx.input para igualar las coordenadas del táctil a las coordenadas del rectángulo del pez arreglando el problema de que no se mueva por las esquinas, sino que el pulso del dedo lleve el centro del pez a el centro del dedo (mas o menos).

Paso 5 (opcional) Evitar que el pez desaparezca por los bordes de la pantalla.

Paso 6. Mover la gamba (Rectangulo) de un lado al otro de la pantalla.

Paso 7.  Dibujar las 2 texturas en la ubicación de los dos rectángulos que toque.

Paso 8, Crear la colisión y en caso de que toquen, llevar al pez a su posición inicial.


Aqui os dejo las dos classes como me quedan:


package com.Firedark.libgdxspain;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.Rectangle;

public class LibgdxSpain implements ApplicationListener {
   
    private OrthographicCamera camera;
    private SpriteBatch batch;
    private AssetsManager assets;
    //Crear atributos Paso 2
    private ShapeRenderer shrend;
    private Rectangle recP,recG;
    private boolean debug;
   
    private float h,w;

    @Override
    public void create() {   
        assets = new AssetsManager();
        assets.cargarAssets();
        //Instanciar ShapeRenderer Paso 3
        shrend = new ShapeRenderer();
        //Instanciar Rectangulo Pez Paso 3
        recP= new Rectangle();
        recP.height = 64;
        recP.width = 64;
        recP.x = 400;
        recP.y = 400;
        //Instanciar Rectangulo Gamba Paso 3, fijaos que la envio fuera de la pantalla al empezar.
        recG = new Rectangle(800,0,60,60);
       
        //Podemos alternar entre true y false para ver o no los rectangulos.
        debug = true;
   
        w = 800;
        h = 480;
        camera = new OrthographicCamera();
        camera.setToOrtho(false,w,h);

        batch = new SpriteBatch();   
        assets.musica.setLooping(true);
        assets.musica.play();
    }

    @Override
    public void dispose() {
   
        batch.dispose();
        shrend.dispose();
        assets.disposeAssets();
       
    }

    @Override
    public void render() {       

        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        batch.setProjectionMatrix(camera.combined);

        //Paso 4
        //Si tocamos la pantalla.
        if(Gdx.input.isTouched()){
            //las coordenadas del pez son iguales a la coordenada del "dedo" menos la mitad de lo que mida el rectangulo del pez.
            //asi lo centramos. Lo hacemos en X e Y.
            recP.x = Gdx.input.getX() - (recP.height/2);
            recP.y = h - Gdx.input.getY() - (recP.width/2);
           
        }
        //Paso 5
        // Si el Pez toca el borde izquierdo, se queda en el borde izquierdo.
        if(recP.x < 0){
            recP.x = 0;
        }
       
        //Si el pez toca el borde derecho , "w" es lo largo de la pantalla,
        //Fijaos que añado a la X del rectangulo lo que mide el rectangulo
        //Para no meter todo el pez en el borde, pensar que la coordenada X del rectangulo
        //es la esquina inferior.
        if(recP.x + recP.width > w){
            recP.x = w - recP.width;
        }
       
        //Lo mismo en el eje Y.
        if(recP.y < 0){
            recP.y = 0;
        }
       
        if(recP.y + recP.height > h){
            recP.y = h - recP.height;
        }
       
       
        //Paso 6
        //Movimiento de la Gamba, el bucle render es ciclico, así que aprobecharemos para ir
        //restandole -0.5 a su coordenada x contantemente.
        recG.x = recG.x - 0.5f;
        //Si llega al -60 de la pantalla, es decir se esconde en ella, vuelve a empezar.
        if(recG.x < -60){
            recG.x = 800f;
        }
       
        //Paso 8
        // Si Pez choca contra Gamba, el Pez vuelve a 400 x 400, aqui podriamos hacer lo que quisieramos
        //GameOver, Perder vidas, etc etc
        if(recP.overlaps(recG)){
            recP.x = 400;
            recP.y = 400;
        }
       
       
        batch.begin();
        batch.draw(assets.background,0,0);
        //Paso 7
        //Después del Background, dibujamos Pez y Gamba en las posiciones de los 2 rectangulos
        //esto ya hará que pez y gamba sigan a los rectangulos pertinentes, y se moverán.
        batch.draw(assets.pez,recP.x,recP.y);
        batch.draw(assets.gamba,recG.x,recG.y);
        batch.end();
        //Si activamos el Debug, dibujaremos los 2 rectangulos, pez azul, y gamba rojo.
        if(debug){
            //Azul
        shrend.begin(ShapeType.Line);
        shrend.setColor(Color.BLUE);
        shrend.rect(recP.x, recP.y, recP.width, recP.height);
        shrend.end();
            //Rojo
        shrend.begin(ShapeType.Line);
        shrend.setColor(Color.RED);
        shrend.rect(recG.x, recG.y, recG.width, recG.height);
        shrend.end();
        }
    }

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}



También he tocado la classe AssetsManager para añadir las 2 nuevas Imágenes:

package com.Firedark.libgdxspain;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;


public class AssetsManager {
   
    public Texture Tbackground,pez,gamba;
    public TextureRegion background;
    public Music musica;
   
    public void cargarAssets(){
        Tbackground = new Texture(Gdx.files.internal("data/Images/oceanbackground.jpg"));
        pez = new Texture(Gdx.files.internal("data/Images/pez.png"));
        gamba = new Texture(Gdx.files.internal("data/Images/gamba.png"));
        background = new TextureRegion(Tbackground,0,0,800,480);
        musica = Gdx.audio.newMusic(Gdx.files.internal("data/Music/musica.wav"));      
    }
   
    public void disposeAssets(){
        Tbackground.dispose();
        musica.dispose();
        pez.dispose();
        gamba.dispose();
    }
   



Bueno, ésto ha sido todo ésta semana, espero que haya sido productivo, se agradecen comentarios, dudas, +1 de Google y todo eso. Espero que empezeís a pensar en vuestra lógica del juego y nos vemos la próxima semana con el tema de las pantallas de juego y algun detallito más. ¡Gracias!

15 comentarios:

  1. Gracias por compartir, mi pregunta es como hago para que cuando toque algun lado el pez no aparezca automaticamente en ese lugar, me explico, tal como esta el codigo al dejar por ejemplo el pez en la ezquina inferior izquierda y luego tocar la ezquina superior derecha(o cualquier otro lado) el pez aparece automaticamente ahi, no se deberia poder permitir eso, lo correcto seria tener que arrastrarlo con el dedo hasta el lugar.

    ResponderEliminar
    Respuestas
    1. Me encontré en la misma situación y el código que publicó Sergio me trajo algunos problemas cuando soltaba y volvía a tocar el pez. Copio lo que a mi me funcionó por si a alguien le sirve.

      //Paso 4
      //Si tocamos la pantalla.
      if(!move && Gdx.input.getX() > recP.x && Gdx.input.getX() < recP.x + recP.width &&
      Gdx.input.getY() > recP.y && Gdx.input.getY() < recP.y + recP.height){
      move = true;
      }

      //Cuando soltamos
      if(!Gdx.input.isTouched() && move){
      move = false;
      }

      if(move){

      //las coordenadas del pez son iguales a la coordenada del "dedo" menos la mitad de lo que mida el rectangulo del pez.
      //asi lo centramos. Lo hacemos en X e Y.
      recP.x = Gdx.input.getX() - (recP.width/2);
      recP.y = h - Gdx.input.getY() - (recP.height/2);

      }

      Eliminar
  2. Saludos, únicamente tenemos que condicionar el movimiento del pez, si queremos que se mueva exclusivamente al tocar su rectángulo podemos hacer una serie de condiciones para que cuando los dos punteros de entrada Gdx.input.getX() y Gdx.input.getY() se situen en el interior del rectángulo, active el movimiento de éste, me he tomado la libertad de escribir el código, espero que lo entiendas y para cualquier duda mas no dudes en preguntarmelo.

    //Esto va dentro del render, donde actualmente se situa el movimiento del pez.

    if(Gdx.input.isTouched()){
    //Invertimos las coordenadas Y para que sea más fácil
    // manejarlas ya que van desde el punto
    //superior al punto inferior, 0 a 480 pixeles y 480 en 0,
    //esto nos corregirá éste dato.
    float sY = game.h -Gdx.input.getY();
    //Entra únicamente cuando la entrada (o las coordenadas inversas de Y anteriores) se situan dentro del rectangulo
    if(Gdx.input.getX() > recP.x & Gdx.input.getX() < recP.x + recP.width & sY > recP.y & sY < recP.y + recP.height){
    //Al entrar activamos una variable booleana move a true, si ponemos el código de movimiento
    //directamente aqui, al mover rapido el puntero nos saldrá de la condición y se parará
    // el pez.
    move = true;
    }

    }
    //Cuando soltemos, se pone la variable a false.
    if(!Gdx.input.isTouched()){
    move = false;
    }

    //condicionamos el movimiento a la variable move.
    if(move){
    recP.x = Gdx.input.getX() - (recP.height/2);

    recP.y = game.h - Gdx.input.getY() - (recP.width/2);

    }

    Éste sistema es mejor aunque aún tiene alguna falla, te animo a mejorarlo, conforme avanze con los tutoriales entraremos más en el paquete scene2D que nos permitirá hacer éstas cosas más rápido.

    Suerte ;)

    ResponderEliminar
  3. Gracias por el tuto solo que me sale un par de errores en shrend.begin(ShapeType.Rectangle); tanto en //azul conmo en //rojo me salta el Eclipse "Rectangle cannot be resolved or is not a field" ¿alguna idea de que podria ser?

    ResponderEliminar
    Respuestas
    1. Es la versión de libgdx, cuando hice el tutorial eso era así pero a cambiado,
      si escribes ShapeType. en begin te dará varias opciones para poner juraría que ahora hay sólo 3 Filled, Line y Point.

      Eliminar
    2. Muchas gracias, las he puesto en filled y no me funciona, seguire probando cosas, he estado repasando tu codigo y lo unico que veo es que tu tienes los imports encima de tus dos clases y yo estoy metiendo la clase assets manager dentro de libgdxspain he probado a sacarla fuera pero me dice :The public type AssetsManager must be defined in its own file.. gracias de nuevo

      Eliminar
  4. Muy buen tutorial, gracias por el material. Espero que sigas subiendo articulos.

    ResponderEliminar
  5. Muy buen tuto, una gran ayuda para conocer esta tecnología.

    Tengo un problema con el ejemplo, resulta que cuando establezco la variable debug a true para que pinte los rectángulos, éstos no quedan centrados con la imagen. Por ejemplo, al hacer click en el centro de la ventana se centra el rectángulo en esa posición pero el pez queda más a la izquierda. ¿A qué puede deberse?

    Saludos!!

    ResponderEliminar
  6. Comprueba bien que dibujes en la misma coordenada que donde esté el rectángulo.

    ResponderEliminar
  7. Solucionado!! El problema era que no le asignaba la matriz al renderer y al hacer el rect() me lo pintaba donde quería.

    Gracias de todos modos!

    ResponderEliminar
  8. muy buenos tutoriales el problema es como resover esto shrend.begin(ShapeType.Rectangle); ?

    ResponderEliminar
    Respuestas
    1. Cuando hice ésta entrada era diferente, ahora basta con poner:
      srend.begin(ShapeType.Line);
      srend.setColor(Color.YELLOW); srend.rect(rectangulo.x,rectangulo.y,rectangulo.width,rectangulo.height);
      srend.end();

      Eliminar
  9. Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: java.lang.NoSuchFieldError: pez
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:120)
    Caused by: java.lang.NoSuchFieldError: pez
    at com.me.st_toledo.Metodo_Toledo.render(Metodo_Toledo.java:142)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:207)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)

    me da ese error cuando trato de dibujar al pez

    ResponderEliminar
  10. buenas yo e programado en libgdx y e creado mis propias fisicas de formas geometricas, y creo que al pez globo seria mejor un circulo: public boolean Circulo(int X, int Y, int posicionX,int posicionY,int Radio,int piso)
    {int j=Radio-piso;

    for(int i=0; i<=Radio-piso; i++)
    {if((X<=(posicionX+Radio-i) && X>=posicionX-Radio+i) && (Y<=posicionY+Radio-j && Y>=posicionY-Radio+j))
    {return true;}
    j--;}

    siendo piso la distancia del circulo antes de doblarse posicoinX y Y su punto central, radio se explica por si solo, y X,Y la posicion actual de lo que evalas que colicina con el objeto y la langosta la veo msa como un triangulo xD:

    public boolean Aceptacion_forma(int X, int Y,int posicionX,int posicionY,byte derecha)
    {switch(derecha){
    case 0:
    for(int a=0;a<12; a++)
    {if((X<=posicionX && X>=(posicionX-6-a)) && (Y>posicionY && Y<=posicionY+a))
    {return true;}
    }

    if((X<=posicionX && X>=(posicionX-6-12)) && (Y>=posicionY+12 && Y<=posicionY+18))
    {return true;}

    for(int a=0;a<12; a++)
    {if((X<=posicionX && X>=(posicionX-6-10+a)) && (Y>=posicionY+18 && Y=posicionX && X<=(posicionX+6+a)) && (Y>posicionY && Y<=posicionY+a))
    {return true;}
    }

    if((X>=posicionX && X<=(posicionX+6+12)) && (Y>=posicionY+12 && Y<=posicionY+18))
    {return true;}

    for(int a=0;a<12; a++)
    {if((X>=posicionX && X<=(posicionX+6+10-a)) && (Y>=posicionY+18 && Y<posicionY+19+a))
    {return true;}
    }
    break;
    }
    return false;
    } este codigo es para 2 flechas una que mira la izquierda y otra a la derecha este si esta algo escuidado xD pero es basicamente la logica espero les si
    rva

    ResponderEliminar