LibGDX Tutorial 9: Scene2D Part 2– Actions

In the second part of the LibGDX Scene2D tutorial we will now look at Actions.  Actions are a convenient ( and completely optional! ) way to get your game’s Actors to “do stuff”.

Let’s jump straight in to an example:

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.MoveToAction;


public class SceneDemo3 implements ApplicationListener {
    
    public class MyActor extends Actor {
        Texture texture = new Texture(Gdx.files.internal("data/jet.png"));
        public boolean started = false;

        public MyActor(){
            setBounds(getX(),getY(),texture.getWidth(),texture.getHeight());
        }
        
        @Override
        public void draw(Batch batch, float alpha){
            batch.draw(texture,this.getX(),getY());
        }
    }
    private Stage stage;
    
    @Override
    public void create() {        
        stage = new Stage();
        Gdx.input.setInputProcessor(stage);
        
        MyActor myActor = new MyActor();
        
        MoveToAction moveAction = new MoveToAction();
        moveAction.setPosition(300f, 0f);
        moveAction.setDuration(10f);
        myActor.addAction(moveAction);
        
        stage.addActor(myActor);
    }

    @Override
    public void dispose() {
    }

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

    @Override
    public void resize(int width, int height) {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

When you run this example, it will slowly move the jet until it is at the position 300,0.

MoveToActionReduced

Much of this code is recycled from the previous Scene2D example, so we will only discuss the new portions, which reside in the create() function:

@Override
public void create() {        
    stage = new Stage();
    Gdx.input.setInputProcessor(stage);
    
    MyActor myActor = new MyActor();
    
    MoveToAction moveAction = new MoveToAction();
    moveAction.setPosition(300f, 0f);
    moveAction.setDuration(10f);
    myActor.addAction(moveAction);
    
    stage.addActor(myActor);
}

Here we create a MoveToAction, which moves the attached Actor to a given position over time.  You set the position to move to with the method setPosition() and the total duration of the action using setDuration().  You assign the action to the Actor using addAction().  All actions are derived from the Action class, while MoveToAction is derived from TemporalAction, which is an action that has a duration.  In addition to MoveToAction, there are a number of others such as MoveByAction, ScaleToAction, ColorAction, DelayAction, RepeatAction, RotateByAction, etc.

There is one important thing to be aware of here.  Back in part one of the Scene2D tutorial our custom Actor MyActor overrode the act method.  It is the act() method of Actor that updates all of the Actions connected to an Actor.  Therefore if you create your own Actor class, be sure to call it’s parent’s act() or call each attached Action manually like so:

@Override
public void act(float delta){
    for(Iterator<Action> iter = this.getActions().iterator(); iter.hasNext();){
        iter.next().act(delta);
    }
}

If you run more than one actions like so:

MoveToAction moveAction = new MoveToAction();
RotateToAction rotateAction = new RotateToAction();
ScaleToAction scaleAction = new ScaleToAction(); 

moveAction.setPosition(300f, 0f);
moveAction.setDuration(5f);
rotateAction.setRotation(90f);
rotateAction.setDuration(5f);
scaleAction.setScale(0.5f);
scaleAction.setDuration(5f);

myActor.addAction(moveAction);
myActor.addAction(rotateAction);
myActor.addAction(scaleAction);

stage.addActor(myActor);

You will see:

RotateScaleMoveActionReduced

As you can see, all of your actions run concurrently by default.

There is one important thing to be aware of.  The draw() method in MyActor we’ve been using until this point is not capable of displaying rotation or scaling by default.  If you wish to enabled scaled/rotated drawing like above, you need to make a minor adjustment to draw(), like so:

@Override
public void draw(Batch batch, float alpha){
    batch.draw(texture,this.getX(),getY(),this.getOriginX(),this.getOriginY(),this.getWidth(),
            this.getHeight(),this.getScaleX(), this.getScaleY(),this.getRotation(),0,0,
            texture.getWidth(),texture.getHeight(),false,false);
}

Obviously there are going to be many times when you want to delay an action, or run them in sequence.  Fortunately LibGdx supports exactly this.  Say you wanted to scale then rotate then move your Actor, you can accomplish this using a SequenceAction like so:

final MyActor myActor = new MyActor();

SequenceAction sequenceAction = new SequenceAction();

MoveToAction moveAction = new MoveToAction();
RotateToAction rotateAction = new RotateToAction();
ScaleToAction scaleAction = new ScaleToAction(); 

moveAction.setPosition(300f, 0f);
moveAction.setDuration(5f);
rotateAction.setRotation(90f);
rotateAction.setDuration(5f);
scaleAction.setScale(0.5f);
scaleAction.setDuration(5f);

sequenceAction.addAction(scaleAction);
sequenceAction.addAction(rotateAction);
sequenceAction.addAction(moveAction);


myActor.addAction(sequenceAction);

stage.addActor(myActor);

Simply add all the actors to the SequenceAction, then one will run, followed by the next then the next.

By statically importing Actions, like so:

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;

You can also chain actions, so it’s possible to express the above action like:

myActor.addAction(sequence(scaleTo(0.5f,0.5f,5f),rotateTo(90.0f,5f),moveTo(300.0f,0f,5f)));
stage.addActor(myActor);

Or run in parallel, like so:

myActor.addAction(parallel(scaleTo(0.5f,0.5f,5f),rotateTo(90.0f,5f),moveTo(300.0f,0f,5f)));
stage.addActor(myActor);

There are several other actions, such as Int or FloatAction, that allow you to modify a value over time, DelayAction for running an action, um, after a delay, RepeatAction, for performing an Action over and over.  However, they all follow the same basic layout, so you should be able to figure them out easily at this point.  One last thing to point out, all Actions are poolable.  This means you can keep a pool of actions instead of allocating them over and over.  Here is one such example of a Pool of MoveToActions:

final MyActor myActor = new MyActor();
Pool<MoveToAction> actionPool = new Pool<MoveToAction>(){
    protected MoveToAction newObject(){
        return new MoveToAction();
    }
};

MoveToAction moveAction = actionPool.obtain();
moveAction.setDuration(5f);
moveAction.setPosition(300f, 0);

myActor.addAction(moveAction);

stage.addActor(myActor);

You can also have an action simply be code.  Consider the above example, let’s say you wanted to log when the MoveToAction was complete.  You could accomplish this by doing the following:

myActor.addAction(sequence(moveAction,
        run(new Runnable(){
            @Override
            public void run() {
                Gdx.app.log("STATUS", "Action complete");
            }
     })));

A Runnable action simply runs the code specified in the run method when, um, run. That’s about it for Actions.  On their own they are pretty simply, but can easily be chained to create very complex interactions.


Previous PartTable Of ContentsNext Part
Scroll to Top