Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


23. September 2012

This is one of those things I am constantly searching for, so I figured I might as well put something together so I end up on my own site!

 

The following is simply a list of devices and their respective screen resolutions.  I am writing this as much for me as anything else, but hopefully some of you find it useful too.

 

Device Name

Resolution

PlayStation Portable (PSP) 480x272
PlayStation Vita 960x544
Nintendo DS  2 x 256x192
Nintendo 3DS 800x240 upper screen ( 400 per eye, effectively 400x240 ). 320x240 lower screen
iPhone 3 320x480
iPhone 4 640x960
iPhone 5 1136x640
iPad 1024x768
Galaxy S2 480x800
Galaxy S3 720x1280
Galaxy Note 800x1280
HTC OneX 720x1280
Lumia 920 768x1280
Lumia 820 480x800
Transformer Prime 1280x800
Razor HD 1280x800
Common Resolutions by name
QVGA 320x240
VGA 640x480
WVGA 800x480
XGA 1024x768
WXGA 1280x768
UXGA 1600x1200
WUXGA 1920x1280
Television Resolutions
Standard Def NTSC (480i) 720x480 interlaced
Standard Def PAL (576i) 720x576 interlaced
720p 1280x720
1080i 1920x1080 interlaced
1080p 1920x1080
4K 4096x1714 ( varies by manufacturer )

Design, General

blog comments powered by Disqus

Month List

Popular Comments

LibGDX Tutorial 3: Basic graphics
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


2. października 2013

 

This is the part people always find the most fun, the actual act of putting graphics up on screen.  Let’s start with about the simplest project that we can.

 

We are going to display this sprite (created in this tutorial):

jet

 

On screen.  One important thing to note, the above graphic is 512x256.  OpenGL in general and LibGDX in specific, require your image files to be in power of two dimensions.  This means your width and height are 2,4,8,16,32,64,128,256,512,1024,2048, etc… pixels in size.  Be sure to add this file to the assets\data folder in the android project before continuing.

 

Let’s jump right in with code:

 

