Libgdx version used on this post: 0.9.2 (download)
It's time to start coding our great menu screen. Before digging into it, let's have a look at some important changes I made to the source code.
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.
Important changes
- Updated the version of libgdx to the latest version available. All I had to do was download the latest build and replace the JARs and .so files on both projects (tyrian-android and tyrian-game). We should get used to this procedure.
-
Changes on AbstractScreen:
- The collaborators (BitmapFont, SpriteBatch, Skin) are now lazily loaded.
- The show() method redirects the input processing to our Stage object through: Gdx.input.setInputProcessor( stage );
- The hide() method calls dispose(), or the dispose() method would never be called at all.
TWL - Themable Widget Library
Libgdx includes a library called TWL. This is the description copied from their official web-site: TWL is a graphical user interface library for Java built on top of OpenGL. It provides a rich set of standard widgets including labels, edit fields, tables, popups, tooltips, frames and a lot more. Different layout container are available to create even the most advanced user interfaces.When compared to libgdx's scene2d's UI abstractions, TWL is way more powerful. It contains even an HTML renderer capable of processing CSS configuration. You can fully customize your widgets using a tool called TWL Theme Editor (Java Web-Start link). For the sake of simplicity we'll stick to scene2d, but in the future we can move up to TWL.
Some examples of TWL GUIs:
|
Creating the Menu Screen with scene2d
Using the TextButton (com.badlogic.gdx.scenes.scene2d.ui.TextButton) actor that comes with libgdx we could create a simple menu screen with three buttons: "Start game", "Options" and "Hall of Fame". Let's remember the screen flow published on a previous post:We'll use the following screen layout:
- A welcome message should be displayed at the top.
- The three buttons should appear sequentially.
- All components should be vertically aligned on the center of the screen.
The most straightforward way of implementing this would be to assign absolute values to the (x,y) coordinates of each actor. If we did that, we'd come up with the following code:
package com.blogspot.steigert.tyrian.screens;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.blogspot.steigert.tyrian.Tyrian;
public class MenuScreen
extends
AbstractScreen
{
// setup the dimensions of the menu buttons
private static final float BUTTON_WIDTH = 300f;
private static final float BUTTON_HEIGHT = 60f;
private static final float BUTTON_SPACING = 10f;
public MenuScreen(
Tyrian game )
{
super( game );
}
@Override
public void resize(
int width,
int height )
{
super.resize( width, height );
final float buttonX = ( width - BUTTON_WIDTH ) / 2;
float currentY = 280f;
// label "welcome"
Label welcomeLabel = new Label( "Welcome to Tyrian for Android!", getSkin() );
welcomeLabel.x = ( ( width - welcomeLabel.width ) / 2 );
welcomeLabel.y = ( currentY + 100 );
stage.addActor( welcomeLabel );
// button "start game"
TextButton startGameButton = new TextButton( "Start game", getSkin() );
startGameButton.x = buttonX;
startGameButton.y = currentY;
startGameButton.width = BUTTON_WIDTH;
startGameButton.height = BUTTON_HEIGHT;
stage.addActor( startGameButton );
// button "options"
TextButton optionsButton = new TextButton( "Options", getSkin() );
optionsButton.x = buttonX;
optionsButton.y = ( currentY -= BUTTON_HEIGHT + BUTTON_SPACING );
optionsButton.width = BUTTON_WIDTH;
optionsButton.height = BUTTON_HEIGHT;
stage.addActor( optionsButton );
// button "hall of fame"
TextButton hallOfFameButton = new TextButton( "Hall of Fame", getSkin() );
hallOfFameButton.x = buttonX;
hallOfFameButton.y = ( currentY -= BUTTON_HEIGHT + BUTTON_SPACING );
hallOfFameButton.width = BUTTON_WIDTH;
hallOfFameButton.height = BUTTON_HEIGHT;
stage.addActor( hallOfFameButton );
}
}
And the following result:
All right, it does the job. But is this code easy to read and maintain? It contains many mathematical calculations just to come up with screen coordinates for a simple design. Let's try something better.
Introducing the TabletLayout
We could use the TableLayout project, which: is a lightweight library for describing a hierarchy of GUI widgets and laying them out using rows and columns. Assembling GUI widgets in code is clumsy and obfuscates the widget hierarchy. TableLayout provides its own simple and concise language for expressing the GUI. TableLayout cleanly describes an entire object graph of GUI widgets, leaving the Java code clean and uncluttered.That's exactly what we want, and these are the steps to follow:
- Open the TableLayout Editor application (Java Web-Start link).
- Write the layout we want following the editor's language.
- Save the layout descriptor to a text file inside our resources folder.
- On the MenuScreen class, load this layout descriptor and make final configurations.
debug * spacing:10 padding:0 align:center 'Welcome to Tyrian for Android!' spacingBottom:20 --- [startGameButton] width:300 height:60 --- [optionsButton] width:300 height:60 --- [hallOfFameButton] width:300 height:60I got the following preview:
Now we should save this layout descriptor inside our resources folder. I saved it under: tyrian-android/assets/layout-descriptors/menu-screen.txt
The final step is to modify our MenuScreen class to read this file, as follows:
package com.blogspot.steigert.tyrian.screens;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.TableLayout;
import com.blogspot.steigert.tyrian.Tyrian;
public class MenuScreen
extends
AbstractScreen
{
public MenuScreen(
Tyrian game )
{
super( game );
}
@Override
public void resize(
int width,
int height )
{
super.resize( width, height );
// retrieve the skin (created on the AbstractScreen class)
Skin skin = super.getSkin();
// create the table actor
Table table = new Table( getSkin() );
table.width = width;
table.height = height;
// add the table to the stage and retrieve its layout
stage.addActor( table );
TableLayout layout = table.getTableLayout();
// [edit] this section is not needed
// register the label "welcome"
// Label welcomeLabel = new Label( "Welcome to Tyrian for Android!", skin );
// layout.register( "welcomeMessage", welcomeLabel );
// register the button "start game"
TextButton startGameButton = new TextButton( "Start game", skin );
startGameButton.setClickListener( new ClickListener() {
@Override
public void click(
Actor actor,
float x,
float y )
{
game.setScreen( game.getStartGameScreen() );
}
} );
layout.register( "startGameButton", startGameButton );
// register the button "options"
TextButton optionsButton = new TextButton( "Options", skin );
optionsButton.setClickListener( new ClickListener() {
@Override
public void click(
Actor actor,
float x,
float y )
{
game.setScreen( game.getOptionsScreen() );
}
} );
layout.register( "optionsButton", optionsButton );
// register the button "hall of fame"
TextButton hallOfFameButton = new TextButton( "Hall of Fame", skin );
hallOfFameButton.setClickListener( new ClickListener() {
@Override
public void click(
Actor actor,
float x,
float y )
{
game.setScreen( game.getHallOfFameScreen() );
}
} );
layout.register( "hallOfFameButton", hallOfFameButton );
// finally, parse the layout descriptor
layout.parse( Gdx.files.internal( "layout-descriptors/menu-screen.txt" ).readString() );
}
}
And that's it! All mathematical calculations are gone. I also set the listeners to the buttons, so that they move the player to other screens. Note that I'm just adding the table to the stage. The other actors are registered on the table's layout, using the same IDs contained in the layout descriptor.





