2012-02-27

#3 - libgdx Tutorial: scene2d

All we have by now is a splash screen that displays an static image. Wouldn't it be nice to add some fading effect on this image? Things like these improve the user experience a lot, so soon we'll deal with that. As we build our graphical user interface, we need to handle things like selecting options, cliking on buttons, giving focus to an input widget and so on so forth. Can you imagine doing that with images inside image atlases? You're lucky today. There is a very cool feature of libgdx called scene2d that basically provides useful abstractions for rendering 2D components. Let's understand it better!

As of libgdx 0.9.6 some of the classes used on this post were modified or removed. You may want to read the post #13 after reading this post, which covers those changes.

About scene2d

Scene2d is a module of libgdx that eases the management and rendering of 2D components, which are called Actors. These actors live on a tree structure inside a container called Stage. They keep track of many rendering attributes, such as position relative to the parent, color, visibility, dimensions, scale and rotation factors and more. They are also responsible for hit detection.

Examples of actors would be: buttons, text fields, images, enemy targets, coins, the ship we'll fly, shots and so on. We'll use scene2d a lot in our game. Also, it's possible to apply actions on the actors, like translate, rotate, scale and fade actions. If needed, you can also write your own action, but we'll get to that later.

I'll try and summarize the main concepts of scene2d below:

Actor - A 2D component that knows how to draw itself.

Group - An actor that contains other actors.

Stage - An engine that requests the actors to be drawn and handles user interaction by distributing touch events to the actors.

Action - A function that modifies the actors' properties over time.

The following UML diagram shows it graphically:

Using scene2d

The first thing to do is setup a Stage where the actors will act. A nice place for it to live is within a screen (each screen with its own stage). These are the steps for managing the stage in our game:

Create a Stage instance on the constructor of AbstractScreen class

Resize its viewport when the screen is resized

Call the Stage's act and draw methods within the screen's render method

Dispose the stage within the screen's dispose method

For further details, please have a look at the source code of the AbstractScreen class. Now, everything we have to do is add actors to the stage. Our actors should override methods like act() and draw(), which are automatically invoked by the stage on the appropriate time.

Modifying the Splash screen

We want the splash image to be an actor so that we can do cool things with it. Instead of extending the Actor class, we can just use the Image (com.badlogic.gdx.scenes.scene2d.ui.Image) actor that comes with scene2d. We still have to load the texture and the texture region. We should also tell the image how to be drawn, and that it should take all the screen size. Have a look at the source code for the modified SplashScreen class to see how it's done.

Regarding that "actions" object inside SplashScreen.resize(), that's the coolest thing we can do with scene2d. Each actor can be animated by actions. What we do with our splash image is to add a set of the following actions:

Fade-in in 0.75 seconds

Wait for 1.75 sconds

Fade-out in 0.75 seconds

We want also to move on to the next screen when the action is completed, so all we have to do is add a completion listener to the "actions" object. Note that we didn't need to override the screen's render method on the splash screen, because the splash image is an actor that was added to the stage, and the stage is the one in charge of rendering it.

More about actions

The three actions we used on the splash image ship with libgdx. That is:

In order to have the complete effect we want, we need to join both actions:

Sequence sAction = Sequence.$(fadeInAction, delayAction);

And finally, add the action to the actor:

actor.action(sAction);

Piece of cake, isn't it? The last thing about actions I should not forget, is that you can also add interpolators to them. That means your action can start slow and them accelerate gradually, for instance. Interpolators define the rate of change for a given animation. In libgdx they also come with the "$" factory method, so you can easily create them.

Domain model

A domain model describes the entities, their attributes, roles and relationships for a given domain of interest. Most of our business logic will stay on the domain entities, because in software engineering it's a nice idea to isolate the business logic. It makes the code straightforward to other programmers, and even to ourselves later on. It also helps when switching technologies. Say we want to switch from 2D to 3D graphics. If the business logic is isolated, the impact of this change will be kept to a minimum.

With the help of scene2d we can achieve this objective rather easy, as each of the drawable domain entities can map to a scene2d actor. That's how we'll separate the business code from the presentation code. So the next task for us is to define our domain model. In order to make it simple, we're not going to implement a full clone of Tyrian, but just the main aspects of it. After analysing the game for some time, I came up with the following diagram:

Conclusion

This post was an introduction to scene2d. We saw how easy it is to use it, and how to extend it with custom functionality. We added a fade effect to the splash image and now it looks like a real splash image. Finally, we implemented the domain model for our game in an isolatted manner. The tag for this post on the Subversion is here. Thanks for reading!

I agree with Honorio - Larsen. The Domain Model should have its own tutorial entry, IMO. I was understanding everything just fine until that part, and it might throw me off for the whole tutorial. I hate having to download the source code just because i don't understand it.

Thanks for the feedback! I'll review the domain model section. I didn't want to detail it very much when I wrote the post, because it's simple and pure java. Also, it's not a final implementation, but just a start.

Well, I guess what I mean to say is that some people are simply better at learning by looking at the code than others. Personally, I'm not so great at that, and I'm surely not the only one. I would be extremely grateful if you did a post describing the domain model code in more detail. I've also been having a heck of a time getting a handle on scene2d, but that seems to be because every tutorial has a different way of doing it and the code is always spread out. Of course, I know that the code needs to be organized and such, but sometimes it just makes it harder to analyze when looking for one specific aspect at a time.

Anyway, that's just my two cents. Of course, thanks for making these helpful tutorials and for responding to us in the comments!

Hi Gustavo. Your tutorial is awesome, really!I'm having trouble with one line of code in particular in the SplashScreen.java class. The line number is 59 and my problem is that I'm having an error that says that "The constructor Image(TextureRegion, Scaling, int) is undefined". Currently I see that there is a similar constructor but instead of a TextureRegion as a parameter, it needs a Drawable one.My question is if it will works the same if I cast splashRegion to Drawable?All the rest of the code is pretty straightforward.

I got the same error. It seems that in the latest version they deleted the constructor Image(TextureRegion, Scaling, int). Instead of casting the Texture to Drawable or using TextureRegionDrawable, you simply have to use the new Image constructor Image(Texture), it will stretch and center automatically the Texture, which is what we need here.

I like your tutorial so far, but the new version of libGDX is making it very difficult. I want to learn how to use this engine for making my own games.

These are the first tutorials that I am trying because they were on the top of the list on the official libGDX website. I don't understand it well enough yet to be changing code from the example. If you have a version of this program that works with the new version of libGDX, can you share it with us?

I also don't understand why the render() method was deleted and replaced by a resize() method.

Cool tutorial, thanks a lot Gustavo. The actions model is slightly different now. I suggest anyone who finds it hard to make the code work can refer to http://code.google.com/p/libgdx/wiki/scene2d. Keep up the good work!

Very nice tutorial, Gustavo. I have a question: Having all that code in the resize() method makes all the sequence start over everytime I change the window size.I tried moving most of the code to the constructor, and on resize I just do:img.setWidth(width);img.setHeight(height);but the image size does not change.Is there a way to overcome this? Can I stop actions before they are completed?

For anyone that is having problems with adding a callback to the Actions, You need to use a RunnableAction at the end of a sequence in order to determine if the sequence has finished. Something like this works