package com.gamefromscratch.graphicsdemo;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class GraphicsDemo implements ApplicationListener {
    private SpriteBatch batch;
    private Texture texture;
    private Sprite sprite;
    
    @Override
    public void create() {        
        batch = new SpriteBatch();
        texture = new Texture(Gdx.files.internal("data/jet.png"));
        sprite = new Sprite(texture);
    }

    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

 

And if you run it:

image

 

The image is drawn relative to the origin.  In the case of LibGDX (0,0) is the bottom left corner of the screen.

 

As to the code, there isn’t actually a ton new compared to the Hello World example in the previous tutorial.  The only new concepts are the Texture and the Sprite.  The texture represents the underlying OpenGL texture.  One important thing to keep in mind with Texture ( and other similar classes ) is they implement the Disposable interface.  This means when you are done with it, you have to call the dispose() method, or you will leak memory!  A Sprite holds the geometry and colour data of a texture, this means the positional data ( such as it’s X and Y location ) are stored in the Sprite.  We construct our texture by passing it’s path in, obtained in the same manner we access the font in the prior tutorial.  We then construct the Sprite by passing in our newly created texture.  There are other ways of creating Sprites, that we will see shortly.  Just like in the Hello World sample, we start a SpriteBatch, and draw our sprite to it using the draw() method.

 

Dynamic textures with Pixmap

 

Your Texture’s source doesn’t have to come from a file.  Here we are going to use the Pixmap class to create the texture’s source dynamically.

 

package com.gamefromscratch.graphicsdemo;

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.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class GraphicsDemo implements ApplicationListener {
    private SpriteBatch batch;
    private Pixmap pixmap;
    private Texture texture;
    private Sprite sprite;
    
    @Override
    public void create() {        
        batch = new SpriteBatch();
        
        // A Pixmap is basically a raw image in memory as repesented by pixels
        // We create one 256 wide, 128 height using 8 bytes for Red, Green, Blue and Alpha channels
        pixmap = new Pixmap(256,128, Pixmap.Format.RGBA8888);
        
        //Fill it red
        pixmap.setColor(Color.RED);
        pixmap.fill();
        
        //Draw two lines forming an X
        pixmap.setColor(Color.BLACK);
        pixmap.drawLine(0, 0, pixmap.getWidth()-1, pixmap.getHeight()-1);
        pixmap.drawLine(0, pixmap.getHeight()-1, pixmap.getWidth()-1, 0);
        
        //Draw a circle about the middle
        pixmap.setColor(Color.YELLOW);
        pixmap.drawCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getHeight()/2 - 1);
        
        
        texture = new Texture(pixmap);
        
        //It's the textures responsibility now... get rid of the pixmap
        pixmap.dispose();
        
        sprite = new Sprite(texture);
    }

    @Override
    public void dispose() {
        batch.dispose();
        texture.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        sprite.setPosition(0, 0);        
        sprite.draw(batch);
        sprite.setPosition(Gdx.graphics.getWidth()/2, Gdx.graphics.getHeight()/2);
        sprite.draw(batch);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

 

Once again, the code is remarkably similar to our prior example.  The biggest difference is instead of loading the textures image data from file, we create one dynamically using a Pixmap.  At the simplest, a pixmap can be thought of as a grid of pixel data in memory.  It contains a number of graphical functions, many of which we demoed above.  The drawing code is pretty well commented in terms of what it does, so I wont go into details here.  One very important detail though, in the modern GPU driven world, these kinds of per pixel operations are REALLY REALLY SLOW.  Generally you want to avoid them as much as possible. 

 

The only other thing of note in this example is the changes in the render() method.  Notice how the same sprite is drawn twice to the sprite batch?  Well this behaviour is perfectly OK, and has minimal performance overhead in doing so.  Sprite’s setPosition method is used to position a sprite, once again, (0,0) is the lower left hand corner of the screen by default.  The only other new code here is the Gdx.graphics.getWidth() and getHeight() method calls.  These return the window’s ( or Canvas in the case of HTML5 ) dimensions.  Of course in production code you would probably cache them locally instead of retrieving them every pass through the render loop.

 

TextureAtlas

 

Quite often you want to deal with a sprite sheet, which is a number of sprites combined together into a single image.  Such functionality is built into LibGdx.  The first thing you are going to need is a directory of images that are going to be combined into a sprite sheet.  Like this:

image

 

Open a command line or terminal window and run the following command:

java -cp gdx.jar;extensions/gdx-tools/gdx-tools.jar com.badlogic.gdx.tools.imagepacker.TexturePacker2 c:\tmp c:\tmp spritesheet
tmp

It looks more unwieldy than it is.  Basically you are running the TexturePacker2 class inside the gdx-tools jar.  The first parameter is the source directory, the second parameter is the destination direction and the final parameter is the filename to use.  It will automatically add the required file extensions.  This process will create two files, a .atlas file and a .png.  The atlas file is a text file describing how the sprites are laid out in the spritesheet image, with the images filename used as the key ( minus extension ), like so:

spritesheet.atlas:

spritesheet.png
format: RGBA8888
filter: Nearest,Nearest
repeat: none
0001
  rotate: false
  xy: 1, 651
  size: 192, 128
  orig: 192, 128
  offset: 0, 0
  index: -1
0002
  rotate: false

 

While the sprite sheet itself looks like this:

spritesheet

 

The spritepacker tool automatically pads the image out to be a power of 2 in size.  I’ve only scratched the very surface of what this tool can do.  You can set it to run as part of your build process, run if from code or within Eclipse or even run it at program run time. There are a wealth of options you can configure.  You can read much more about it right here.

 

So how do you actually use a texture atlas then?  It’s very simple, first copy the generated png and atlas file to your assets.  The following code shows how to use a TextureAtlas:

 

package com.gamefromscratch.graphicsdemo;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.utils.Timer;
import com.badlogic.gdx.utils.Timer.Task;

public class GraphicsDemo implements ApplicationListener {
    private SpriteBatch batch;
    private TextureAtlas textureAtlas;
    private Sprite sprite;
    private int currentFrame = 1;
    private String currentAtlasKey = new String("0001");
    
    @Override
    public void create() {        
        batch = new SpriteBatch();
        textureAtlas = new TextureAtlas(Gdx.files.internal("data/spritesheet.atlas"));
        AtlasRegion region = textureAtlas.findRegion("0001");
        sprite = new Sprite(region);
        sprite.setPosition(120, 100);
        sprite.scale(2.5f);
        Timer.schedule(new Task(){
                @Override
                public void run() {
                    currentFrame++;
                    if(currentFrame > 20)
                        currentFrame = 1;
                    
                    // ATTENTION! String.format() doesnt work under GWT for god knows why...
                    currentAtlasKey = String.format("%04d", currentFrame);
                    sprite.setRegion(textureAtlas.findRegion(currentAtlasKey));
                }
            }
            ,0,1/30.0f);
    }

    @Override
    public void dispose() {
        batch.dispose();
        textureAtlas.dispose();
    }

    @Override
    public void render() {        
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

 

Here is the HTML5 version of the above code:

 

 

As you can see, it’s remarkably consistent.  The majority of the code above is actually part of the demo related, as opposed to being part of using a TextureAtlas.  Instead the only import new code is:

batch = new SpriteBatch();
textureAtlas = new TextureAtlas(Gdx.files.internal("data/spritesheet.atlas"));
AtlasRegion region = textureAtlas.findRegion("0001");
sprite = new Sprite(region);

Just like working with a texture, but instead you load a TextureAtlas.  Then instead of assign the texture to the sprite, you use an AtlasRegion, which describes the coordinates of the individual sprite within the spritesheet.  You get the region by name by calling the findRegion() method and passing the key.  Remember this value is set by the file names of the source images.  The TextureAtlas needs to be dispose()’d or you will leak memory.

As you can see by the call:

sprite.setRegion(textureAtlas.findRegion(currentAtlasKey));

You can change the region within the sprite sheet that the sprite will refer to by calling setRegion().

 

The rest of the code simply positions and scales the sprite up 2.5x times.  We then schedule a Task using Timer.schedule().  This task will be called ever 30th of a second.  It simply changes the key we will use within the TextureAtlas.   In this case the files were named 0001.png, 0002.png, etc…  so we want a value between 0001 and 0020.  We then use this value to update the region the sprite refers to.  As a result every 30th of a second, the sprite moves on to the next frame of animation, rolling over when it gets to the end.

 

EDIT: I should state for the record, this is NOT how you would use a TextureAtlas to perform animation, this code was simply for demonstration purposes.  There are dedicated animation classes and we will cover them later on.

 

Be warned though, if you try to run this under GWT, you will see:

image

 

This is because the GWT compiler ( this has nothing to do with LibGDX ) doesn’t support String.format() for some reason.  If you want to run this example in a browser you can simply replace

currentAtlasKey = String.format("%04d", currentFrame);

With:

String base = new String();
        
        if(currentFrame >= 10)
            base = "00";
        else
            base = "000";
        
currentAtlasKey = base + currentFrame;

 

Now the HTML target should run just fine.

 

In the next part we will take a look at controlling input in LibGDX.

 

Configuring LibGDX to use GL 2

 

It was brought to my attention by Mario Zechner that LibGDX is not limited to power of 2 texture sizes.  That instead is an artefact of OpenGL ES 1.  If you run using OpenGL ES2 it will work fine.  That said, OpenGL and the underlying hardware still perform better if you stick to power of two.  If you want to use GL2, you set it during as part of the configuration process, we discussed briefly in the Hello World tutorial.  Simply set the useGL20 value to true in the configuration you pass in to your application listener.  Here for example is Main from the desktop project configured to use GL 2.

 

public class Main {
    public static void main(String[] args) {
        LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
        cfg.title = "graphicsdemo";
        cfg.useGL20 = true;
        cfg.width = 480;
        cfg.height = 320;
        
        new LwjglApplication(new GraphicsDemo(), cfg);
    }
}

 

Remember of course to configure this for all targets you are supporting.

 

EDIT:

12/18/2013 – It was pointed out to me that I didn’t include the atlas files, making this tutorial hard to follow along with.  I have included an archive of the data folder used for this example, you can download it here.

Programming , ,

blog comments powered by Disqus

Month List

Popular Comments