Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon
3. November 2013

There was a new LibGDX release today, new features include:

  • 3D API.  This one has been in the works for some time and brings 3D to LibGDX built over OpenGL 2.0 ES.  Click here for more information on 3D support.
  • iOS back end moved from Xamarin’s MonoTouch to ROBOVM.  No more $300 charge to support iOS!
  • Updates to LWJGL, box2D and Bullet Physics libraries to the latest stable releases.
  • Android x86 support.  Beyond the contest not sure the win here.  Faster emulation?
  • LibGDX added to maven ( com.badlogicgames.libgdx ).
  • Gradle build option… is this one step away from the insanity that is Eclipse?  I sure hope so!
  • Small bug fixes and improvements.  See the list here.

 

LibGDX test of shader with skinning:

LibGDX bullet physics on iOS using ROBOVM

 

You can read more about the release here.

News


30. October 2013

 

In the previous tutorial we looked at handling touch and gesture events.  These days, most mobile devices have very accurate motion detection capabilities, which LibGDX fully supports.  In this example we will look at how to handle motion as well as detect if a device supports certain functionality and to detect which way the device is oriented.

 

This project revolves around a single code example, but there are some configuration steps you need to be aware of.

 

First off, in order to tell LibGDX that you want to use the compass and accelerometer, you need to pass that as part of the configuration in your Android MainActivity.  In the android project locate MainActivity.java and edit it accordingly:

package com.gamefromscratch;

import android.os.Bundle;

import com.badlogic.gdx.backends.android.AndroidApplication;

import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class MainActivity extends AndroidApplication {

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

       

        AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();

        cfg.useGL20 = true;

        cfg.useAccelerometer = true;

        cfg.useCompass = true;

       

        initialize(new MotionDemo(), cfg);

    }

}

 

The meaningful lines are

cfg.useAccelerometer = true;

and

cfg.useCompass = true;

 

These lines tell LibGDX to enable both.

Next we need to make a couple of changes to your Android manifest.  This is a configuration file of sorts that tells the Android operating system how your application performs and what permissions it requires to run.  You could literally write an entire book about dealing with Android manifests, so if you want more information read here.  The manifest is located at the root of your Android project and is called AndroidManifest.xml.  There are a couple ways you can edit it.  Simply right click AndroidManifest.xml and select Open With->.

ManifestEditAs

 

I personally prefer to simply edit using the Text Editor, but if you want a more guided experience, you can select Android Manifest Editor, which brings up this window:

Java motion android AndroidManifest xml Eclipse Users Mike Dropbox Workspace

This is basically a GUI layer over top of the Android manifest.  Using the tabs across the bottom you can switch between the different categories and a corresponding form will appear.  If you click AndroidManifest.xml it will bring up a text view of the manifest.  Use whichever interface you prefer, it makes no difference in the end.

There are two changes we want to make to the manifest.  First we want the device to support rotation, so if the user rotates their device, the application rotates accordingly.  This is done by setting the property android:screenOrientation to fullsensor.  Next we want to grant the permission android.permission.VIBRATE.  If you do not add this permission calling a vibrate call will cause your application to crash!

 

Here is how my manifest looks with changes made:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.gamefromscratch"

    android:versionCode="1"

    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="17" />

    <uses-permission android:name="android.permission.VIBRATE"/>

    <application

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name" >

        <activity

            android:name=".MainActivity"

            android:label="@string/app_name"

            android:screenOrientation="fullSensor"

            android:configChanges="keyboard|keyboardHidden|orientation|screenSize">

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

    </application>

</manifest>

The changes have been bolded above.  You want to be careful when you request additional permissions as they will be shown when the user installs your application.  Too many permissions and people start getting scared of your application.  Of course, if you need to do something that requires a permission there isn’t much you can do!  As to the screenOrientation value, this tells Android which direction to create your application as.  There are a number of options, Landscape and Portrait being two of the most common.  fullSensor basically means all directions supported.  This means you can rotate the device 360 degrees and it will be rotated accordingly.  On the other hand, if you select “user”, you cannot rotate the device 180 degrees, meaning you cannot use it upside down.  You can read more about the available properties in the link I provided earlier.

There is one last important thing to be aware of before moving on.  Your android project will actually have two AndroidManifest.xml files, one in the root directory another in the bin subfolder.  Be certain to use the one in the root directory, as the other one will be copied over!

 

