Oh, the Places You'll Go! - A Guide to Character Movement in libGDX
Tags: libgdx, gamedev, beginners, java
Published: 2023-01-14 09:30:17

![Magika moving in a circle in her bedroom](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5se2ukye4ahtn4fs51bg.gif)

> "First we'll move our wee Vector2! This float pair will become dear to you. Once we have an x and a y, I think you and I, will have little work left to do."
> *- adapted from the libGDX Discord*

Are you new to libGDX and are struggling with character movement in 2D? Well, fellow explorer of a seemingly rule-less world, you've come to the right place.

I'm 250 hours into programming a Stardew Valley inspired 2D RPG - and let me tell you, it's been a ride. I'm still relatively new to programming, so it's been cool learning about fancy software patterns, wrangle a big project, and become besties with Ms. Red Debug Circle.

But we definitely encountered some meltdowns along the way. Especially in the beginning I struggled with figuring out the big design decisions. libGDX gives you all the freedom in the world, but sometimes that can feel a little overwhelming.

That's why I'll be showing you today how I answered the question "But how do I get my character moving?!".


### Laying the land

> "libGDX is a cross-platform Java game development framework. [It] does not force a specific design or coding style on you; it rather gives you the freedom to create a game the way you prefer".
> *- from https://libgdx.com/*

Before diving into the specifics of translating user input into 2D character movement, let's go over a few ground rules so we can keep this post focussed on the topic of movement.

