LibGDX: The Screen Object

seeing things

I should say here that LibGDX comes with an Abstract Game class that has helper methods very similar to the ones I used in my Game.java class. And that it has a Screen interface too, that I used in this game.

But LibGDX is not about how you organize your games. So you don't need to use its Game class, or organize your application as a series of screens.

It's just one of the nice things about LibGDX that it grew to implement a number of common procedures in Game development. It seems to have bits and pieces from a variety of frameworks: from Flixel to Cocos2D. But these implementations can be completely ignored by you, if you have your own preferences and personal system on how to build games. And better yet, you can easily expand LibGDX to include your way of doing things.

For instance, LibGDX does not use the container metaphor of say Sparrow or Cocos2D (the addChild calls for instance); it uses a more basic Canvas metaphor. But you can easily implement logic that recreates a display list inside LibGDX. (Although I warn you, you will lose some of the freedom the canvas metaphor gives you, as I will show you soon)

Screen.java

package com.rengelbert.froggergdx.screens;

import java.util.ArrayList;
import java.util.List;

import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.sprites.GameSprite;

public abstract class Screen  implements com.badlogic.gdx.Screen {

	public List elements;
	protected Game _game;
	
	public Screen (Game game) {
		this._game = game;
		elements = new ArrayList();
	}
	
	public void pause () {};
	public void resume () {};
	public void dispose (){};
	public void hide (){};
	public void show (){};
	public void destroy () {};
	
	public abstract void createScreen ();
	public abstract void update (float dt);
	
	@Override
	public void render(float arg0) {
	}

	@Override
	public void resize(int arg0, int arg1) {
	}
}

The Screen interface has methods to hook up to 5 of the main Notifications I spoke about before. Screen can react to RENDER, PAUSE, RESUME, RESIZE, DISPOSE.

You can create your own Screen interface and hook up differently to the Notifications or ignore them completely (at your peril). You can for instance drop the render method and call it update. You can break it into update, place, render. You can break it into update, checkCollisions, render... It's up to you and your logic. Again, LibGDX does not care.

Just keep in mind this is Android and that the less method calls you make inside the loop the faster your application will be.

I added to my Screen a ELEMENTS array. Every element in this array will be drawn to the view.

Take a look at the MenuScreen class:

MenuScreen.java

package com.rengelbert.froggergdx.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GLCommon;
import com.badlogic.gdx.graphics.g2d.SpriteCache;
import com.rengelbert.froggergdx.Game;
import com.rengelbert.froggergdx.sprites.GameSprite;


public class MenuScreen extends Screen {

	private SpriteCache _spriteCache;
	private int _spriteCacheIndex;
	
	public MenuScreen (Game game) {
		super(game);
	}
	
	@Override
	public void createScreen() {
		
		if (elements.size() == 0) {
			
			GameSprite logo = new GameSprite ("logo", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.7f);
			GameSprite label1 = new GameSprite ("label_how_to", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.53f);
			GameSprite label2 = new GameSprite ("label_instructions", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.2f);
			GameSprite label3 = new GameSprite ("label_tap", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.02f);
			GameSprite control = new GameSprite ("control", _game, _game.screenWidth * 0.5f,  _game.screenHeight * 0.4f);
			
			
			/*
			//OPTION 1: With SpriteBatch
			elements.add(logo);
			elements.add(label1);
			elements.add(label2);
			elements.add(label3);
			elements.add(control);
			*/
			
			//OPTION 2: With SpriteCache
			_spriteCache = new SpriteCache();
			_spriteCache.beginCache();
			_spriteCache.add(logo.skin, logo.x, logo.y);
			_spriteCache.add(label1.skin, label1.x, label1.y);
			_spriteCache.add(label2.skin, label2.x, label2.y);
			_spriteCache.add(label3.skin, label3.x, label3.y);
			_spriteCache.add(control.skin, control.x, control.y);
			_spriteCacheIndex = _spriteCache.endCache();
			
		}
		
	}

	@Override
	public void update(float dt) {
		
		
		if (Gdx.input.justTouched()) {
			Gdx.app.log("A HIT!", "A MOST PALPABLE HIT");
			_game.setScreen("GameScreen");
		
		} else {
			GLCommon gl = Gdx.gl;
			gl.glClearColor(0.4f, 0.4f, 0.4f, 1);
			gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
			
			_game.camera.update();
			
			/*
			//OPTION 1: With SpriteBatch
			_game.spriteBatch.setProjectionMatrix(_game.camera.combined);
			_game.spriteBatch.enableBlending();
			_game.spriteBatch.begin();
			
			int len = elements.size();
			GameSprite element;
			for (int i = 0; i < len; i++) {
				element = elements.get(i);
				_game.spriteBatch.draw(element.skin, element.x, element.y);
			}
			_game.spriteBatch.end();
			*/
			
			//OPTION 2: With SpriteCache
			gl.glEnable(GL10.GL_BLEND);
	        gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
	        _spriteCache.setProjectionMatrix(_game.camera.combined);
			_spriteCache.begin();
	        _spriteCache.draw(_spriteCacheIndex);
	        _spriteCache.end();
	        
		}

	}
	
}