Ok, now that we are fully configured, let’s jump into the code sample:

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationListener;

import com.badlogic.gdx.Gdx;

import com.badlogic.gdx.Input.Orientation;

import com.badlogic.gdx.Input.Peripheral;

import com.badlogic.gdx.graphics.Color;

import com.badlogic.gdx.graphics.GL10;

import com.badlogic.gdx.graphics.g2d.BitmapFont;

import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class MotionDemo implements ApplicationListener {

private SpriteBatch batch;

private BitmapFont font;

private String message = "Do something already!";

private float highestY = 0.0f;

@Override

public void create() {

   batch = new SpriteBatch();

   font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);

   font.setColor(Color.RED);

}

@Override

public void dispose() {

   batch.dispose();

   font.dispose();

}

@Override

public void render() {

   int w = Gdx.graphics.getWidth();

   int h = Gdx.graphics.getHeight();

   Gdx.gl.glClearColor(1, 1, 1, 1);

   Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

   batch.begin();

   int deviceAngle = Gdx.input.getRotation();

   Orientation orientation = Gdx.input.getNativeOrientation();

   float accelY = Gdx.input.getAccelerometerY();

   if(accelY > highestY)

      highestY = accelY;

   message = "Device rotated to:" + Integer.toString(deviceAngle) + " degrees\n";

   message += "Device orientation is ";

   switch(orientation){

      case Landscape:

         message += " landscape.\n";

         break;

      case Portrait:

         message += " portrait. \n";

         break;

      default:

         message += " complete crap!\n";

         break;

   }

 

   message += "Device Resolution: " + Integer.toString(w) + "," + Integer.toString(h) + "\n";

   message += "Y axis accel: " + Float.toString(accelY) + " \n";

   message += "Highest Y value: " + Float.toString(highestY) + " \n";

   if(Gdx.input.isPeripheralAvailable(Peripheral.Vibrator)){

      if(accelY > 7){

         Gdx.input.vibrate(100);

      }

   }

   if(Gdx.input.isPeripheralAvailable(Peripheral.Compass)){

      message += "Azmuth:" + Float.toString(Gdx.input.getAzimuth()) + "\n";

      message += "Pitch:" + Float.toString(Gdx.input.getPitch()) + "\n";

      message += "Roll:" + Float.toString(Gdx.input.getRoll()) + "\n";

   }

   else{

      message += "No compass available\n";

   }

   font.drawMultiLine(batch, message, 0, h);

   batch.end();

}

@Override

public void resize(int width, int height) {

   batch.dispose();

   batch = new SpriteBatch();

   String resolution = Integer.toString(width) + "," + Integer.toString(height);

   Gdx.app.log("MJF", "Resolution changed " + resolution);

}

@Override

public void pause() {

}

@Override

public void resume() {

}

 

}

 

When you run this program on a device, you should see:

Appresults

 

As you move the device, the various values will update.  If you raise your phone to be within about 30 degrees of completely upright it will vibrate.  Of course, this assumes that your device supports all these sensors that is!

 

The code itself is actually remarkably straight forward, LibGDX makes working with motion sensors remarkably easy, its just actually understanding the returned values that things get a bit more complicated.  The vast majority of the logic is in the render() method.  First we get the angle the device is rotated in.  This value is in degrees with 0 being straight in front of you parallel to your face.  One important thing to realize is this value will always have 0 as up, regardless to if you are in portrait or landscape mode.  This is something LibGDX does to make things easier for you, but is different behaviour than the Android norm.

Next we get the orientation of the device.  Orientation can be either landscape or portrait (wide screen vs tall screen).  Next we check the value of the accelerometer along the Y access using getAccelerometerY().  You can also check the accelerometer for movement in the X and Z axis using getAcceleromterX() and getAcceleromterZ() respectively.  Once again, LibGDX standardizes the axis directions, regardless to the devices orientation.  Speaking of which, Y is up.  The means if you hold your phone straight in front of you parallel to your face, the Y axis is what you would traditionally think of as up and down.  The Z axis would be in front of you, so if you made a push or pulling motion, this would be along the Z axis.  The X axis would track movements to the left and right.

