scene2d changes

Mario and I have been refactoring the scene2d package. This is our 2D scene graph, which is useful for simple games and complex GUIs. If you already use scene2d, updating to the latest SVN may cause a few compilation errors in your projects. We don’t make API changes lightly, so please bear with us as the library improves.

Most of the changes are for the input system, which is now easier to use. Actor has these methods:

When a touch goes down, the Group class calls hit() on each child. If a child returns non-null then it was hit and touchDown() is called on the child. If this returns true, it means that the event has been consumed and no other children should be tried. It also means that if no other actor is “focused”, the child gains focus. Being focused means that the usual system just described will be bypassed and the focused widget receives all touch events. When a touchUp occurs, the focus is lost. This mechanism allows individual actors to easily handle complex touch events.

Another important change happened to Group. This class is an Actor that has children. When a group is rotated and scaled, its children are rotated and scaled. This is the main reason to use a scene graph and it is very powerful. The cost is that every frame each group does matrix transformations which use between 27 and 108 multiplications. This is not an issue for most games, as there is rarely a need for more than a few nested groups. However, when using the scene graph to create GUIs, groups are used to layout their children and it is easy to have a dozen or more nested groups. Since it is rare to use rotation and scale in a GUI, we added a boolean named “transform” to Group. When set to false, no matrix transformations will be done. Just be sure not to use rotation or scale on a group that has transform set to false!

As a result of the Group change, TableLayout is now more efficient. Feel free to nest tables to your heart’s content!

Skin saw some major changes. It used to be serialized by hand written XML (reading only). It is now serialized with our fancy automatic JSON serialization (reading and writing), with a fraction of the old code. Besides being good exercise for the JSON stuff, now that skin supports both read and write, some sort of editor may be in the works. A skin file now looks like this:

You’ll notice this is only similar to JSON (has no quotes). This is because our JSON stuff now has multiple modes: JSON, JavaScript, and the “minimal” format above. The minimal format is easier on the eyes, and if the data is only going to be read by libgdx, it really doesn’t matter if it follows the JSON spec.

Anyway, there are two parts to a skin. The “resources” section describes a bunch of colors, fonts, texture regions, nine patches, etc. The “styles” section describes a bunch of objects that use the resources. Above you can see a BitmapFont named “default-font” is defined, as well as a few Colors. Next, a LabelStyle named “default” is defined that references the font “default-font” and the color “white”. Here is some scene2d code to use this stuff:

The skin is completely extensible, you can use your own classes in the skin file to define any kind of resources or styles you want. The serialization happens automatically, thanks to the Json class, so you don’t have to add any special code to your classes.

The TableLayout SVN repo is now linked via the libgdx repo. This means a couple package names have changed. There are also a few new additions. GestureDetector detects tap, long press, fling, pan, and zoom touch events. FlickScrollPane is a scrollable area that works great on touch screens.

TL;DR: Return true from touchDown() if you handled the event and want to receive touchDragged and touchUp events. Use Group.transform = false if your group doesn’t use rotation or scale. Skin changed and is awesome. Use the JSON shit, it rocks!