I'm new to Android and this just excellent! Thanks so much for sharing.
ReplyDeleteVery nice. Your articles help.
ReplyDeleteI was using tablelayout in a project of mine which is different than this articles menu screen. but a problem I had was creating the actors inside the resize method which kept duplicating actors. So i created the actors inside the create(or show) method then in the resize method I reassign width and height to table, then redid the layout (e.g. layout.reset, register all the actors, then parse the layout)
ReplyDeleteThat's true. Like create, resize is an initialization method and executing it repeatedly is not desired. I believe that's because of a design bug in libgdx. In order to avoid this problem, I do two things:
ReplyDelete1) When setting the initial screen in the Tyrian class, I do it inside the resize method after super.resize() completed.
2) In the resize method of AbstractScreen, I call "stage.clear()", removing any actors on the stage.
But your solution is more elegant. Thanks for the contribution!
Thank you very much! I am working hard on this, I was performing a similar, window based ui's but this is really interesting, I will try it out!
ReplyDeleteHas the bug with the resize being called twice been fixed? Because the resize method is not being called twice anymore.
ReplyDeleteAlso, are you part of the LibGDX project?
Yeah, it has been fixed. Check out the introduction on post #5. I used the above suggestion from xryz. And no, I'm have no connection with the libgdx project. ;)
Delete:@ Do we have to use table layout? can we not pass in texture and render the damn button
ReplyDeleteUsing TableLayout is completely optional. The advantage of using it, is that positioning your elements on the screen gets easier. It's also possible to use a custom skin to show beautiful UI widgets.
DeleteLove the tutorial, it's very helpful on a lot of points. Looking over your skin file, I went and expanded the image to explain what pieces are used where, and better understand what the components are called. Feel free to use the image if you like, but this helped me get a better understanding of the skin terminology.
ReplyDeleteExpanded Skin Atlas
The Skin feature of libgdx is somewhat complicated. It lacks documentation, so we have to end up understanding the code. :(
DeleteI thought of writing about it, but I read somewhere that the guys of libgdx will refactor this feature (alongside with the scene2d package), so let's wait and see!
Thanks for reading!
How can we download the source code for each part to test and play with it in our own ?
ReplyDeleteYou can checkout the tag related to each post by using Subversion.
Deletehttps://code.google.com/p/steigert-libgdx/
why you delete my comment ?????
ReplyDeleteHi Rawan,
DeleteI'm not deleting your comments, and I don't know why they're not appearing here.
But I received your comment in my email:
hi ,, i try to create a menu for my fame so i test your code but i faces problem with it ,,:(
eclips told me that there a problem "Error reading file: uiskin.json"
"Error loading font file: default.fnt" because it's invalid
can you help me and told me what i can do to solve this problem, please ???
I have no idea! Have you tried the libgdx forum?
http://www.badlogicgames.com/forum/
Regards
ok ,, thanks :)
ReplyDeleteThanks you very much, Gustavo, for this tutorial!
ReplyDeleteI'm working hard on this, but when i launch the desktop application the splash image appears and then, nothing but black screen, i obtain the following error, and i don't know how
to fix it :
A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0xb75eb78f, pid=20024, tid=1789393728
#
# JRE version: 6.0_24-b24
# Java VM: OpenJDK Server VM (20.0-b12 mixed mode linux-x86 )
# Derivative: IcedTea6 1.11.1
# Distribution: Ubuntu 12.04 LTS, package 6b24-1.11.1-4ubuntu3
# Problematic frame:
# C [libc.so.6+0x7578f]
Hi Makavelli,
DeleteYeah, I had some similar problems too. Check out the comments on the next post.
It seems that if you comment the "stage.dispose();" call in AbstractScreen#dispose, the problem goes away. Could you please test that?
Thanks!
Hey guys,
DeleteIt worked for me too. Is this going to have a big impact on the game?
I do have another question though, how would you show an background image underneath the table layout? It looks like it's not showing any image for me, I just have a text button that is not clickable for some reason and acts as a label with no action.
Hi Gustavo,
ReplyDeleteI have just tested it, and the problem disappeared ;)
Thanks a lot for your help!!
Hi!
ReplyDeleteI try to do this layout. but i encounter a problem with the uiskin files. I tried like 6 versions of uiskin.json and png and fonts, but it just wont work. Now it says uiskin.png is missing from the folder, when its clearly not (assets/data/uiskin.png. I even cleaned my project and it still isnt working.
It may be an eclipse bug because after closing eclipse, the whole IDE wont start, its generating an error log with some thread issue (!?)...
Anyways...Could you please send me the assets folder you got?
Thx for the tutorials, they help a lot!
In this new version of libgdx the Skin class was redesigned. Try using the skin files at: http://code.google.com/p/libgdx/source/browse/#svn%2Ftrunk%2Ftests%2Fgdx-tests-lwjgl%2Fassets%2Fui
DeleteIf you have a custom Skin, you'll have to read the new Skin documentation and update it accordingly.
And regarding the assets of this tutorial, you can get all the files at: https://code.google.com/p/steigert-libgdx/source/browse/#svn%2Ftrunk
Tip: use a Subversion client to check-out the trunk.
I still cant get it work! I put the files in the assets/ui/ folder and now it says this:
Deletehttp://pastebin.com/tYiN42xP
However if i change this:
com.badlogic.gdx.graphics.g2d.BitmapFont: {
default-font: "default.fnt"
},
to this (like it is in the documentation):
com.badlogic.gdx.graphics.g2d.BitmapFont: {
default-font: {file: default.fnt"}
},
i will get the following error:
Caused by: java.lang.ClassNotFoundException: com.badlogic.gdx.scenes.scene2d.ui.ImageToggleButton$ImageToggleButtonStyle
I'm using the latest stable version of libGDX (0.9.6).
Using the version 0.9.6 of libgdx, I got it to work with these files:
Deletehttps://code.google.com/p/steigert-libgdx/source/browse/#svn%2Ftags%2Fpost-20120709%2Ftyrian-android%2Fassets%2Fskin
I suggest you try and use them.. If it doesn't help, try posting a question in the libgdx official forum at: http://badlogicgames.com/forum/
Sorry I doesn't have enough time to help you out!
If anyone else has problems reading uiskin.json, the new files are the answer, but they (and all other code for LibGDX) have moved from Google Code to the Github repository here: https://github.com/libgdx/libgdx/tree/master/tests/gdx-tests-android/assets/data
DeleteI think i actually solved it and the code in the MenuScreen will finally run. However now, i get this:
ReplyDeletehttp://bit.ly/LOUpEg
Uh...sorry for this. I have just seen the comments above mine! Thanks for the help! :)
DeleteYou're welcome! :)
DeleteHi,
ReplyDeleteI wish to ask,in latest version,because I'm using portrait with 320 x 480 size of screen.
But seen the menu button look too big,how do I adjust it?
Sorry about this just learning libgdx.
In the post #9 you'll find some answers! :)
DeleteHi,
ReplyDeleteI am getting following error related to reading of menu-screen.txt:
Exception in thread "LWJGL Application" com.esotericsoftware.tablelayout.ParseException: Error parsing layout on line 5:2 near: !DOCTYPE html>
What can be the reason?
Google search doesn't give much on this issue.
Manish
It seems like an HTML tag! The menu-screen.txt should not contain it. Have a look at the original menu-screen.txt file at: https://code.google.com/p/steigert-libgdx/source/browse/tags/post-20120301/tyrian-android/assets/layout-descriptors/menu-screen.txt
DeleteAlso, make sure you're using libgdx 0.9.2 for using the examples in this post. Notice that I added a "Libgdx version used on this post: x.y.z" at the beggining of all posts..
Thank you so much for this tutorial. However i am not able to move forward because i am getting errors in import: ui.ClickListener, ui.tablelayout.Table, ui.tablelayout.TableLayout. It says it cannot be resolved. Because of these import errors the whole file is littered with errors everywhere.
ReplyDeleteHi Pritam,
DeleteIt looks like they rewrote the Event system in libgdx recently to be more general and flexible (read: better), as Gustavo explains in his tutorial #13. Instead of ui.ClickListener, I think actor elements have their own addListener() method in which you can supply a handler method.
Maybe Gustavo can correct me if I am wrong about this.
You're correct, Michael. The refactorings of libgdx created some confusion. That's why I put below the post's title the libgdx version I used to write it. Also, I think I will wait for libgdx 1.0 before moving on with the tutorial.
DeleteThis comment has been removed by the author.
ReplyDeleteHello,
ReplyDeleteI don't know why but 19 out of 20 times, I giot the following error when I launch the game :
Tyrian: Creating game on Desktop
Tyrian: Resizing game to: 800 x 480
Tyrian: Showing screen: SplashScreen
Tyrian: Resizing screen: SplashScreen to: 800 x 480
Tyrian: Setting screen: SplashScreen
FPSLogger: fps: 0
FPSLogger: fps: 64
FPSLogger: fps: 63
Tyrian: Hiding screen: SplashScreen
Tyrian: Disposing screen: SplashScreen
Tyrian: Showing screen: MenuScreen
Tyrian: Resizing screen: MenuScreen to: 800 x 480
Tyrian: Setting screen: MenuScreen
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000005f5510f0, pid=5436, tid=4772
#
# JRE version: 7.0_06-b24
# Java VM: Java HotSpot(TM) 64-Bit Server VM (23.2-b09 mixed mode windows-amd64 compressed oops)
# Problematic frame:
# C [atio6axx.dll+0x2210f0]
I'm using lbgdx 0.9.6 eclipse juno. I tried to integrate the files of your subversion but the error continues.
Can you help me ?
I found the problem and I fixed it by commenting out the line stage.dispose() in the AbstractScreen class. But I don't know why
DeleteI think I figured out the issue with disposing of the stage. If you look at the code for the Stage class, you'll see that the dispose() method only disposes of its SpriteBatch object, if it owns it. The clear() method does most of the clean up. I call the clear() method before dispose(), and I haven't seen the error, yet.
ReplyDeleteIt works for me too: libgdx 0.9.5
DeleteIsn't it a libgdx bug?
Hi I'm having problems how to create or get hold of a skin file, could you point me in the right direction please
ReplyDeleteIt's in the android project under assets folder:
Deletehttps://code.google.com/p/steigert-libgdx/source/browse/#svn%2Ftags%2Fpost-20120301%2Ftyrian-android%2Fassets%253Fstate%253Dclosed
Hi Gustavo,
ReplyDeleteI played around with the TWL demos and it looks really neat. But, it sounds like they say it isn't ready to run on android yet...
"TWL is also supported on Android but to use it an Android compatible renderer needs to be written"
Has this "renderer" been built since that was written? If not, do you plan on writing one?
Thanks,
Michael
Hi is there a link to data/uiskin.png I don't have it so it's causing an error.
ReplyDeleteI checked
Source path: svn/ trunk/ tyrian-android/ assets/ skin/ uiskin.png
but only this came up
This file is not plain text (only UTF-8 and Latin-1 text encodings are currently supported).
Go to https://code.google.com/p/steigert-libgdx/source/browse/tags/post-20120301/tyrian-android/assets/uiskin.png and then click "View raw file" on the right. You can download the file that comes up.
Deletegetscreen() gives error
ReplyDeleteTableLayout layout = table.getTableLayout();
ReplyDeleteis not working for libgdx 0.9.8. I think they have made some changes in their API. can you suggest me the method like above for 0.9.8 version. I can create the UI without using above UI management trick but then my code would look clumsy and your other examples are also based on TableLayout editor code. So, please help in figuring out how to use TableLayout like thing in 0.9.8 version.
Thanks
Can I download the source code and run it on EClipse... i am a newbie and find your tutorial the best among others... getting the project ...playing it and then begin to learn will be a great help.
ReplyDelete