Escenas en Scene2d bajo LibGDX

4 Febrero, 2015 - 22:53
Scene2d

Relatando mis esperiencias con LibGDX, hoy voy a hablar de una librería muy interesante que incorpora, Scene2d. Con ella, podremos representar escenas gráficas, siguiendo un guión preprogramado. Por ejemplo, cualquier juego de rol de 8 o 16 bits tiene muchas escenas donde los personajes andan, corren, se acercan unos a otros, se alejan, encogen, etc… representando una escena, sin que el jugador pueda intervenir. O bien los efectos que realizan imágenes o letras en las intros o créditos de los juegos. Estas son las típicas cosas para la que está diseñado Scene2d.

También trae una librería de widgets, Scene2d-ui, que son componentes de formularios para hacer interfaces (botones, inputbox, sliders, etc…). Pero de estos hablaré en otra ocasión… sólo usaremos lo mínimo necesario para pintar componentes de ejemplo.

En este pequeño artículo / tutorial, asumo que sabemos programar en Java y usar mínimamente LibGDX.

Vamos con la aburrida teoría. Empecemos a hablar de los componentes principales de Scene2d. En primer lugar, está el Stage. Este objeto representa la escena, y también captura la entrada de teclado, si lo pedimos (principalmente para el uso de Scene2d-ui). A este stage (escena) se le añaden Actors (actores). Estos actores son los objetos que representarán la escena. Estos actores pueden recibir acciones, que son las que provocan el movimiento / cambio de los actores de la escena. También hay Groups (grupos), que son agrupaciones de actores (se considera un Actor que permite tener Actors hijos). Y también hay (tablas), como layouts que permiten agrupar adecuadamente a los actores, para facilitar su posicionamiento. Aunque como su uso es principalmente para colocar widgets de Scene2d-ui, no los tocaremos aquí.

Empezaremos a poner un poco de código...
Creamos un Stage:


stage=new Stage();

Ahora, crearemos un Label (que es un widget de Scene2d-ui para pintar etiquetas de texto), usando la skin por defecto de LibGDX:


Label credits = new Label(“Helo World", uiSkin);

Posicionaremos esta etiqueta en la mitad de la pantalla:


credits.setPosition(Gdx.graphics.getWidth()/2.0f, Gdx.graphics.getHeight()/2.0f);
Y ahora, le añadiremos una acción para que se desplace en diagonal 100 píxeles, tardando 2 segundos en el proceso:
credits.addAction(Actions.moveTo((Gdx.graphics.getWidth()/2.0f)+100, (Gdx.graphics.getHeight()/2.0f)+100,2));

Ahora, añadiremos este actor a la escena:


stage.addActor(credits);

Bien, ya está la escena preparada. Pero esto tiene que pintarse de algún modo. Para ello, montaremos la función render() de la siguiente forma:


public void render(float deltaTime) { Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); stage.act(deltaTime); stage.draw(); if(Gdx.input.isKeyPressed(Keys.Q) || Gdx.input.isTouched()) Gdx.app.exit(); }

Las 2 primeras lineas limpian la pantalla, como sabemos bien. Las 2 siguientes lineas hacen que la escena se retrace. La última es una tecla de escape, que java me deja procesos zombies si tengo que matar el proceso a pelo...

La creación del Stage lo pondré por ejemplo en el show(), y el resto en una función, quedando así:


public void scene1() { Label credits = new Label("Credits", uiSkin); credits.setPosition(Gdx.graphics.getWidth()/2.0f, Gdx.graphics.getHeight()/2.0f); credits.addAction(Actions.moveTo((Gdx.graphics.getWidth()/2.0f)+100, (Gdx.graphics.getHeight()/2.0f)+100,2)); stage.addActor(credits); } public void show() { stage=new Stage(); scene1(); }

Esto debería pintar un Hello World que se mueve en diagonal durante 2 segundos. Si no hubiésemos indicado los dos segundos, se hubiese movido al instante a esa posición.

Label Credits

Aumentemos un poco la complejidad. Añadamos un nuevo actor, esta vez una imagen. Además, le añadiremos una secuencia de acciones:

goal = new Texture(Gdx.files.internal("images/goal.png")); Image img = new Image(goal); img.setPosition(100, 100); img.addAction( Actions.sequence( Actions.sizeTo(300, 300,3), Actions.moveTo(500, 500, 3) ) ); stage.addActor(img);

¿Que hace todo esto? Añadimos una textura, construimos un objeto Image con esa textura asociada (otro widget de Scene2d-ui), lo posicionamos en las coordenadas absolutas 100,100, luego le añadimos una secuencia de acciones. La acción Actions.sequence() nos permite añadir varias acciones encadenadas. En cuanto termine una, pasará a la siguiente. La primera acción hará que la imagen crezca (se reescale) a 300x300 durante 3 segundos, luego se moverá al 500,500 tardando 2 segundos más.

Ahora aumentaremos un poco más la complejidad. Cambiaremos esa acción por:


img.addAction( Actions.sequence( Actions.sizeTo(300, 300,3), Actions.parallel( Actions.moveTo(500, 500, 3), Actions.color(new Color(0.1f,0.9f,0.9f, 1),3) ) ) );

Ahora, se añaden dos acciones a la secuencia, la primera de aumentar el tamaño, y la segunda añade otra secuencia de acciones. Pero la diferencia es que Actions.paralell() ejecutará simultáneamente las acciones que tenga asociadas, en vez de ejecutarlas secuencialmente como sequence(). La imagen se moverá al 500,500 durante 3 segundos, y durante ese movimiento irá cambiando de color a un tono azulado.

Goal

¿Vemos el potencial de combinar sequences y paralells? Podemos hacer lo que nuestra imaginación nos permita.


Hay otras acciones útiles, como pueden ser las siguientes:


Actions.rotateTo() Actions.delay() Actions.visible()

Encontraremos una relación de acciones en la documentación oficial de LibGDX.

También podemos reescribir la clase de un widget para aumentar funcionalidades. Os pongo un ejemplo de una clase Imagen que he creado, sobreescribiendo el draw(), para poder usar Shaders (una tecnología fabulosa de la que quizá hable en otra ocasión) con objetos de Scene2d.


package com.achisoft.mightyironball.util; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.scenes.scene2d.ui.Image; public class Imagen extends Image { TextureRegion path; public boolean applyShader; public ShaderProgram shader; public float amount; public Imagen(TextureRegion path) { super(path); applyShader=false; shader=null; } public void setShader(ShaderProgram shd, float amountt) { shader=shd; amount=amountt; } public void toggleShader(boolean mode) { applyShader=mode; } public boolean getShader() { return applyShader; } @Override public void draw(Batch batch, float alpha) { if(applyShader) { batch.setShader(shader); shader.setUniformf("u_amount", amount); } else batch.setShader(null); super.draw(batch, alpha); batch.setShader(null); } }

Scene2d tiene más potencial de lo que se muestra aquí, esto solo es una pequeña iniciación. Espero que pueda seros de utilidad.