Overview
Scene2d.ui LibGDX.info for Scene2d examples (Image, Label etc…) It provides the following features:
- Rotation and scale of a group is applied to all child actors. Child actors always work in their own coordinate system, parent transformations are applied transparently.
- SpriteBatch. Each actor draws in its own un-rotated and unscaled coordinate system where 0,0 is the bottom left corner of the actor.
- Hit detection of rotated and scaled actors. Each actor determines if it is hit using its own un-rotated and unscaled coordinate system.
- Routing of input and other events to the appropriate actor. The event system is flexible, enabling parent actors to handle events before or after children.
- Action system for easy manipulation of actors over time. Actions can be chained and combined for complex effects. scene2d.ui package provides many actors and other utilities specifically for building UIs. Scene graphs have the drawback that they couple model and view. Actors store data that is often considered model data in games, such as their size and position. Actors are also the view, as they know how to draw themselves. This coupling makes MVC separation difficult. When used solely for UIs or for apps that don’t care about MVC, the coupling is not an issue. scene2d has three classes at its core:
- The Actor class is a node in the graph which has a position, rectangular size, origin, scale, rotation, and color.
- The Group class is an actor that may have child actors.
- The Stage class has a camera, SpriteBatch, and a root group and handles drawing the actors and distributing input events.
Stage
Stage is an InputProcessor. When it receives input events, it fires them on the appropriate actors. If the stage is being used as a UI on top of other content (eg, a HUD), an InputMultiplexer can be used to first give the stage a chance to handle an event. If an actor in the stage handles an event, stage’s InputProcessor methods will return true, indicating the event has been handled and should not continue on to the next InputProcessor.actmethod that takes a delta time since last frame. This causes theactmethod on every actor in the scene to be called, allowing the actors to take some action based on time. By default, the Actoractmethod updates all actions on the actor. Callingacton the stage is optional, but actor actions and enter/exit events will not occur if it is omitted.Viewport
Viewport instance. The viewport manages aCameraand controls how the stage is displayed on the screen, the stage’s aspect ratio (whether it is stretched) and whether black bars appear (letterboxing). The viewport also converts screen coordinates to and from stage coordinates.setViewport. If running where the application window can be resized (eg, on the desktop), the stage’s viewport should be set when the application window is resized.ScreenViewport. With this viewport, each unit in the stage corresponds to 1 pixel. This means the stage is never stretched, but more or less of the stage is visible depending on the size of the screen or window. This is often useful for UI applications.private Stage stage;public void create () { stage = new Stage(new ScreenViewport()); Gdx.input.setInputProcessor(stage);}public void resize (int width, int height) { // See below for what true means. stage.getViewport().update(width, height, true);}public void render () { float delta = Gdx.graphics.getDeltaTime(); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); stage.act(delta); stage.draw();}public void dispose () { stage.dispose();}
truewhen updating the viewport changes the camera position so it is centered on the stage, making 0,0 the bottom left corner. This is useful for UIs, where the camera position is not usually changed. When managing the camera position yourself, pass false or omit the boolean. If the stage position is not set, by default 0,0 will be in the center of the screen.StretchViewport. The stage’s size of 640x480 will be stretched to the screen size, potentially changing the stage’s aspect ratio.stage = new Stage(new StretchViewport(640, 480));
FitViewport. The stage’s size of 640x480 is scaled to fit the screen without changing the aspect ratio, then black bars are added on either side to take up the remaining space (letterboxing).stage = new Stage(new FitViewport(640, 480));
ExtendViewport. The stage’s size of 640x480 is first scaled to fit without changing the aspect ratio, then the stage’s shorter dimension is increased to fill the screen. The aspect ratio is not changed and there are no black bars, but the stage may be longer in one direction.stage = new Stage(new ExtendViewport(640, 480));
ExtendViewportwith a maximum size. As before, the stage’s size of 640x480 is first scaled to fit without changing the aspect ratio, then the stage’s shorter dimension is increased to fill the screen. However, the stage’s size won’t be increased beyond the maximum size of 800x480. This approach allows you to show more of the world to support many different aspect ratios without showing black bars.stage = new Stage(new ExtendViewport(640, 480, 800, 480));
glViewport, so the stage cannot draw within the black bars. TheglViewportcan be set to the full screen to draw in the black bars outside of the stage.
Viewport for more information.// Set the viewport to the whole screen.Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());// Draw anywhere on the screen.// Restore the stage's viewport.stage.getViewport().update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
Drawing
drawis called on the stage, it calls draw on every actor in the stage. Actors’drawmethod can be overridden to perform drawing:public class MyActor extends Actor { TextureRegion region; public MyActor () { region = new TextureRegion(...); setBounds(region.getRegionX(), region.getRegionY(), region.getRegionWidth(), region.getRegionHeight()); } @Override public void draw (Batch batch, float parentAlpha) { Color color = getColor(); batch.setColor(color.r, color.g, color.b, color.a * parentAlpha); batch.draw(region, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation()); }}
drawmethod draws a region using the position, origin, size, scale, and rotation of the actor. The Batch passed to draw is configured to draw in the parent’s coordinates, so 0,0 is the bottom left corner of the parent. This makes drawing simple, even if the parent is rotated and scaled. Batch begin has already been called. If theparentAlphais combined with this actor’s alpha as shown, child actors will be influenced by the parent’s translucency. Note the color of the Batch may be changed by other actors and should be set by each actor before it draws.setVisible(false)is called on an actor, its draw method will not be called. It will also not receive input events.ShapeRenderer, the Batch should be ended and then begun again at the end of the method. Of course, this causes the batch to be flushed, so should be used judiciously. The transformation and projection matrices from the Batch can be used:private ShapeRenderer renderer = new ShapeRenderer();public void draw (Batch batch, float parentAlpha) { batch.end(); renderer.setProjectionMatrix(batch.getProjectionMatrix()); renderer.setTransformMatrix(batch.getTransformMatrix()); renderer.translate(getX(), getY(), 0); renderer.begin(ShapeType.Filled); renderer.setColor(Color.BLUE); renderer.rect(0, 0, getWidth(), getHeight()); renderer.end(); batch.begin();}
Group transform
setTransform(false)can be used for the group. When this is done, each child’s position will be offset by the group’s position for drawing, causing the children to appear in the correct location even though the Batch has not been transformed. This cannot be used for a group that has rotation or scale.Hit detection
hitmethod receives a point and returns the deepest actor at that point, or null if no actor was hit. Here is the defaulthitmethod:public Actor hit (float x, float y, boolean touchable) { if (touchable && getTouchable() != Touchable.enabled) return null; return x >= 0 && x < width && y >= 0 && y < height ? this : null;}
touchableboolean parameter indicates if the actor’s touchability should be respected. This enables hit detection for purposes other than touch on actors that are not touchable.hitis called on the stage,hitis called on the stage’s root group, which in turn callshiton each child. The first non-null actor found is returned as the actor deepest in the hierarchy that contains the given point.Event system
scene2d uses a generic event system. Each actor has a list of listeners that are notified for events on that actor. Events are propagated in two phases. First, during the “capture” phase an event is given to each actor from the root down to the target actor. Only capture listeners are notified during this phase. This gives parents a chance to intercept and potentially cancel events before children see them. Next, during the “normal” phase the event is given to each actor from the target up to the root. Only normal listeners are notified during this phase. This allows actors to handle an event themselves or let the parent have a try at it.stopis called on the event, any remaining listeners for the current actor are still notified, but after those no other actors will receive the event. This can be used to prevent children (during the capture phase) or parents (during the normal phase) from seeing the event. Ifcancelis called on the event, it stops propagation the same as stop and also prevents any default action that would have been taken by the code that fired the event. E.g., if the event is for a check-box being checked, cancelling the event could prevent the check-box from being checked. For example, imagine a group (a button) which has a child (a label). When the label is clicked, capture listeners are fired. Usually there are none. Next, the label’s normal listeners are notified. The label is both the target and the listener actor. If the event was not stopped, the button gets the event and its normal listeners are notified. The label is the target and the button is the listener actor. This continues up to the root. This system allows a single listener on a parent to handle events on its children.InputListener
handle(Event)method. Classes that implement the EventListener interface useinstanceofto determine whether they should handle the event. For most types of events, specific listener classes are provided for convenience. For example, InputListener is provided for receiving and handling InputEvents. An actor just needs to add an InputListener to start receiving input events. InputListener has several methods that may be overridden, and two are shown below:
Note that the actor must specify its bounds in order to receive input events within those bounds.actor.setBounds(0, 0, texture.getWidth(), texture.getHeight());actor.addListener(new InputListener() { public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { System.out.println("down"); return true; } public void touchUp (InputEvent event, float x, float y, int pointer, int button) { System.out.println("up"); }});
touchDown,touchDragged, andtouchUp. The touchDragged and touchUp events will only be received if touchDown returns true. Also, the touchDragged and touchUp events will be received even if they do not occur over the actor. This simplifies the most common touch event use cases.enterorexit.mouseMovedmethod.scrolledmethod. This will only be called on the actor with scroll focus, which is set and cleared by calling setScrollFocus on the stage.keyDown,keyUp, andkeyTypedmethods. These will only be called on the actor with keyboard focus, which is set and cleared by callingsetKeyboardFocuson the stage.setTouchable(false)orsetVisible(false)is called on an actor, it will not receive input events.Other listeners
clickedmethod that is called when the actor is clicked. ActorGestureListener detects tap, longPress, fling, pan, zoom, and pinch gestures on an actor.actor.addListener(new ActorGestureListener() { public boolean longPress (Actor actor, float x, float y) { System.out.println("long press " + x + ", " + y); return true; } public void fling (InputEvent event, float velocityX, float velocityY, int button) { System.out.println("fling " + velocityX + ", " + velocityY); } public void zoom (InputEvent event, float initialDistance, float distance) { System.out.println("zoom " + initialDistance + ", " + distance); }});
Actions
actmethod. Many types of actions are included with libgdx. These can be instantiated, configured, and added to an actor. When the action is complete, it will automatically be removed from the actor.
LibGDX.info for a tutorial on ActionsMoveToAction action = new MoveToAction();action.setPosition(x, y);action.setDuration(duration);actor.addAction(action);
Action pooling
To avoid allocating a new action each time it is needed, a pool can be used:
When the action is complete, it is removed from the actor and put back in the pool for reuse. The above code is quite verbose though. The Actions class (note the plural) provides convenience methods. It can provide pooled actions:Pool<MoveToAction> pool = new Pool<MoveToAction>() { protected MoveToAction newObject () { return new MoveToAction(); }};MoveToAction action = pool.obtain();action.setPool(pool);action.setPosition(x, y);action.setDuration(duration);actor.addAction(action);
Even better, the Actions class has methods that return a pooled and configured action.MoveToAction action = Actions.action(MoveToAction.class);action.setPosition(x, y);action.setDuration(duration);actor.addAction(action);
The Actions class has many of these convenience methods for completely configuring any of the built in actions using a single method call. This can be made even simpler by using a static import, which allows the static methods to be referenced on Actions without specifying “Actions.” each time. Note that Eclipse will not add a static import for you, you must add it yourself.actor.addAction(Actions.moveTo(x, y, duration));
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;...actor.addAction(moveTo(x, y, duration));
Complex actions
More complex actions can be built by running actions at the same time or in sequence. ParallelAction has a list of actions and runs them at the same time. SequenceAction has a list of actions and runs them one after another. Use of the static import for the Actions class makes defining complex actions very easy:actor.addAction(sequence(moveTo(200, 100, 2), color(Color.RED, 6), delay(0.5f), rotateTo(180, 5)));
Action completion
To run code when an action is complete, a sequence with a RunnableAction can be used:actor.addAction(sequence(fadeIn(2), run(new Runnable() { public void run () { System.out.println("Action complete!"); }})));
Interpolation
InterpolationTest for an interactive demo of each interpolation.MoveToAction action = Actions.action(MoveToAction.class);action.setPosition(x, y);action.setDuration(duration);action.setInterpolation(Interpolation.bounceOut);actor.addAction(action);
bounceOutandswinginterpolations are used:import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;import static com.badlogic.gdx.math.Interpolation.*;...actor.addAction(parallel(moveTo(250, 250, 2, bounceOut), color(Color.RED, 6), delay(0.5f), rotateTo(180, 5, swing)));actor.addAction(forever(sequence(scaleTo(2, 2, 0.5f), scaleTo(1, 1, 0.5f), delay(0.5f))));
External Links
- netthreads A fully documented scene2d example game.
- gdx-ui-app A library on top of scene2d for easier development.
- Should I use scene2d for my game?
- Street Race game tutorial