This post assumes that you have a basic understanding of how to [set up a libGDX project](https://libgdx.com/wiki/start/setup) and create a game. If not, don't worry! There are [many tutorials available](https://project-awesome.org/rafaskb/awesome-libgdx), such as my personal favourite, [ColdWild's Multiplatform tutorial](https://www.youtube.com/watch?v=BJZT1HeulNQ).

I also will not be covering map creation in this post, but I highly recommend using [Tiled](https://www.mapeditor.org/) for all your map-making needs.

Nevertheless, if you have any libGDX questions, feel free to reach out or comment on the blog. I'll do my best to answer and if it's a bigger topic, I might tackle it in a future post.


----------------------------------------------------------


# Oh, the places you'll go
## - How to set up your Player Actor -

<table><tr>
<td> <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyaiqq6rjs7r2z80j1pl.png" alt="Magika exploring the world" /> </td>
<td> <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7pqnszhbi3p72npuyq4e.png" alt="Magika exploring the world"/> </td>
<td> <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ghkyso9ftmys12y0yt3s.png" alt="Magika exploring the world" /> </td>
</tr></table>

## `Player` Object

The first thing you'll want to do is create your `Player` object - the representative of our user, the star of the show, the walking impersonation of main-character-syndrome. But instead of giving them a personality and a story, we set up:
- a [Sprite](https://libgdx.com/wiki/graphics/2d/spritebatch-textureregions-and-sprites#sprite) (the picture of the `Player`),
- the [Actor](https://libgdx.com/wiki/graphics/2d/scene2d/scene2d#:~:text=The%20Actor%20class%20is%20a%20node%20in%20the%20graph%20which%20has%20a%20position%2C%20rectangular%20size%2C%20origin%2C%20scale%2C%20rotation%2C%20and%20color.) (a 2D scene graphic node, aka a sprite that has a location and size that can be tracked by Scene2D and can `act` upon events, and
- the [Box2D body](https://libgdx.com/wiki/extensions/physics/box2d#objectsbodies)

*Note: the below code has been stripped from everything not related to movement. Your `Player` will probably be quite a big more extensive*

```java
//Player.java
public class Player extends Actor {
   [...]

    public Player(Vector2 startPosition) {
        super();
        sprite = [sprite retrieving logic];

        // Set the actor's x and y coordinates, as well as width and height.
        setBounds(startPosition.x, startPosition.y, sprite.getWidth(), sprite.getHeight());

        // Create the Box2D body for the Player
        physicalBody = [libgdx Body creation logic]

        // connect Actor with Box2D body
        physicalBody.setUserData(this); 

        [...]
     }
```

In the next step, we will want to ensure that our `Player` moves at the correct speed. To do so, we call `trackMovement` whenever our `Player`(which is an `Actor`) is called upon to [act](https://libgdx.com/wiki/graphics/2d/scene2d/scene2d#:~:text=Stage%20has%20an,it%20is%20omitted).

```java
//Player.java
public class Player extends Actor {
   [...]

    public float speed = 5 * Resources.TILE_SIZE; // = 5 * 48
    private Vector2 currentVelocity = new Vector2(0, 0); // starting velocity is 0 in either direction x or y; aka we're standing still

    @Override
    public void act(float delta) {
        super.act(delta);

        trackMovement(delta);
    }

    private void trackMovement(float delta) {
        // calculate how far the body has moved by multiplying the constant speed by how much time has elapsed 
        float movement = delta * speed;

        // move the physical body by multiplying current velocity with the movement
        physicalBody.setLinearVelocity(currentVelocity.cpy().scl(movement));

        // move sprite by moving actor to the same coordinates as the physical body
        this.setPosition(physicalBody.getPosition().x - Resources.WORLD_TILE_SIZE / 2f, physicalBody.getPosition().y - Resources.WORLD_TILE_SIZE / 2f);
    }
```

Prize question: Where will our character move where while 
`currentVelocity == Vector2(0, 0)`?
Answer: Nowhere.

So how do we get out of The Waiting Place?

![Magika, waiting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5yg2qbjxjee1y4g7ihru.gif)

>"Waiting for a train to go or a bus to come,
>or a plane to go or the mail to come,
>or the rain to go or the phone to ring,
>or the snow to snow or waiting around for a Yes or No
>or waiting for their hair to grow.
>
>Everyone is just waiting."
> *- Dr. Seuss*

----------------------------------------------------------

## Capturing Inputs with an `InputListener`

### Within `Player`

My `Player` class listens to a set of `InputListener`s; which one depends on its current state.
When the `Player` is walking around freely, it listens to input events relating to movement, environment checks, etc. But, for example, when the `Player` is sitting, movement inputs will be ignored while `Player` reacts to other inputs directing sitting-related tasks.

For the sake of this tutorial, however, we'll only be adding one listener to our `Player`: the `FreeRoamingMovementListener`.

```java
//Player.java
    public Player(Vector2 startPosition) {
        [...]
        addListener(FreeRoamingMovementListener(this));
    }
```

### Within `FreeRoamingMovementListener`

This listener is all about capturing user input and translating it into movement. To do so, it overrides both the method `keyDown` (so we keep applying movement while the key is pressed) as well as `keyUp` (in order to stop the moment a key is released)

> "`EventListeners` are added to actors to be notified about events. For example, `InputListener` is provided for receiving and handling InputEvents. An actor just needs to add an `InputListener` to start receiving input events. [It] has several methods that may be overridden."
> *- From the [Wiki](https://libgdx.com/wiki/graphics/2d/scene2d/scene2d#inputlistener)*

#### @Override `keyDown()`

When a key is pressed, it goes through several steps:
1. Keep track of all pressed keys (in `pressedKeyCodes`)
2. Determines the state of the `Player` - is it moving North, East, South-West, ...?
3. Calculates the corresponding force/velocity on the `Player`'s Box2D `Body` & `Sprite`
4. Informs `Player` of its new state

```java
//FreeRoamingMovementListener.java
class FreeRoamingMovementListener extends InputListener {
    private final Set<Integer> pressedKeyCodes = new HashSet<>(); // note: chose a set because we care about values being unique and want efficient lookups

    [...]

    @Override
    public boolean keyDown(InputEvent event, int keycode) {
        pressedKeyCodes.add(keycode);

        // Step 1: Determining the state.
        PlayerState state = getPlayerStateBasedOnCurrentlyPressedKeys();
        if (state == null) {
            pressedKeyCodes.remove(keycode);
            return false;
        }

        // Step 2: Translate the direction the player is facing into velocity
        Vector2 newVelocity = state.calculateDirectionVector();

        // Step 3: Update velocity & state in `Player`
        player.setStateAndVelocity(state, newVelocity);

        return true;
    }
```

Let's cover each step in more detail.


**Step 1: Determining the state.**

With the press of `W`, `S`, `A`, or `D`, our `Player` can move North, South, West and East respectively; on an especially fancy day, we can even head North-East, South-West, and all points in between. The trick is to keep track of that state to determine things like the correct sprite, animation and interactionable-object search radius.

I use `enum PlayerState` to keep track of a `Player`'s movement type and direction (see its definition under Step 2), and the following code helps us find the right state for the given input.

*Note: This code needs a bit of fine-tuning to avoid preferring certain directions. But for now, it'll do the trick.*

```java
//FreeRoamingMovementListener.java
class FreeRoamingMovementListener extends InputListener {
    [...]

    private PlayerState getPlayerStateBasedOnCurrentlyPressedKeys() {
        // get sprite based on walking direction
        if (pressedKeyCodes.contains(Input.Keys.UP)) {
            if (pressedKeyCodes.contains(Input.Keys.RIGHT)) {
                return PlayerState.WALKING_NE;
            } else if (pressedKeyCodes.contains(Input.Keys.LEFT)) {
                return PlayerState.WALKING_NW;
            } else {
                return PlayerState.WALKING_N;
            }
        } else if (pressedKeyCodes.contains(Input.Keys.DOWN)) {
            if (pressedKeyCodes.contains(Input.Keys.RIGHT)) {
                return PlayerState.WALKING_SE;
            } else if (pressedKeyCodes.contains(Input.Keys.LEFT)) {
                return PlayerState.WALKING_SW;
            } else {
                return PlayerState.WALKING_S;
            }
        } else if (pressedKeyCodes.contains(Input.Keys.RIGHT)) {
            return PlayerState.WALKING_E;
        } else if (pressedKeyCodes.contains(Input.Keys.LEFT)) {
            return PlayerState.WALKING_W;
        } else {
            return null;
        }
    }
```

**Step 2: Translate the direction the `Player` is facing into velocity**

I use a `Vector2` to help me determine which direction on a xy-axis the `Player` is facing. If it's headed north, we'll increase the y-axis by setting `DirectionVector = Vector2(0, 1);`. But if it's headed south, we'll have to go the opposite direction with `DirectionVector = Vector2(0, -1);`.

```java
//PlayerState.java
public enum PlayerState {
    STANDING_S, STANDING_N, STANDING_E, STANDING_W,
    WALKING_S, WALKING_N, WALKING_E, WALKING_W, WALKING_NE, WALKING_SE, WALKING_SW, WALKING_NW,
    SITTING_W,
    PICKUP_S, PICKUP_N, PICKUP_E, PICKUP_W,
    HOLD_S,
    ;

    private static final float ONE_ON_ROOT_TWO = (float) (1.0 / Math.sqrt(2));

    public Vector2 calculateDirectionVector() {
        return switch (this) {
            case WALKING_N, STANDING_N -> new Vector2(0, 1);
            case WALKING_S, STANDING_S -> new Vector2(0, -1);
            case WALKING_E, STANDING_E -> new Vector2(1, 0);
            case WALKING_W, STANDING_W -> new Vector2(-1, 0);
            case WALKING_NE -> new Vector2(ONE_ON_ROOT_TWO, ONE_ON_ROOT_TWO);
            case WALKING_NW -> new Vector2(-ONE_ON_ROOT_TWO, ONE_ON_ROOT_TWO);
            case WALKING_SE -> new Vector2(ONE_ON_ROOT_TWO, -ONE_ON_ROOT_TWO);
            case WALKING_SW -> new Vector2(-ONE_ON_ROOT_TWO, -ONE_ON_ROOT_TWO);
            case SITTING_W -> new Vector2(0, 0);
            case PICKUP_S, PICKUP_E, PICKUP_W, PICKUP_N -> new Vector2(0, 0);
            case HOLD_S -> new Vector2(0, 0);
        };
    }
```

**Step 3: Update velocity in `Player`**

Now we've determined the direction our `Player` should be heading, it's time to get moving!

Back in `Player` class, we use the newly calculated velocity and set it as our `currentVelocity`. And by applying it to the Box2d `Body`, the `Player` has started moving in the direction of our choice.

```java
//Player.java
public class Player extends Actor {
    private Vector2 currentVelocity = new Vector2(0, 0);
   [...]

    void setStateAndVelocity(PlayerState newState, Vector2 newVelocity) {
        // Update our velocity and enact it on our physical body.
        currentVelocity = newVelocity;
        physicalBody.setLinearVelocity(currentVelocity);

        // Update our state and let the sprite animator know of our new state.
        [...]

        // Trigger hooks upon a state change.
        [...]
    }

```

Remember the question I asked earlier when we looked at `trackMovement()`? Let's turn it around. 
What happens to `Player`, while `currentVelocity != Vector2(0, 0);`?
```java
    private void trackMovement(float delta) {
        float movement = delta * speed;
        physicalBody.setLinearVelocity(currentVelocity.cpy().scl(movement));
        this.setPosition(physicalBody.getPosition().x - Resources.WORLD_TILE_SIZE / 2f, physicalBody.getPosition().y - Resources.WORLD_TILE_SIZE / 2f);
    }
```
... Right! Our character starts going places!

![Magika, going places](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ftunn91orjiopphnawlu.gif)

#### @Override `keyUp()`

When a key gets released, our `Player` can take a break from moving in that direction. We remove the key from the `pressedKeyCodes` and recalculate the `Player`'s state and velocity, just like we did when the key was first pressed.

```java
//FreeRoamingMovementListener.java
    @Override
    public boolean keyUp(InputEvent event, int keycode) {
        pressedKeyCodes.remove(keycode);

        // Step 1: determine PlayerState based on remaining pressed keycodes (see description for Step 1 under `keyDown`)
        PlayerState state = getPlayerStateBasedOnCurrentlyPressedKeys();

        // Step 2: Set newVelocity back to `Vector2(0, 0)` and override it once more, if applicable (see description of Step 2 under `keyDown`)
        Vector2 newVelocity = Vector2.Zero;
        if (state != null) {
            newVelocity = state.calculateDirectionVector();
        }

        // Step 3: Update the player's state (see description of Step 3 under `keyDown`)
        updatePlayerState(state, newVelocity);

        return true;
    }
```

And that's it!

----------------------------------------------------------

# Concluding thoughts... get on your way!

>"You tried to escape math while at school.
>You copied, and you scraped, and you hid like a fool.
>Well, enduring soul, as you shall see soon,
>What once was a game now turned into
>a living nightmare because Vector Math has become as inescapable as death and taxes.
>
>... and that's on you."
> *- by yours truly*

Let's put it all together!

```java
//Player.java
public class Player extends Actor { 
    public float speed = 5 * Resources.TILE_SIZE;
    private Vector2 currentVelocity = new Vector2(0, 0);

    public Player(Vector2 startPosition) {
        super();
        sprite = [sprite retrieving logic];

        // Set the actor's x and y coordinates, as well as width and height.
        setBounds(startPosition.x, startPosition.y, sprite.getWidth(), sprite.getHeight());

        // Create the Box2D body for the Player
        physicalBody = [libgdx Body creation logic]

        // connect Actor with Box2D body
        physicalBody.setUserData(this); 

        addListener(FreeRoamingMovementListener(this));
     }

    @Override
    public void act(float delta) {
        super.act(delta);

        trackMovement(delta);
    }

    private void trackMovement(float delta) {
        // calculate how far the body has moved by multiplying the constant speed by how much time has elapsed 
        float movement = delta * speed;

        // move the physical body by multiplying current velocity with the movement
        physicalBody.setLinearVelocity(currentVelocity.cpy().scl(movement));

        // move sprite by moving actor to the same coordinates as the physical body
        this.setPosition(physicalBody.getPosition().x - Resources.WORLD_TILE_SIZE / 2f, physicalBody.getPosition().y - Resources.WORLD_TILE_SIZE / 2f);
    }

    void setStateAndVelocity(PlayerState newState, Vector2 newVelocity) {
        // Update our velocity and enact it on our physical body.
        currentVelocity = newVelocity;
        physicalBody.setLinearVelocity(currentVelocity);

        // Update our state and let the sprite animator know of our new state.
        [...]

        // Trigger hooks upon a state change.
        [...]
    }
}
```


```java
//FreeRoamingMovementListener.java
class FreeRoamingMovementListener extends InputListener {
    private final Player player;
    private final Set<Integer> pressedKeyCodes = new HashSet<>();

    public FreeRoamingMovementListener(Player player) {
        this.player = player;
    }

    @Override
    public boolean keyDown(InputEvent event, int keycode) {
        pressedKeyCodes.add(keycode);

        // Step 1: Determining the state.
        PlayerState state = getPlayerStateBasedOnCurrentlyPressedKeys();
        if (state == null) {
            pressedKeyCodes.remove(keycode);
            return false;
        }

        // Step 2: Translate the direction the player is facing into velocity.
        Vector2 newVelocity = state.calculateDirectionVector();

        // Step 3: Update velocity & state in `Player`.
        player.setStateAndVelocity(state, newVelocity);

        return true;
    }

    @Override
    public boolean keyUp(InputEvent event, int keycode) {
        pressedKeyCodes.remove(keycode);

        // Step 1: Determining the state.
        PlayerState state = getPlayerStateBasedOnCurrentlyPressedKeys();

        // Step 2: Translate the state of player into velocity or default to Vector2.Zero.
        Vector2 newVelocity = Vector2.Zero;
        if (state != null) {
            newVelocity = state.calculateDirectionVector();
        }

        // Step 3: Update velocity & state in `Player`.
        updatePlayerState(state, newVelocity);

        return true;
    }

    private PlayerState getPlayerStateBasedOnCurrentlyPressedKeys() {
        // get sprite based on walking direction
        if (pressedKeyCodes.contains(Input.Keys.UP)) {
            if (pressedKeyCodes.contains(Input.Keys.RIGHT)) {
                return PlayerState.WALKING_NE;
            } else if (pressedKeyCodes.contains(Input.Keys.LEFT)) {
                return PlayerState.WALKING_NW;
            } else {
                return PlayerState.WALKING_N;
            }
        } else if (pressedKeyCodes.contains(Input.Keys.DOWN)) {
            if (pressedKeyCodes.contains(Input.Keys.RIGHT)) {
                return PlayerState.WALKING_SE;
            } else if (pressedKeyCodes.contains(Input.Keys.LEFT)) {
                return PlayerState.WALKING_SW;
            } else {
                return PlayerState.WALKING_S;
            }
        } else if (pressedKeyCodes.contains(Input.Keys.RIGHT)) {
            return PlayerState.WALKING_E;
        } else if (pressedKeyCodes.contains(Input.Keys.LEFT)) {
            return PlayerState.WALKING_W;
        } else {
            return null;
        }
    }
}
```

```java
//PlayerState.java
public enum PlayerState {
    STANDING_S, STANDING_N, STANDING_E, STANDING_W,
    WALKING_S, WALKING_N, WALKING_E, WALKING_W, WALKING_NE, WALKING_SE, WALKING_SW, WALKING_NW,
    SITTING_W,
    PICKUP_S, PICKUP_N, PICKUP_E, PICKUP_W,
    HOLD_S,
    ;

    private static final float ONE_ON_ROOT_TWO = (float) (1.0 / Math.sqrt(2));

    public Vector2 calculateDirectionVector() {
        return switch (this) {
            case WALKING_N, STANDING_N -> new Vector2(0, 1);
            case WALKING_S, STANDING_S -> new Vector2(0, -1);
            case WALKING_E, STANDING_E -> new Vector2(1, 0);
            case WALKING_W, STANDING_W -> new Vector2(-1, 0);
            case WALKING_NE -> new Vector2(ONE_ON_ROOT_TWO, ONE_ON_ROOT_TWO);
            case WALKING_NW -> new Vector2(-ONE_ON_ROOT_TWO, ONE_ON_ROOT_TWO);
            case WALKING_SE -> new Vector2(ONE_ON_ROOT_TWO, -ONE_ON_ROOT_TWO);
            case WALKING_SW -> new Vector2(-ONE_ON_ROOT_TWO, -ONE_ON_ROOT_TWO);
            case SITTING_W -> new Vector2(0, 0);
            case PICKUP_S, PICKUP_E, PICKUP_W, PICKUP_N -> new Vector2(0, 0);
            case HOLD_S -> new Vector2(0, 0);
        };
    }
```

I really hope this blog post helps you along your journey. 

Let me know if you have any questions, suggestions, ideas or want to just show me how your character moves around! I'm always keen to get in touch with other (game) devs!

----------------------------------------------------------
# For completeness sake
## - aka some more code snippets -

In this post I sprinkled some pseudo-code in `[ ]` brackets to keep the code focussed. That's why below, I added the actual code called by my pseudo-brackets. I hope this proves useful and am happy to answer questions.

### Get Actor's Sprite
```java 
    //Resources.java
    private Resources() {
        gameSprites = new TextureAtlas(Gdx.files.internal("packed/game.atlas"));
    }

    public Sprite createSpriteInTileSizeUnits(String spriteFileName) {
        Sprite sprite = new Sprite(gameSprites.createSprite(spriteFileName));
        resizeSpriteSizeToTileUnits(sprite);
        return sprite;
    }

    private void resizeSpriteSizeToTileUnits(Sprite s) {
        s.setSize(spriteUnitsToTileUnits(s.getWidth()), spriteUnitsToTileUnits(s.getHeight()));
    }

    public static float spriteUnitsToTileUnits(float val) {
        return val / TILE_SIZE;
    }
``` 


### Create a Box2D body
```java
    //TiledObjectsUtil.java
    public static Body createBox(World world, float x, float y, float width, float height, boolean isStatic) {
        BodyDef def = new BodyDef();  // a description of all the physical aspects of the body (e.g. solid vs. dynamic, etc.)
        if (isStatic) {
            def.type = BodyDef.BodyType.StaticBody;
        } else {
            def.type = BodyDef.BodyType.DynamicBody;  // non-zero velocity
        }
        def.position.set(x, y);
        def.fixedRotation = true;  // true --> will not be rotating when impacted by other bodies
        Body pBody = world.createBody(def);  // initialises body, puts it into world with def physical properties.

        // give a shape to pBody enttiy
        PolygonShape shape = new PolygonShape();
        shape.setAsBox(width/2f, height/2f);  // expands out from the "center", meaning that it's actually double that for width and height. So if  you set only 32, it'll be 64 wide
        pBody.createFixture(shape, 1f);
        shape.dispose(); // keep it clean!

        return pBody;
    }
```

>"Congratulations!
>Today is your day.
>You're off to Great Places!
>You're off and away!"
> *- Dr. Seuss*


Happy coding!


![a day in the life of Magika](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bcc5dc2squmk0z5wg7oh.gif)

-------------------------------

*Mirjam is a self-taught software developer and passionate board gamer. She works as a backend developer at [Digio](https://digio.com.au/), an Australian software consultancy.* 

*You can reach her on [LinkedIn](https://www.linkedin.com/in/mirjam-uher/?locale=en_US) if you wanna chat about anything and everything related to tech, game dev, boardgames or the latest reality TV show you binged.*