So then, what exactly are the values returned by the accelerometer?  Well this part gets a bit confusing, as it measures both speed and position in a way.  If you hold your phone straight out in front of you, with the screen parallel to your face, it will return a value of 9.8.  That number should look familiar to you, it’s the speed a body falls due to gravity in meters per second.  Therefore if your phone is stationary and upright, its 9.8.  If you move the phone up parallel to your body, the value will rise above 9.8, the amount depends on how fast your are moving the phone.  Moving down on the other hand will return a value below 9.8.  If you put the phone down flat on a desk it will instead return 0. Flipping the phone upside down will instead return -9.8 if held stationary.  Obviously the same occurs along the X and Z axis, but instead that would indication motion left and right or in and out instead of up and down.

Ok, back to our code.  We check to see if the current accelY value is the highest and if it is, we record it to display.  Next we check what value the orientation returned and display the appropriate message.  We dump some information we’ve gathered out to be displayed on screen.  Next we make the very important call Gdx.input.isPeripheralAvailable().  This will return true if the users device supports the requested functionality.  First we check to see if the phone supports vibrating and if it does, we check if the phone is over 7.  Remember the value 9.8 represents straight up and down, so if its 7 or higher its within about 35 degrees of vertical.  If it is, we vibrate by calling vibrate(), the value passed is the number of milliseconds to vibrate for.

Next we check to see if the device has a compass.  If it does, you can check the position of the device relative to polar north.  Here are the descriptions of each value from Google’s documentation:

Azimuth, rotation around the Z axis (0<=azimuth<360). 0 = North, 90 = East, 180 = South, 270 = West
Pitch, rotation around X axis (-180<=pitch<=180), with positive values when the z-axis moves toward the y-axis.
Roll, rotation around Y axis (-90<=roll<=90), with positive values when the z-axis moves toward the x-axis.

You can read more about it here.

Finally we draw the message we have been composing on screen.

There is only one other very important thing to notice in this example:

public void resize(int width, int height) {

   batch.dispose();

   batch = new SpriteBatch();

   String resolution = Integer.toString(width) + "," + Integer.toString(height);

   Gdx.app.log("MJF", "Resolution changed " + resolution);

}

 

In the resize() method we dispose of and recreate our SpriteBatch().  This is because when you change the orientation of the devices from landscape to portrait or vice versa you invalidate the sprite batch, it is now the wrong size for your device.  Therefore in the resize() call, we recreate the SpriteBatch structure.

Programming


24. October 2013

 

In the previous tutorial we looked at handling mouse and keyboard events, both event driven and polled.  Now we will look at how touch works.  To follow along at this point you need to have a touch enabled device ( multi-touch with a mouse is tricky to say the least! ) although all the code will work in Desktop and HTML targets, you simply wont be able to test it.  Let’s jump right in with an example.  This example shows how to handle multiple simultaneous touches:

 

Multitouch

 

package com.gamefromscratch;

import java.util.HashMap;
import java.util.Map;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public class InputDemo2 implements ApplicationListener, InputProcessor {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "Touch something already!";
    private int w,h;
    
    class TouchInfo {
        public float touchX = 0;
        public float touchY = 0;
        public boolean touched = false;
    }
    
    private Map<Integer,TouchInfo> touches = new HashMap<Integer,TouchInfo>();
    
    @Override
    public void create() {        
        batch = new SpriteBatch();    
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
        font.setColor(Color.RED);
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
        Gdx.input.setInputProcessor(this);
        for(int i = 0; i < 5; i++){
            touches.put(i, new TouchInfo());
        }
    }

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

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        
        message = "";
        for(int i = 0; i < 5; i++){
            if(touches.get(i).touched)
                message += "Finger:" + Integer.toString(i) + "touch at:" +
                        Float.toString(touches.get(i).touchX) +
                        "," +
                        Float.toString(touches.get(i).touchY) +
                        "\n";
                                
        }
        TextBounds tb = font.getBounds(message);
        float x = w/2 - tb.width/2;
        float y = h/2 + tb.height/2;
        font.drawMultiLine(batch, message, x, y);
        
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @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) {
        if(pointer < 5){
            touches.get(pointer).touchX = screenX;
            touches.get(pointer).touchY = screenY;
            touches.get(pointer).touched = true;
        }
        return true;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        if(pointer < 5){
            touches.get(pointer).touchX = 0;
            touches.get(pointer).touchY = 0;
            touches.get(pointer).touched = false;
        }
        return true;
    }

    @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;
    }
}

Now when you run it, diagnostic information will be displayed for each finger you are touching with:

photo

 

