Libgdx version used on this post: 0.9.2 (download)
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
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
More about actions
The three actions we used on the splash image ship with libgdx. That is:- Fade-in action: com.badlogic.gdx.scenes.scene2d.actions.FadeIn
- Fade-out action: com.badlogic.gdx.scenes.scene2d.actions.FadeOut
- Delay action: com.badlogic.gdx.scenes.scene2d.actions.Delay
I suggest you take some time to check all the available actions inside the package com.badlogic.gdx.scenes.scene2d.actions, but basically there are two types of them:
- Concrete actions - Actions that modifies the actors directly (FadeIn, FadeOut, MoveBy, RotateBy, ScaleTo, etc).
- Support actions - Actions that group or organize other actions in specific ways (Delay, Sequence, Parallel, Repeat, Forever, etc).
FadeIn fadeInAction = FadeIn.$(2f);The delay action has a different factory method. The following code creates the FadeOut action 5 seconds delayed.
FadeOut fadeOutAction = FadeOut.$(2f); Delay delayAction = Delay.$(fadeOutAction, 5f);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:
You can check the complete source code for the domain model here.
Thank you for this article! I have been trying to find source code so I could learn how to make UIs in libgdx. But this is better.
ReplyDeletethank you for your tutorial I appreciate you going step by step through your thought process vs. alot of tutorials that are not as comprehensive.
ReplyDeleteThanks guys! :)
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteYour tutorials are just awesome! Maybe we need some more explanation on the implementation of the Domain Model but in all the other things the explanations was just perfect.
ReplyDeleteGood Job ! I look forward for your new posts :D
Thanks for the feedback! I'll review the domain model section. :)
DeleteIn your latest sources splashscreen,
ReplyDeleteI cant find the "Drawable splashDrawable",
I have no "import com.badlogic.gdx.scenes.scene2d.utils.Drawable;"
Maybe you're using the lastest version of the code, when you should use the tag for this post instead. Please have a look at the conclusion section, I put a link to the tag there.
DeleteThe latest version Drawble you write in Ship2D.java inside?
ReplyDeleteIf I follow the official API with create interface Drawable,its work?
http://code.google.com/p/libgdx/source/browse/branches/scene2d-new/gdx/src/com/badlogic/gdx/scenes/scene2d/utils/Drawable.java?spec=svn4265&r=4265
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.
ReplyDeleteThanks 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.
DeleteWell, 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.
DeleteAnyway, that's just my two cents. Of course, thanks for making these helpful tutorials and for responding to us in the comments!
This comment has been removed by the author.
ReplyDeleteThank you soo much for your tutorials... :)
ReplyDeleteHi Gustavo. Your tutorial is awesome, really!
ReplyDeleteI'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.
Thank you very much!
Hi MJ! You're propably using an updated version of Libgdx. Yeah, go ahead and try it out using Drawables! Have a look at the TextureRegionDrawable class.
DeleteThanks for reading!
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.
DeleteI am getting:
ReplyDeletejava.lang.NoSuchMethodError: com.badlogic.gdx.InputProcessor.touchMoved(II)Z
^this error when i run your project from your svn!
Help me please!
I'm getting the same error!
DeleteAnyone have any advice on this?
This comment has been removed by the author.
DeleteI'm getting the same error with libgdx 0.9.6 when my mouse moves on the splash screen or the menu screen.
DeleteDid you find how to fix it ?
regards
I found a solution, upgrade libgdx to the 0.9.7 version and replace :
Delete- ActorEvent by InputEvent
- actorListener by InputListener
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.
ReplyDeleteThese 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.
Any help would be appreciated.
Hello Keldor, I'm a newbie too but I can help you with the resize() method.
DeleteHe changed the abstractScreen code, see https://code.google.com/p/steigert-libgdx/source/browse/tags/post-20120227/tyrian-game/src/com/blogspot/steigert/tyrian/screens/AbstractScreen.java
The SplashScreen extends AbstractScreen, remember? Now, at each render() call, the stage.act(delta) and stage.draw() methods are called.
Great tutorial, but is there any chance of seeing an updated version? It looks like the actions have been completely rewritten in libGDX, and many of the classes and methods are depreciated.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteGreat tutorial
ReplyDeleteThanks for your great tutorial.
ReplyDeleteCool 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!
ReplyDeleteVery cool tutorial Gustavo. I'm having compilation issues with the static imports of actions:
ReplyDeleteimport static com.badlogic.gdx.scenes.scene2d.actions.Actions.delay;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.fadeIn;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.fadeOut;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.sequence;
Eclipse tells me that those imports cannot be resolved, what can I do??
Cheers
Diego
Thank you for this tutorial Gustavo. I'm a beginner in Java gaming and this is really helpful.
ReplyDeleteP.S. The game could not have been choosed better, Tyrian was my youth too!