So as you can see, I dropped the render method entirely and did everything I needed in the update method. If you find this strange, feel free to break up update and render any way you want.

In this class you can already see one of my all time favorite things about LibGDX: the fact that user input is processed in the same thread.

if (Gdx.input.justTouched()) {
	Gdx.app.log("A HIT!", "A MOST PALPABLE HIT");
	_game.setScreen("GameScreen");
}

This is synchronism heaven! There is also getAccelerometerX, getAccelerometerY, getAccelerometerZ, getPitch, getRoll, getRotation, isKeyPressed among others...

I also added in this class two rendering options: SpriteBatch and SpriteCache.

If you did the Starling/Sparrow or Cocos2D tutorials you will remember that you could flatten or use so called pre-compiled sprites in those frameworks whenever you could draw something that didn't change in every frame. The equivalent in LibGDX is the SpriteCache.

It collects all the TextureRegions you want to draw and that will not change overtime (will not move, rotate, scale, change its alpha value...). And then you draw the entire cache object in one call. The vertex data for these regions are stored so they can be quickly accessed with every iteration

Each collection of TextureRegions in a SpriteCache has an id, which you get when you are done adding them to the SpriteCache object:

_spriteCacheIndex = _spriteCache.endCache();

You then use this value to draw the cache:

_spriteCache.begin();
_spriteCache.draw(_spriteCacheIndex);
_spriteCache.end();

So you can create multiple groupings of elements and draw them inside one begin and end pass, just by feeding different ids to the draw method.

There is an example online of how to use this to draw tiles, with support for multiple layers, all drawn with SpriteCache.

In Option 1 I use the SpriteBatch object I created in FroggerGame.

_game.spriteBatch.setProjectionMatrix(_game.camera.combined);
_game.spriteBatch.enableBlending();
_game.spriteBatch.begin();

int len = elements.size();
GameSprite element;
for (int i = 0; i < len; i++) {
	element = elements.get(i);
	_game.spriteBatch.draw(element.skin, element.x, element.y);
}
_game.spriteBatch.end();

The draw method in SpriteBatch receives a TextureRegion and the screen X and Y coordinates for that region.

There are a gazillion overloads for this draw method and I urge you to check them out on the reference docs for LibGDX. You can draw by passing it only a Texture object for instance. And also, the draw method can be made very powerful: you can resize things for instance just by drawing them in a certain way, and you can clip images too. (I will use the clipping option in this game later on)

I grab the TextureRegion and the screen coordinates from my own GameSprite objects.

In the GameScreen you will see a more evolved rendering logic, where I check to see if GameSprites are visible; and if the skin value is null I call a draw method on GameSprite. I'll explain that later.

Sprites

LibGDX has its own Sprite class. What does it do? It groups the TextureRegion (texture coordinates and source texture object) with the screen coordinates.

So when you create a Sprite, you pass it the TextureRegion (again, tons of overloaded methods here, check them out) and you can then update the X and Y properties of the Sprite.

And when you want to draw the Sprite, all you need is to pass it the SpriteBatch object you created.


Sprite mySprite = new Sprite (someTextureRegion);

mySprite.setPosition(10, 200);

mySprite.draw (_game.spriteBatch);

One interesting thing to point out here are the differences between a Sprite and a TextureRegion:

- TextureRegion cannot be scaled, rotated, positioned. It can be flipped however on the X and/or Y axis. The flip occurs relative to the center of the image. TextureRegion does not have an origin point.

- Sprites can be scaled, rotated, positioned. It can be flipped by using the flip() method, and by using the setScale() method. When using the setScale method you can later retrieve the current scale by using the getScaleX, getScaleY methods. With flip you cannot retrieve that information later. (The two things of course mean two different things)


//this will flip the texture the sprite uses on the X axis.
//but I cannot check later if the texture is flipped
mySprite.flip(true, false); 

//this will flip the sprite on the X axis
mySprite.setScale(-1,1);
//but now I can check to see if it's flipped
if (mySprite.getScaleX() == -1)

Sprites do have an origin point: by default the lower left corner. This point can be changed but only for scaling and rotation purposes. Changing it will not alter the positioning of the Sprite. That will still refer to its lower left corner

For the most part I used TextureRegions in this game. I used Sprite for the player, because I needed to retrieve the information of whether or not the texture was flipped either on the X axis or the Y axis. So I needed to use setScale, as I will show you next. I also used Sprites inside NumberSprite and the Lives display.