23 thoughts on “scene2d changes

  1. java.lang.NullPointerException
    at ….Group.touchUp(Group.java:212)
    at ….Group.touchUp(Group.java:213)
    at ….Stage.touchUp(Stage.java:221)
    at ….AndroidInput.processEvents()
    at ….AndroidGraphics.onDrawFrame()

    in β€œlibgdx-nightly-20110902.zip” ,There is a mistake. I now can not use the new version, the old version only.plese help me!

  2. Error: java.lang.NullPointerException is reported. I have no problem with the older version, I noticed that the new edition and old edition of the Group is not the same as. Is it right? Version is bug? My code is not much to look at, very ordinary call only, as shown below:
    —————————————————————————-
    public class FoderScreen implements Screen {

    Stage stage;
    MainBackgroundTable bgg;
    MainSearchBarTable mSearchBarTable;
    Table mFolderTable;
    boolean canScroll;
    protected int mStartY;

    private static FoderScreen instance_ = new FoderScreen();

    public static FoderScreen getInstance() {
    return instance_;
    }

    private FoderScreen() {
    stage = new GameStage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
    true) {
    public boolean touchDragged(int x, int y, int pointer) {
    super.touchDragged(x, y, pointer);
    if (canScroll) {
    mFolderTable.y = mStartY + (GS.lastTouchDownY – y);
    }
    return false;
    }

    public boolean touchDown(int x, int y, int pointer, int button) {
    super.touchDown(x, y, pointer, button);
    mStartY = (int) mFolderTable.y;
    return false;
    }

    @Override
    public boolean keyDown(int keycode) {
    super.keyDown(keycode);
    if (keycode == android.view.KeyEvent.KEYCODE_BACK) {
    Gdx.input.setCatchBackKey(true);
    if (GR.CurFolder != GR.RootFolder) {
    GR.CurFolder = GR.CurFolder.parent;
    initProperty(true);
    return false;
    } else {
    GS.postUIRunnable(GS.ExitRunnable);
    return false;
    }
    }
    return false;
    }

    };

    bgg = MainBackgroundTable.getInstance();
    stage.addActor(bgg);

    mFolderTable = new FolderTable(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()-100);
    stage.addActor(mFolderTable);

    mSearchBarTable = MainSearchBarTable.getInstance();
    stage.addActor(mSearchBarTable);
    }

    private void initProperty(boolean isEnter) {
    mFolderTable.y = 50;
    mFolderTable.clear();
    int size = GR.CurFolder.size();
    if (size == 0)
    return;

    int width = (int) this.mFolderTable.width;
    int height = (int) this.mFolderTable.height;
    int folderW = 172;
    int folderH = 172;
    int columnGap = 2;
    int lineGap = 2;
    int columnCount = 2;
    int rowCount = 1;
    int maxColumnCount = (int) ((width – columnGap) / (folderW + columnGap));
    int maxRowCount = (int) ((height – lineGap) / (folderH + lineGap));
    columnGap = (width – maxColumnCount * folderW) / (maxColumnCount + 1);
    lineGap = (height – maxRowCount * folderH) / (maxRowCount + 1);

    columnCount = maxColumnCount;
    if (size 3; columnCount–) {
    rowCount = size / columnCount;
    if (size % columnCount > 0) {
    rowCount++;
    } else {
    break;
    }
    if (rowCount > maxRowCount) {
    columnCount++;
    break;
    }
    if (size % columnCount == columnCount – 1) {
    break;
    }
    }

    if (size 0) {
    rowCount++;
    }
    lineGap = (height – rowCount * folderH) / (rowCount + 1);
    columnGap = (width – columnCount * folderW) / (columnCount + 1);
    } else {
    canScroll = true;
    rowCount = size / columnCount;
    if (size % columnCount > 0) {
    rowCount++;
    }
    }

    if (GR.CurFolder == GR.RootFolder) {
    columnCount = 4;
    rowCount = size / columnCount;
    if (size % columnCount > 0) {
    rowCount++;
    }
    lineGap = (height – rowCount * folderH) / (rowCount + 1);
    columnGap = (width – columnCount * folderW) / (columnCount + 1);
    }

    for (int i = 0, j = 0; i 1) {
    GL.getInstance().setScreen(
    LawArticleListScreen.getInstance());
    }
    else if (GR.CurFolder.childs.size() == 0) {
    GL.getInstance().setScreen(
    ArticleListScreen.getInstance());

    }else {
    initProperty(true);
    }
    }
    });

    folder.destLeft += (folder.width – fi.width) / 2;
    folder.destTop -= (folder.height – fi.height) / 2;

    if (folder.destLeft Gdx.graphics.getWidth() / 2 + 80) {
    fi.x = folder.destLeft + Gdx.graphics.getWidth() / 2;
    } else {
    fi.x = folder.destLeft;
    }

    if (folder.destTop > Gdx.graphics.getHeight() / 2) {
    fi.y = folder.destTop + Gdx.graphics.getHeight() / 2;
    } else {
    fi.y = folder.destTop – Gdx.graphics.getHeight() / 2;
    }

    addEnterAction(fi);
    this.mFolderTable.addActor(fi);
    }

    /*
    * if (size < rowCount * columnCount) { int lrindex = (rowCount – 1) *
    * columnCount; if (lrindex < 0) lrindex = 0;
    *
    * columnCount = size – lrindex; columnGap = (width – columnCount *
    * folderW) / (columnCount + 1);
    *
    * for (int i = 0; i + lrindex < size; i++) { FolderInfo folder =
    * Resources.CurFolder.get(i + lrindex); folder.left = (int)
    * (this.mFolderGroup.x + columnGap + (folderW + columnGap) i);
    * folder.destLeft = folder.left; } }
    */

    }

    private void addEnterAction(final FolderImage fi) {
    float duration = 0.35f;
    MoveTo moveto = MoveTo.$(fi.folder.destLeft, fi.folder.destTop,
    duration);
    moveto.setCompletionListener(new OnActionCompleted() {
    public void completed(Action action) {
    /*Action actioncomplete = Forever.$(Parallel.$(Sequence.$(
    FadeTo.$(0.7f, 0.7f), FadeTo.$(1f, 1f))));
    fi.action(actioncomplete);*/
    }
    });
    Action action = Parallel.$(moveto);
    fi.action(action);

    }

    public FoderScreen back() {
    if (GR.CurFolder != GR.RootFolder)
    GR.CurFolder = GR.CurFolder.parent;
    return this;
    }

    @Override
    public void render(float delta) {
    Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
    bgg.updateFPS();
    stage.act(Gdx.graphics.getDeltaTime());
    stage.draw();
    }

    @Override
    public void resize(int width, int height) {
    stage.setViewport(width, height, true);
    bgg.x = 0;
    bgg.y = 0;
    bgg.resize(width, height);
    mSearchBarTable.x=0;
    mSearchBarTable.y=Gdx.graphics.getHeight()-50;
    mSearchBarTable.resize(width, height);
    mFolderTable.x = 0;
    mFolderTable.y = 50;
    mFolderTable.width=width;
    mFolderTable.height = height – 100;
    initProperty(true);
    }

    @Override
    public void show() {
    GS.postUIRunnable(MainSearchBarTable.addUIViewRunnable);
    Gdx.input.setInputProcessor(stage);
    }

    @Override
    public void hide() {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public void dispose() {
    }

    }

  3. After updating libgdx touch stopped working – only the first touched element works fine, every next touch is treated like touching the previous element. I use stage.getLastTouchedChild to determine what was touched last.

  4. Exception in thread “Thread-3” javax.media.opengl.GLException: com.badlogic.gdx.utils.GdxRuntimeException: Couldn’t parse skinFile
    at javax.media.opengl.Threading.invokeOnOpenGLThread(Threading.java:271)
    at javax.media.opengl.GLCanvas.maybeDoSingleThreadedWorkaround(GLCanvas.java:410)
    at javax.media.opengl.GLCanvas.display(GLCanvas.java:244)
    at com.badlogic.gdx.backends.jogl.JoglAnimator.display(JoglAnimator.java:137)
    at com.badlogic.gdx.backends.jogl.JoglAnimator$MainLoop.run(JoglAnimator.java:174)
    at java.lang.Thread.run(Unknown Source)
    Caused by: com.badlogic.gdx.utils.GdxRuntimeException: Couldn’t parse skinFile
    at com.badlogic.gdx.scenes.scene2d.ui.Skin.parseSkin(Skin.java:239)
    at com.badlogic.gdx.scenes.scene2d.ui.Skin.(Skin.java:219)
    at com.app.brickbreaker.MenuScreen.(MenuScreen.java:27)
    at com.app.brickbreaker.BrickBreakerGame.create(BrickBreakerGame.java:9)
    at com.badlogic.gdx.backends.jogl.JoglGraphics.init(JoglGraphics.java:87)
    at com.sun.opengl.impl.GLDrawableHelper.init(GLDrawableHelper.java:72)
    at javax.media.opengl.GLCanvas$InitAction.run(GLCanvas.java:418)
    at com.sun.opengl.impl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:189)
    at javax.media.opengl.GLCanvas$DisplayOnEventDispatchThreadAction.run(GLCanvas.java:452)
    at java.awt.event.InvocationEvent.dispatch(Unknown Source)
    at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
    at java.awt.EventQueue.access$000(Unknown Source)
    at java.awt.EventQueue$1.run(Unknown Source)
    at java.awt.EventQueue$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)
    Caused by: java.lang.IllegalArgumentException: Error parsing XML on line 1 near: {
    resources: {
    com.bad
    at com.badlogic.gdx.utils.XmlReader.parse(XmlReader.java:308)
    at com.badlogic.gdx.utils.XmlReader.parse(XmlReader.java:60)
    at com.badlogic.gdx.utils.XmlReader.parse(XmlReader.java:64)
    at com.badlogic.gdx.utils.XmlReader.parse(XmlReader.java:68)
    at com.badlogic.gdx.scenes.scene2d.ui.Skin.parseSkin(Skin.java:235)
    … 22 more

    used an example from svn to write own gui.json
    line with error :
    skin = new Skin(Gdx.files.internal(“data/gui.json”), Gdx.files.internal(“data/skin.png”));
    can it be a bug? (json file formated properly, i have fnt file with png as well)

  5. I donloaded the new nightlies packages i overwritte the files in my project and the error still occurs.
    Exception in thread “Thread-3” javax.media.opengl.GLException: com.badlogic.gdx.utils.SerializationException: Error reading file: data\uiskin.json

  6. I tried also:

    Button helpMenu = new Button(“Help”,Art.sSkin.getStyle(ButtonStyle.class),”Help”);

    but Art.sSkin cannot be found. πŸ™

  7. I just started experimenting with libgdx (am a newbie here), it was all going really great, till I went into skins. Is there any working sample skin.json that I can use for writing trial code?

    I am getting same problem that “duna” was getting on 28 Sep 2011.

    Build: 0.9.2 (tag)

    Skin File Used:
    http://code.google.com/p/libgdx/source/browse/trunk/tests/gdx-tests-lwjgl/assets/ui/uiskin.json?r=2606

    Code:
    sknButton = new Skin(Gdx.files.internal(“data/uiskin.json”), Gdx.files.internal(“data/uiskin.png”));

    Error:
    Exception in thread “Thread-3” javax.media.opengl.GLException: com.badlogic.gdx.utils.SerializationException: Error reading file: data\uiskin.json
    at javax.media.opengl.Threading.invokeOnOpenGLThread(Threading.java:271)
    at javax.media.opengl.GLCanvas.maybeDoSingleThreadedWorkaround(GLCanvas.java:410)

  8. To duna and those having that read problem: are you guys including the default font files (fnt, png) to your project as well? If not then thats probably the problem. If you open the uiskin.json file, theres a line that says: “com.badlogic.gdx.graphics.g2d.BitmapFont:” below that line you have to specify which font you are using as default for the skin. By default it says default.fnt. If you are not using the default.fnt then you need to change it to the font you are using otherwise it will give you that error and crash. If not then simply copy the default font and png from the asset folder in the gdx-tests project and paste them in your data folder. Sorry if im really late but I hope it helps those with the problem.

  9. Hi guys, in trying to implement the UI stuff I found a slight error (maybe) in the example in the post. It says

    com.badlogic.gdx.graphics.g2d.BitmapFont: {
    default-font: “default.fnt”
    }

    and I was continually getting the
    Error reading file: assets\gfx\skin\uiskin.json
    and
    No com.badlogic.gdx.graphics.g2d.BitmapFont resource registered with name: default.fnt

    even though I had default.fnt in my directory. Comparing this example to the example at http://code.google.com/p/libgdx/source/browse/trunk/tests/gdx-tests-android/assets/data/uiskin.json the difference was that it needed the file:{} parameter in it, like so

    com.badlogic.gdx.graphics.g2d.BitmapFont: { default-font: { file: default.fnt } }

    That fixed my problem straight away πŸ™‚

  10. I got the same error πŸ™
    Error reading file: assets\gfx\skin\uiskin.json
    and
    No com.badlogic.gdx.graphics.g2d.BitmapFont resource registered with name: default.fnt i checked the json file for {} and the default.fnt and defaulr.png are present in the my /data/ folder. I cant figure out what is the problem. Anyway the gdx-tests are working

  11. Exception in thread “Thread-1” javax.media.opengl.GLException: com.badlogic.gdx.utils.SerializationException: Error reading file: dat/uiskin.json

    * Only change was update to the new libgdx with this change. (if I revert to old libgdx jars it works fine)

    * When uiskin.json is missing I get a different file not found error. (Thus, I suspect the file path is not an issue)

    * Used example json skin snip it from this post and I still get the error.

    Anything different about opening the file for read/write that could be causing my issue?

  12. To everyone having the error: “Error reading file: uiskin.json”, I have found the solution thanks to the forums! Replace all the files (uiskin.json,uiskin.png,uiskin.atlas,default.fnt,default.png) with the new files of the same name located here: https://github.com/libgdx/libgdx/tree/master/tests/gdx-tests-android/assets/data

    The problem is when they refactored LibGDX a month ago, the old skin files (which all these tutorials still use) no longer worked. For more details, read this forum post: http://badlogicgames.com/forum/viewtopic.php?f=11&t=5330

    I hope this helps everyone because I’ve spent at least a month trying to figure out the answer to this, and the forums were my salvation.

  13. First things first, thank you very much for your awesome work !

    However, I don’t really like the “lose focus on touchUp” thing. I am trying to create a button slider using an extension of the Group class which contains one or many TextButtons. To enable the sliding action, I need to catch the touchDown and touchDragged on that group.

    Trying to find another way to do that though.

  14. Thanks for posting Peter πŸ˜€ You saved me a lot of pain trying to find a solution. Have been banging my head against the wall for a few hours thanks to skin files and your linked forum post fixed everything.
    Cheers

    PS an important point is to sync to nightlies rather than stable for anyone having trouble.

  15. When I’m try using “Skin skin = new Skin(JSON_skin_file, texture_file)));”, how come it’s not available? There’s an error already on Eclipse IDE appearing that “Texture texture_file” is not available in the parameter’s arguement. Can you explain me why? And also, another syntax error when I’m trying to use this code “skin.getStyle(TextButtonStyle.class)” from the LibGDX tutorial documents. Please help me and reply me as early as possible. I’m revising a Android game project that I’m gonna include a text field and a button from Scene2D.

Leave a Reply

Your email address will not be published.