2012-07-09

#13 - libgdx Tutorial: Libgdx refactoring (version 0.9.6)

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 added
Using 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 interface
An 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;
I suggest you take some time to view all classes under com.badlogic.gdx.utils. They can really save you hours of work.

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 packFileName
But 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!

47 comments:

  1. Regarding the SplashScreen, on my game, I'm using...
    run(new Runnable() {
    @Override
    public void run() {
    game.setScreen( new MenuScreen( game ) );
    }
    })

    Instead of a new Action. I'm not sure which one's better, but the people at libgdx recommended runnable over instantiating a new Actor.

    ReplyDelete
    Replies
    1. For sure it's more classy than instantiating an Action. I'll change it soon! Thanks!

      Delete
  2. hi Gustavo, great post! but can you write a tutorial on collision detection too? I'm stuck at how to use it on actor/stage setup, i keep getting wrong collision detection. I created a class OverlapTester.
    code:
    public static boolean pointInImage(Image s, float x, float y, float scale) {
    return s.x <= x && s.x + s.width*scale >= x &&
    s.y <= y && s.y + s.height*scale >= y;
    }

    please help.

    ReplyDelete
    Replies
    1. For sure, I'll do so when the time comes!
      For now you're better off using the libgdx forum at: http://www.badlogicgames.com/forum/
      Good luck!

      Delete
    2. Your blog is really great. It has the best coverage on Scene2D I've found. Thanks for the great work!

      I would also like to see how collision detection is done.

      Delete
  3. Hi!
    First, thanks for your cool blog. :)
    Second, skin files, that are in svn repo on google-code don't work with last libgdx release (0.9.6 at this moment), but work fine with nightly build.

    ReplyDelete
    Replies
    1. Hm, but with night build I have NullPointerException in

      Button.setStyle() {
      ...
      padBottom(background.getBottomHeight());
      ...
      }

      while creating CheckBox.

      Delete
    2. This comment has been removed by the author.

      Delete
    3. I added "up: check-off" into
      com.badlogic.gdx.scenes.scene2d.ui.CheckBox$CheckBoxStyle: {
      default: {

      And check box works. But it's only hack. What's correct solution?

      Delete
    4. To be honest I don't know.. I never got into much detail for the skin feature. I tried some versions of the skin files I found in the libgdx project, until it worked.. The files I used are here:
      https://code.google.com/p/steigert-libgdx/source/browse/tags/#tags%2Fpost-20120709%2Ftyrian-android%2Fassets%2Fskin

      Thanks for reading!

      Delete
  4. I Gustavo, god tutortial, the better that I find Online, I wanted to ask you, if you know of libgdx tilemap use TMX format and if you could make a tutorial, I found several online but none explains well as make it work and am having many problems in implementing it. Thank you very much.

    ReplyDelete
    Replies
    1. Hi Carlos! Thanks! :)
      I will definitely write a post regarding tilemap.. I know it will come eventually, but I can't say for sure when.
      Thanks for reading!

      Delete
  5. Hi there,

    I'm not sure if this is important, but if you look at the pages.atlas the filter is sometimes Nearest and sometimes Linear, do you know the logic behind that? We specified in the pack.json that we want Linear.

    ReplyDelete
  6. Thanks for this tutorial and Rize is providing Android Development with qualified professionals.

    ReplyDelete
  7. Hello, so far I've been following your tutorial, and it's been working perfectly, but I've encountered a serious problem: I have the latest nightly build (8-19-12 as of this writing), and I cannot import com.badlogic.gdx.scenes.scene2d.ActorEvent. According to Google Code, ActorEvent is still used, but I have all the latest gdx libraries imported as specified in both tyrian-game and tyrian-android and neither manually inserting the import nor ctrl-shift-O is working for me. Can you please help me know what I've done wrong? Everything was going so smoothly until now (even refactoring the splash screen code, which was difficult, but it works).

    Thank you very much,
    Peter

    ReplyDelete
  8. Peter,

    I had the same problem.
    I checked the recent changes in scene2d in libgdx source.
    It seems that on 16.7.2012 nathan.sweet renamed ActorListener and ActorEvent to InputListener and InputEvent.
    So if you do the same rename in your code it should work.

    I really recommend you to download libgdx source code so you can check this kind of situations. The codebase seems to change so frequently that if you are planning to use the nightly releases you have no hope of ever making anything work if you can't monitor the changes in source code.

    To download the source code you need a GIT client, I would recommend TortoiseGit at least for Windows systems.
    With TortoiseGIT you can check out the changes, say in scene2d, by navigating to scene2d folder with file explorer, right-click, select TortoiseGit->Check for modifications.


    Gustavo, thank you for the great tutorial!
    However, I should point out that this post #13 does not work with libgdx 0.9.6 but some nightly release. 0.9.6 release does not contain the scene2d updates that are handled in this article.
    I think it should be mentioned somewhere because people may be trying to do their own projects based on 0.9.6 release and this tutorial and it wont work.

    - Divergence

    ReplyDelete
    Replies
    1. MORE FIXES! YAY!

      You should only need to rename ActorListener in one place, and that is in the DefaultActorListener class' extension. It should extend InputListener instead of ActorListener. Change any ActorEvent to InputEvent though.
      If you still find ActorListener elsewhere after doing this, you should of course change that too.

      Also, in the file uiskin.json, find the line:
      default: { background: default-slider, knob: default-slider-knob }

      and replace it with this, to make the slider in the OptionsMenu work:

      default-horizontal: { background: default-slider, knob: default-slider-knob }

      In Ship2D.java replace this:
      super.setTouchable(false);

      with this:
      super.setTouchable(Touchable.disabled);

      Delete
    2. This comment has been removed by the author.

      Delete
    3. BTW, I'm using the newest nightly build.
      It all works for me now, except ONE thing!

      The tilting of the ship has gone AWAL. It tilts both ways when going right, and it tilts a random way when going left. I tested a bit, and getRegionWidth is always 24. I think that might be the reason.

      Good luck hunting that one down. I'm going to bed.

      Thanks for the best Android game tutorial out there! Even though it doesn't really work from lesson #3 anymore, it is still the best.

      Delete
  9. I don't understand. I just started following your tutorial and I have libGDX 0.9.6 but it seems like I haven't installed it properly. If I follow the original tutorial my code compiles properly but if I try to add these changes mentioned in this post it will not work. Eclipse says "the import cannot be resolved" when adding those new Action class imports.

    ReplyDelete
    Replies
    1. Jiia,

      This article does not work for the 0.9.6 release. It is based on a nightly build (development version). If you install libgdx nightly release and do the renaming mentioned in the above post it should work (at least it worked with the nightly release I downloaded on 24.8.2012).

      Delete
  10. Hi Steigert, I want to ask about your code's lisence. Can I redistribute and/or modify it? Thanks.

    ReplyDelete
    Replies
    1. Sure, you can do anything you want with it!

      Delete
  11. You're a champ Gustavo for those tutorials, they're seriously helping me out. However as Divergence said the title is a bit misleading, this doesn't work with 0.9.6 (but the nightly builds however seem to work for me).

    ReplyDelete
  12. Info is out of this world, I would love to read more.
    additional reading

    ReplyDelete
  13. Hi all,

    This tutorial is great! Can someone send me import of his project. I want to study by changing codes. I tried to write this tutorial but after libgdx uptade my program give so many errors. Probably I did many mistakes.

    ReplyDelete
  14. Hi Gustavo.
    I tried to use the TexturePacker2 class, but I'm having an error message in the command line (I'm using CMD in windows7) that says that the main class was not loaded or was note found.

    Any thoughts?

    Thanks a lot!
    MJ

    ReplyDelete
    Replies
    1. Hi MJ, I've never tried to use the Texture Packer from the command line, but it sounds like a classpath issue. Check if you're referencing the correct JAR files in your command line.

      Delete
  15. Hello,
    Excellent series, are there any more to come?

    But a question. I'm just learning libgdx and there seems to be many ways of doing similar things. What exactly are the differences between Actors and Sprites (as in, when should you use sprites and when to use the whole scene2d system for managing and drawing game items)?

    Thanks.

    ReplyDelete
    Replies
    1. Hi Neil! Sadly Libgdx isn't a mature framework yet (in my opinion). There are a lot of refactorings going on, and most of the posts I wrote are already old. So I'll wait until Libgdx is mature enough, and them I'll start from scratch.

      The main difference of an actor and an sprite is their nature. An sprite is just a rectangular image, with geometry, color and texture information. An actor is an interactive element that easies the implementation of the game. It has many useful methods that you would end up creating by yourself if you decided not to use scene2d.

      Delete
  16. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. well if you say no steigert then it would be okey but removing comment from website is not a good thing.

      Delete
    2. Hi mysterious/anil,

      There is a problem with the comments. I just delete the spam ones, but sometimes Blogger deletes them automatically. It has happened before with other people. :(

      I received your comment via email, so here it is:
      "Hi, Thanks Once again for the amazing tutorial . But I ain't able to locate the source code zip / project folder anywhere. Downloading the sources from google code is causing me errors , especially the .json and the atlases and .pngs. It would be great if you could please provide a link sharing your source code/project. Thanks"

      First of all, thanks for reading! I think what you're looking for is here: https://code.google.com/p/steigert-libgdx/source/browse/#svn%2Ftrunk%2Ftyrian-android%2Fassets

      Let me know if I can be of any other help!

      Delete
  17. Hi Gustavo ,
    Thanks for replying . I figured out the above isssue's resolution myself by downloading the tortoise SVN client and getting all your source code on my system.
    But , Are you discontinuing with the further parts of this tutorial ?
    The enemy moves and the collision detection and all those stuffs ?
    I am waiting for those areas to be implemented . Also , instead of using the accelerometer , if we want to implement a controller in the form of a wheel like button (A circular button with up down left right and drag along circumference functionality )how do we do that ?

    I have designed two puzzle games in android but not with use of any gaming framework. You can search for them in playstore as :

    1. Numba
    2. BrainVita PaintBall

    Now i want to design a action packed game like TYRIAN and am looking forward to the left over tutorials on this blog .

    Thanks ,
    Vikash

    ReplyDelete
  18. Keep getting null return from getAtlas().findRegion("path to file")

    ReplyDelete
    Replies
    1. Figured that out: It is not "path to file" but simply "file"

      However, super.touchUp() is undefined for type Object....

      Delete
  19. Hi Gustavo,

    I really appreciate you writing this great set of tutorials! props!

    Thanks,
    Michael

    ReplyDelete
  20. Hi Gustavo, thanks for the excellent set of tutorials.

    I'm close to finishing a small educational app based from them, and wondered if you could shed some light on a couple of issues I'm having.

    On android, I have a weird bug in that the sound will play silently first, then play correctly the next time. I think It's got something to do with the LRUCache, but I can't figure out a way to correct the issue.

    Also, from the level screen, how would I go back to the main menu by pressing the android "back" key. I've tried numerous methods by just can't seem to get it.

    ReplyDelete
  21. Ignore my last comment about the back key. I've just fettled it (as is usually the way :) )

    I still have the sound issue however, and any light you could shed on it would be much appreciated.

    ReplyDelete
  22. Hey,

    Just wanted to drop by and let you know that this article really helped, when trying to understand the new changes to Scene2D.

    Thank you for the hard work :)

    Yasith

    ReplyDelete
  23. Thank You a lot for these amazing tutorials :)

    ReplyDelete
  24. I think you would be better off using an OrthographicCamera in your AbstractScreen constructor.

    camera = new OrthographicCamera();
    camera.setToOrtho(false, GAME_VIEWPORT_WIDTH, MENU_VIEWPORT_WIDTH);

    Then in the resize event:

    stage.setViewport(width, height, true);

    Your current code sizes buttons crazily when resizing the screen and when changing the view port size.

    ReplyDelete
  25. Hello and thanks for the great tutorials..i am having a problem..after the atlas is being read ..in

    Drawable splashDrawable = new TextureRegionDrawable( splashRegion );

    an exception is being thrown. I would appreciate it if anyone helped me. Below i have copied the exception.

    Exception in thread "LWJGL Application" com.badlogic.gdx.utils.GdxRuntimeException: java.lang.NullPointerException
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:111)
    Caused by: java.lang.NullPointerException
    at screens.SplashScreen.show(SplashScreen.java:49)

    ReplyDelete
  26. thanks for a lot of tutorials. I use last version of libgdx libraries so i got too much problems. Anyone fixed these problems with last version of libgdx?
    If you did it and post it, i appreciate :)

    ReplyDelete
  27. i try to change the size of checkbox, i use
    table.add( musicCheckbox ).colspan( 2 ).size(30,20).left()
    or musicCheckbox.setSize(30,20)
    or musicCheckbox.getCells().get(0).size(30, 20) but still not working.
    anyone can help me, please?

    ReplyDelete
  28. Thanks for such a series of tutorial.
    I want to develop 3x3 sliding puzzle game. But don't know how to split whole image into 9 pieces of 3x3. Anybody have idea ? Thanks.

    ReplyDelete