For each finger it displays the coordinates the finger is touched up, up to a total of 5 fingers. 

So what exactly is going on here?  We create a simple class TouchInfo for holding touch details: if its touched, the X and Y coordinates.  We then create a HashMap of touches with an Integer as the key and a TouchInfo class as the data.  The key will be the index of the finger touching with.  The logic is actually in the touchDown and touchUp event handlers.  On touch down we update the touches map at the index represented by the value pointer.  As you may recall from the previous tutorial the value pointer represents which finger is currently pressed.  When the finger is released, touchUp is fired and we simply clear the touch entry at that location.  Finally in render() we loop through the touches map and display the ones that are touched and where.

At the end of the day, touches is basically identical to mouse clicks, except you can have multiple of them and there are no buttons.  Oh I suppose I should mention that the 5 touch limit in this example was just an number I picked arbitrarily.  LibGDX supports up to 20 touches although no devices do.  The iPad for example can track up to 11, the iPhone tracks up to 5, while the HTC One tracks 10.  On your Google phone you can track how many touches it supports using this app.  That said, 5 is a pretty safe and reasonable number… heck, I dont think I’ve ever used more than 4 on any device.

 

Touch gestures

 

There are a number of common gestures that have become ubiquitous in the mobile world.  Things like pinch to zoom, or flick/fling and long press have become the norm.  Fortunately GDX supports all of these things out of the box.  Let’s jump right into a simple demo:

package com.gamefromscratch;


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.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.math.Vector2;

public class InputDemo3 implements ApplicationListener, GestureListener {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "No gesture performed yet";
    private int w,h;

    
    @Override
    public void create() {        
        batch = new SpriteBatch();    
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
        font.setColor(Color.RED);
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
        
        GestureDetector gd = new GestureDetector(this);
        Gdx.input.setInputProcessor(gd);
    }

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

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        
        TextBounds tb = font.getBounds(message);
        float x = w/2 - tb.width/2;
        float y = h/2 + tb.height/2;
        
        font.drawMultiLine(batch, message, x, y);
        
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

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

    @Override
    public boolean tap(float x, float y, int count, int button) {
        message = "Tap performed, finger" + Integer.toString(button);
        return true;
    }

    @Override
    public boolean longPress(float x, float y) {
        message = "Long press performed";
        return true;
    }

    @Override
    public boolean fling(float velocityX, float velocityY, int button) {
        message = "Fling performed, velocity:" + Float.toString(velocityX) +
                "," + Float.toString(velocityY);
        return true;
    }

    @Override
    public boolean pan(float x, float y, float deltaX, float deltaY) {
        message = "Pan performed, delta:" + Float.toString(deltaX) +
                "," + Float.toString(deltaY);
        return true;
    }

    @Override
    public boolean zoom(float initialDistance, float distance) {
        message = "Zoom performed, initial Distance:" + Float.toString(initialDistance) +
                " Distance: " + Float.toString(distance);
        return true;
    }

    @Override
    public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
            Vector2 pointer1, Vector2 pointer2) {
        message = "Pinch performed";
        return true;
    }

}

 

If you run it, as you perform various gestures they will be displayed centered on the screen.  Supported gestures include tap, fling(flick), pinch ( two fingers moving closer together ), zoom ( two fingers moving apart ), pan ( one finger hold and slide ) and long press ( tap and hold ) as well as good ole fashion touch.

Just like we implemented InputProcessor to handle touch, mouse and keyboard events, we implement the GestureListener to accept gesture events from LibGDX.  In create() you create a GestureDetector using your GestureListener and once again you register it using Gdx.input.setInputProcessor().  Each different gesture triggers the corresponding even in your GestureListener.  In each, we simple update the displayed message to reflect the most recently performed event.

One important concept we didn’t cover here is configuring your GestureDetector…  how do you determine how long a long touch is, or how long must elapse before a drag becomes a flick?  The simple answer is, using the GestureDetector constructor.  You can read more about it here.

 

Handling multiple InputProcessors

 

So, what if you wanted to handle gestures AND keyboard events… what then?  Fortunately the answer is quite simple, instead of passing setInputProcessor an InputProcessor or GestureDetector, you instead pass in an InputMultiplexer.  Like so:

package com.gamefromscratch;


import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.TextBounds;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.math.Vector2;

