Libgdx version used on this post: 0.9.6 (download)
As libgdx evolves we'll need to update our code base to make use of the latest improvements. The following posts were published on the official libgdx blog:
On this post I'll detail the most important changes. The first thing we should do is download the latest version of libgdx and replace the JAR and SO files in our projects. After doing that you'll notice that many compile errors will appear.
Important changes in scene2d
Getters and setters addedUsing getters and setters is a simple best practice as it improves the encapsulation of behavior, but it could hinder the performance because these methods will be called several times for each rendered frame. I suggest you keep an eye on the FPS output of your game. For Tyrian it won't make a difference. If you'd like to read more about the advantages of using getters and setters, have a look at this question at stackoverflow.
Actions system rewritten
It's now even easier to combine actions because they can operate on the same properties of an actor. The syntax has also changed a bit, so instead of FadeIn.$(0.75f) we can statically import the action and use fadeIn(0.75f). The actions you can statically import can be found in com.badlogic.gdx.scenes.scene2d.actions.Actions.
Previously we did this in SplashScreen:
Sequence actions = Sequence.$( FadeIn.$( 0.75f ), Delay.$( FadeOut.$( 0.75f ), 1.75f ) ); splashImage.action( actions );And now we do:
splashImage.addAction( sequence( fadeIn( 0.75f ), delay( 1.75f ), fadeOut( 0.75f ) ) );Generic event system developed
Each actor has a list of capture and event listeners. Both listeners extend the generic com.badlogic.gdx.scenes.scene2d.EventListener. The capture listeners may intercept the events before they're handled by the event listeners. During this capture phase, the event is given to the root down to the target actor. If the event is not cancelled by a capture listener, the normal phase kicks in and the event is given to the target actor up to the stage's root. Given that, the following is possible:
- Instead of querying the input mechanism to move our ship, we could just receive the input events as they happen and modify our actor accordingly (see com.badlogic.gdx.scenes.scene2d.ActorListener). I didn't see any event related to the accelerometer though, so let's stick to what we were doing;
- We can also fire our own events. I'm glad to see this feature, otherwise I'm pretty sure we'd have to implement it ourselves;
- We can create a hierarchy of event listeners, possibly extending one of the shipped listeners:
- ActorListener: listens for input events, like keyDown/Up/Typed, enter/exit, touchUp/Down and so on;
- ChangeListener: listens for change events, that is, when something changed in an actor;
- ActorGestureListener: makes it easy to work with gesture events, like pinch, zoom and fling.
- We can reuse event listeners by adding them to different actors.
Actions and the new event system
I was expecting to receive events as the actions started/stoped on some actor, but sadly it doesn't work that way. In order to switch from the SplashScreen to the MenuScreen, we used to do something like this:
Sequence actions = Sequence.$( FadeIn.$( 0.75f ), Delay.$( FadeOut.$( 0.75f ), 1.75f ) );
actions.setCompletionListener( new OnActionCompleted() {
public void completed( Action action ) {
game.setScreen( new MenuScreen( game ) );
}
} );
splashImage.action( actions );
Now that OnActionCompleted was removed, we can do this little trick:
splashImage.addAction( sequence( fadeIn( 0.75f ), delay( 1.75f ), fadeOut( 0.75f ),
new Action() {
public boolean act( float delta ) {
game.setScreen( new MenuScreen( game ) );
return true; // returning true consumes the event
}
} )
);
The Drawable interfaceAn abstraction was created to handle objects that know how to draw themselves: com.badlogic.gdx.scenes.scene2d.utils.Drawable. The following list shows all the currently shipped drawables, but of course you can also write your own:
- EmptyDrawable: A good drawable to extend if you're planning to write custom drawables. Provides a rectangular drawable area but draws nothing.
- NinePatchDrawable: Wraps a NinePatch, which provides dynamic image stretching. You can create nine patches with Android's draw9patch tool;
- TextureRegionDrawable: Wraps a TextureRegion, which is a rectangular area of a Texture;
- SpriteDrawable: Wraps a Sprite, which describes both a texture region, the geometry where it will be drawn, and the color it will be drawn.
Animations and the new Drawable interface
Remeber we use a 2D animation to tilt the ship? The utility class we used (com.badlogic.gdx.graphics.g2d.Animation) still requires TextureRegions for the animation's frames. The problem is that the modified Image actor now requires a Drawable, and not a TextureRegion. So we'd better create a map whose keys are TextureRegions and the values, Drawables. This way we can avoid instantiating Drawables on demand, which would wake up the garbage collector sometimes, resulting in small freezes while playing the game. Have a look at the modified com.blogspot.steigert.tyrian.screens.scene2d.Ship2D code for detailed information.
Utility classes added
Utility classes are great, aren't they? This list highlights some of them:
- Pool and Poolable: A pool of objects that can be reused to avoid allocation;
- ReflectionPool: A customization of pool that uses reflection to build instances;
- Timer: Schedules the execution of tasks in a platform independent manner;
- Array: A resizable, ordered/unordered array of objects;
- DelayedRemovalArray and SnapshotArray: Customizations of Array that handle concurrent modification in specific ways;
Documentation is being rewritten
It already contains many useful information (check it out), but meanwhile I make millions of dollars with this blog. Have you clicked the ad banner today? :)
The new TexturePacker
The TexturePacker was completely rewritten. It's now said to be much faster, easier to use and most importantly, it packs images better. A new packing algorithm is being used, the MAXRECTS, created by Jukka Jylänki. Here is the official documentation for the updated version. From the related libgdx post:- Configuration: The underscore file name conventions are gone. File names can still have a numeric index, it is simply any numbers at end of the file name, before the file extension. To better control packing in various ways, a "pack.json" file can be placed in each input directory. This contains the JSON for the com.badlogic.gdx.tools.imagepacker.TexturePacker2$Settings object. Each directory inherits all settings from the parent directory and any property set here will override those.
- Nine patches: Files with ".9" before the file extension are considered a nine patch file. The pixels around the edges are read and stripped off before packing the image. Once again, you can create nine patches with Android's draw9patch tool. The split information is stored in the pack file, enabling TextureAtlas to provide instances of NinePatch. The splits in the TextureAtlas file format are optional, so existing pack files are still compatible.
In Tyrian we'll add the pack.json file in tyrian-game/etc/images, and this file will hold the configuration values for the TexturePacker. You can play with some properties if you want to customize the output. The only non-default values I used were the "filterMin" and "filterMag", which I set to "Linear". I also removed the configuration directives from the raw images' names (e.g.: splash-image_l,l.png is now splash-image.png). To run the TexturePacker, we could execute the following command on the prompt:
java -classpath gdx.jar;gdx-tools.jar com.badlogic.gdx.tools.imagepacker.TexturePacker2 inputDir outputDir packFileNameBut we can do it inside Eclipse by creating a Java launcher. I saved it under /tyrian-game/TyrianTexturePacker2.launch, so you can open it with your Eclipse like so: Run > Run Configurations... > Java Application > TyrianTexturePacker2.
That done we can use the TextureAtlas class like this:
TextureAtlas atlas = new TextureAtlas(Gdx.files.internal("image-atlases/pages.atlas"));
AtlasRegion region = atlas.findRegion("image-name");
Sprite sprite = atlas.createSprite("image-name");
NinePatch patch = atlas.createPatch("image-name");
Important changes in Table Layout
Remember our layout descriptors? Well, they're gone. The main argument for its removal was that most of the time we "have to fallback to the code to manage the UI". As I see, the controller (screen) will be even more coupled to the view. I'm sad with this change because I like to have the layout structure in separate files, but let's follow the updated TableLayout's documentation and modify our code. Now we have to do something like this:// creates the table actor Table table = new Table(); // 100% width and 100% height on the table (fills the stage) table.setFillParent(true); // add the table to the stage stage.addActor(table); // add the welcome message with a margin-bottom of 50 units table.add( "Welcome to Tyrian for Android!" ).spaceBottom( 50 ); // move to the next row table.row(); // add the start-game button sized 300x60 with a margin-bottom of 10 units table.add( startGameButton ).size( 300f, 60f ).uniform().spaceBottom( 10 ); // move to the next row table.row(); // add the options button in a cell similiar to the start-game button's cell table.add( optionsButton ).uniform().fill().spaceBottom( 10 ); // move to the next row table.row(); // add the high-scores button in a cell similiar to the start-game button's cell table.add( highScoresButton ).uniform().fill();I removed the layout descriptors and updated all the screens to comply with this new API, and you can see the result below:
Conclusion
Libgdx is not yet mature, so we should expect future refactorings like this one. But it's evolving quite fast and it already provides many features required for commercial game projects. This is the tag on the Subversion repository for this post, and I these are the commit differences from the previous revision.Thanks for reading!