public class InputDemo4 implements ApplicationListener, GestureListener, InputProcessor {
    private SpriteBatch batch;
    private BitmapFont font;
    private String message = "No gesture performed yet";
    private int w,h;

    
    @Override
    public void create() {        
        batch = new SpriteBatch();    
        font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
        font.setColor(Color.RED);
        w = Gdx.graphics.getWidth();
        h = Gdx.graphics.getHeight();
        
        InputMultiplexer im = new InputMultiplexer();
        GestureDetector gd = new GestureDetector(this);
        im.addProcessor(gd);
        im.addProcessor(this);
        
        
        Gdx.input.setInputProcessor(im);
    }

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

    @Override
    public void render() {        
        Gdx.gl.glClearColor(1, 1, 1, 1);
        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
        
        batch.begin();
        
        TextBounds tb = font.getBounds(message);
        float x = w/2 - tb.width/2;
        float y = h/2 + tb.height/2;
        
        font.drawMultiLine(batch, message, x, y);
        
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public boolean touchDown(float x, float y, int pointer, int button) {
        message = "Touch down!";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean tap(float x, float y, int count, int button) {
        message = "Tap performed, finger" + Integer.toString(button);
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean longPress(float x, float y) {
        message = "Long press performed";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean fling(float velocityX, float velocityY, int button) {
        message = "Fling performed, velocity:" + Float.toString(velocityX) +
                "," + Float.toString(velocityY);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean pan(float x, float y, float deltaX, float deltaY) {
        message = "Pan performed, delta:" + Float.toString(deltaX) +
                "," + Float.toString(deltaY);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean zoom(float initialDistance, float distance) {
        message = "Zoom performed, initial Distance:" + Float.toString(initialDistance) +
                " Distance: " + Float.toString(distance);
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2,
            Vector2 pointer1, Vector2 pointer2) {
        message = "Pinch performed";
        Gdx.app.log("INFO", message);

        return true;
    }

    @Override
    public boolean keyDown(int keycode) {
        message = "Key Down";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean keyUp(int keycode) {
        message = "Key up";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean keyTyped(char character) {
        message = "Key typed";
        Gdx.app.log("INFO", message);
        return true;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        message = "Touch Down";
        Gdx.app.log("INFO", message);

        return false;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        message = "Touch up";
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        message = "Touch Dragged";
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        message = "Mouse moved";
        Gdx.app.log("INFO", message);
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        message = "Scrolled";
        Gdx.app.log("INFO", message);
        return false;
    }

}

Do to the fact multiple events can be fired off at once, in addition to printing them on screen, we also log them using Gdx.app.log().  You can see logged events in the LogCat window in Eclipse:

image

There is also a program called ddms ( its a BAT script on windows ) in the android-sdk tools folder that will display the same information.

image

 

So, that’s what log() does… now back to the code.  The key part here is:

InputMultiplexer im = new InputMultiplexer();
GestureDetector gd = new GestureDetector(this);
im.addProcessor(gd);
im.addProcessor(this);

Gdx.input.setInputProcessor(im);

Essentially you create the multiplexer, then add both the InputProcessor and the GestureDetector to it via addProcessor(), then it is the multiplexer that is passed to setInputProcessor().  Otherwise the code works pretty much exactly the same.  There is two things of critical importance here.  First, the order that processors are added to the multiplexor seems to determine the order that they will have first crack at events that occurred.  Next, and this one is super important, in event handlers if you return true that means that the event is handled.  Think about that for a second, its an important concept to grasp.  While something like a touch up or down event is pretty straight forward, a say… pinch event is not.   In fact, a pinch event is composed of a number of other events, including multiple touch events.  However if you return true out of say, the touchDown event, that event will not bubble through to the gesture detector.  Therefore if you are supporting multi touch, be sure to return false from the more atomic events such as touchDown and TouchUp, so they still get a crack at handling those events!

Programming


15. October 2013

 

In this part we are going to look at how you handle mouse and keyboard input in LibGDX.  There are two ways to go about handling input, by polling for it ( as in… “Has anything happened yet? No, ok…  What about now? No, ok… Now?  Yes!  Handle it” ) or by handling events ( “Hey, you, I’v got this event for you!” ).  Which you go with generally depends on the way you structure your code.  Polling tends to be a bit more resource intensive but at the end of the day that is mostly a non-factor.

 

Polling the keyboard for input

 

Let’s jump right in and look at how you poll the keyboard for input.  Here is the code:

package input.gamefromscratch.com;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
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 InputDemo implements ApplicationListener {
    private SpriteBatch batch;
    private Texture texture;
    private Sprite sprite;
    
    @Override
    public void create() {        
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
        batch = new SpriteBatch();
        
        texture = new Texture(Gdx.files.internal("data/0001.png"));
        sprite = new Sprite(texture);
        sprite.setPosition(w/2 -sprite.getWidth()/2, h/2 - sprite.getHeight()/2);
    }

    @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);
        
        if(Gdx.input.isKeyPressed(Input.Keys.LEFT)){
            if(Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT))
                sprite.translateX(-1f);
            else
                sprite.translateX(-10.0f);
        }
        if(Gdx.input.isKeyPressed(Input.Keys.RIGHT)){
            if(Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT))
                sprite.translateX(1f);
            else
                sprite.translateX(10.0f);
        }
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }
}

 

Other than the highlighted bit and the translateX method, everything here we have seen before.  Basically we draw a simple sprite centered to the screen and each frame we check to see if the user has pressed the LEFT or RIGHT arrow.  If they have, we check if they also have the left control key held.  If so, we move slowly to the left or right.  If they don’t have Control pressed, we move instead by 10 pixels.

 

Here is the app, you need to click it first to give it keyboard focus:

 

If it doesn’t work in an frame, click here.

Just use the left and right arrows to move the jet. Hold down control to move slowly.  There is no clipping so the sprite can fly way off screen.

 

In terms of what the new code is doing, the Sprite.translateX method is pretty self explanatory.  It moves the sprite by a certain amount of pixels along the X axis.  There is a translateY method as well, as well as a more general translate method.  The key method in this example is isKeyPressed() member function of the input instance of the global Gdx object.  We used a similar instance member when we accessed Gdx.files.  These are public static references to the various sub-systems GDX depends on, you can read more here.  isKeyPressed is passed a Key value defined in the Keys object and returns true if that key is currently pressed.  As you can see when we later tested if the Control key is also pressed, multiple keys can be pressed at the same time.

 

Polling the Mouse for input

 

Now let’s take a look at how you poll the mouse for input.  To save space, this code is identical to the last example, with only the render() method replaced.

public void render() {        
    Gdx.gl.glClearColor(1, 1, 1, 1);
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    
    if(Gdx.input.isButtonPressed(Input.Buttons.LEFT)){
        sprite.setPosition(Gdx.input.getX() - sprite.getWidth()/2,
                Gdx.graphics.getHeight() - Gdx.input.getY() - sprite.getHeight()/2);
    }
    if(Gdx.input.isButtonPressed(Input.Buttons.RIGHT)){
        sprite.setPosition(Gdx.graphics.getWidth()/2 -sprite.getWidth()/2, 
                Gdx.graphics.getHeight()/2 - sprite.getHeight()/2);
    }
    batch.begin();
    sprite.draw(batch);
    batch.end();
}

 

Here we instead are checking if a mouse button has been pressed using isButtonPressed passing in a button value defined in the Buttons object.  If the left button is pressed, get then poll the mouse position using Gdx.input.getX() and Gdx.input.getY() and set the sprites location to that position.  The math may look a bit overly complicated, why didn’t we simply set the location to the values returned by getX/Y?  There are two reasons.  First, our sprites coordinate is relative to it’s bottom left corner. so if we want to center the sprite, we need to take half the sprites width and height into consideration.  The next complication comes from the fact that LibGDX sets the origin at the bottom left corner, but mouse positions are relative to the top left corner.  Simply subtracting the position from the screen height gives you the location of the mouse in screen coordinates.  We also check to see if the user as hit the right mouse button, and if they have we reposition the jet sprite at the center of the window.

 

If it doesn’t work in an frame, click here.

Once again, you need to click within above before it will start receiving mouse events ( depending on your browser ).  Left click and the sprite should move to the location you clicked.  Right click to return to default ( in theory… ), right click behaviour is a bit random in web browsers.

 

Event driven keyboard and mouse handling

 

Now we will look at handling the functionality of both of the above examples ( as a single example ), but this time using an event driven approach.

package input.gamefromscratch.com;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.InputProcessor;
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 InputDemo implements ApplicationListener, InputProcessor {
    private SpriteBatch batch;
    private Texture texture;
    private Sprite sprite;
    private float posX, posY;
    
    @Override
    public void create() {        
        float w = Gdx.graphics.getWidth();
        float h = Gdx.graphics.getHeight();
        batch = new SpriteBatch();
        
        texture = new Texture(Gdx.files.internal("data/0001.png"));
        sprite = new Sprite(texture);
        posX = w/2 - sprite.getWidth()/2;
        posY = h/2 - sprite.getHeight()/2;
        sprite.setPosition(posX,posY);
        
        Gdx.input.setInputProcessor(this);
    }

    @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);

        sprite.setPosition(posX,posY);
        batch.begin();
        sprite.draw(batch);
        batch.end();
    }

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

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public boolean keyDown(int keycode) {
        float moveAmount = 1.0f;
        if(Gdx.input.isKeyPressed(Keys.CONTROL_LEFT))
            moveAmount = 10.0f;
        
        if(keycode == Keys.LEFT)
            posX-=moveAmount;
        if(keycode == Keys.RIGHT)
            posX+=moveAmount;
        return true;
    }

    @Override
    public boolean keyUp(int keycode) {
        return false;
    }

    @Override
    public boolean keyTyped(char character) {
        return false;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        if(button == Buttons.LEFT){
            posX = screenX - sprite.getWidth()/2;
            posY = Gdx.graphics.getHeight() - screenY - sprite.getHeight()/2;
        }
        if(button == Buttons.RIGHT){
            posX = Gdx.graphics.getWidth()/2 - sprite.getWidth()/2;
            posY = Gdx.graphics.getHeight()/2 - sprite.getHeight()/2;
        }
        return false;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        return false;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        return false;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        return false;
    }
}

 

And here it is running:

If it doesn’t work in an frame, click here.

 

The code is structured quite a bit differently from when we polled for input.  The most immediate thing to be aware of is our class declaration:

public class InputDemo implements ApplicationListener, InputProcessor {

 

We are implementing another interface, InputProcessor, which as you can see adds a number of overrides to our code.  The most important ones we are dealing with here are keyDown and touchDown.  Touch you say?  Yeah, LibGDX treats the mouse and touch input as the same thing.  We will look at this in a bit more detail later on.  In addition to implementing the various methods of our interface, we also need to register our InputProcessor with the global input instance, this is done here:

Gdx.input.setInputProcessor(this);

 

At this point, our various event handlers will now be called whenever an event occurs.  keyDown will be fired when a key is pressed ( while keyUp is fired when it is released, and keyTyped is fired after it has been fired and released ).  The parameter is the value of the key pressed.  Once again, these values are available in the Keys object.  One thing you may have noticed is that we still poll to see if the Control key is pressed.  The alternative would be to set a flag when the control key is pressed and clear it when it is released.  It is important to realize that a keyDown event will be fired for each individual key fired, so if you want to handle multiple simutanious key presses, this may not be the best way to approach the subject.  Another thing you might notice is that you have to hit the key multiple times to move.  This is because a key press generates only a single event ( as does it’s release ).  If you want to have the old behavior that holding down the key will move the character continously, you will need to implement the logic yourself.  Once again, this can simply be done by setting a flag in your code on keyDown and toggle it when the keyUp event is called.

 

The touchDown event on the other hand is much more straight forward.  It can be a bit confusing handling “mouse” events called “touches”, but it makes sense.  Generally the logic you handle for both would be exactly the same, so no sense treating them differently.  The parameters passed in to touchDown are the x and y coordinates of the touch/click location, the pointer and button clicked.  On a mobile device the Button value will always be Buttons.LEFT.  Once again, screen coordinates and image coordiantes arent the same, so we need to deal with that in our positioning.  Notice how I glossed over just what exactly pointer is?  Well, pointer is a bit oddly named in my opinion.  TouchIndex would probably have made more sense, especially with pointer having a pair of very well defined meanings already.  The pointer value is value between 0 and n ( defined as 20 in LibGDX, in reality much lower ) that represents the ORDER in which the touch event occurred in the event of multiple simultaneous touches.  Therefore if you have multiple fingers touching, a pointer value of 0 would indicate that this touch event represents the first finger to touch the screen, while a value of 3 would be the fourth finger to touch the screen.  Dont worry, we will talk about this later when we deal specifically with touch.

Programming


2. October 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


GFS On YouTube

See More Tutorials on DevGa.me!

Month List