Developing Multiplatform Game With Libgdx, Part 5: Adding Logic and Basic Player Controls

Getting to game logic and basic controls.

Great. In our previous lesson we’ve made the walkable indicators where player can step in. We’ve also displayed the player sprite. First thing’s first: we remove it now. The teaser is over, boys. We’re back into hardcore programming reality, a no-man’s land of writing game logic.

No, seriously, remove the following line from GameScreen:

batch.draw(game.res.player,
        sizeEvaluator.getBaseScreenX(1),
        sizeEvaluator.getBaseScreenY(1) + 2);

Thanks.

Whenever we develop an application, it’s always a good idea to split logic from graphics. Even if it is much easier to go all-in in one GameScreen class, we don’t want that. The mix of code will make project harder to maintain, but it’s not the only reason. Imagine that you want to pause the game. The game should draw everything properly, only the game objects on the scene stay paused. If you intermix the code, you’re in for lots of extra conditions and workarounds. The rendering part simply should not care about the logics.

Creation of GameLogic and Player classes

Let’s create a separate package called “logic”. (in my case it is com.coldwild.dodginghero.logic). In this package, create a new class, call it “GameLogic”

Make constructor public. Forget about it for now. Inside our logic package, create another package, called “objects” (in my case it is com.coldwild.dodginghero.logic.objects)

Inside our “objects” package, create a new class, call it “Player”, make it inherit from Sprite class. (public class Player extends Sprite). Declare two variables, fieldX and fieldY, those will indicate. Make a public constructor, that takes two integers, named  fx and fy. Assign them to fieldX and fieldY respectively. This is a field position of your player. Also, introduce getters and setters for fieldX and fieldY values. Final look of player class:

public class Player extends Sprite {
    private int fieldX;
    private int fieldY;

    public Player(int fx, int fy)
    {
        fieldX = fx;
        fieldY = fy;
    }

    public int getFieldX() {
        return fieldX;
    }

    public void setFieldX(int fx) {
        fieldX = fx;
    }

    public int getFieldY() {
        return fieldY;
    }

    public void setFieldY(int fy) {
        fieldY = fy;
    }
}

Now, go to move our constants from GameScreen class

private static final int MAX_BASE_X = 3;
private static final int MAX_BASE_Y = 3;

to GameLogic class and make them public (that you’ll still be able to access from GameScreen). Change all existing references to those constants by adding GameLogic. In front of them (For example, GameLogic.MAX_BASE_X).

Run the game and make sure everything still works (only you don’t have your hero, because you’ve removed the drawing of his sprite).

Starting to incorporate GameLogic into our GameScreen

Declare GameLogic type variable in our GameScreen class (GameLogic logic;). Initialize it at the end of the GameScreen constructor. Go back to GameLogic and declare Player variable there (Player player), make sure to import it by pressing Alt+Enter. Now here’s where the fun part starts: we’re going to give our player random coordinates at the game start. Inside the GameLogic constructor, initialize our player. player = new Player();

player = new Player(
        MathUtils.random(MAX_BASE_X),
        MathUtils.random(MAX_BASE_Y)
);

Even though the GameLogic is going to make most Player changes, we still need to give access to our GameScreen class, at least for drawing purposes. In our GameLogic class, make a function that returns pointer to the player created:

public Player getPlayer()
{
    return player;
}

Here’s how GameLogic class looks at this point:

public class GameLogic {

    public static final int MAX_BASE_X = 3;
    public static final int MAX_BASE_Y = 3;

    Player player;

    public GameLogic()
    {
        player = new Player(
                MathUtils.random(MAX_BASE_X),
                MathUtils.random(MAX_BASE_Y)
        );
    }

    public Player getPlayer()
    {
        return player;
    }

}

Cool. Now we’ll be able to properly draw it.

Go back to GameScreen. Declare Player variable there (Player player;) assign value to it after our logic has been initialized:

logic = new GameLogic();
player = logic.getPlayer();

Since Player class extends Libgdx class “Sprite”, we’ll be able to draw it quite easily. All we need to do is to assign coordinates and the actual picture that is going to be drawn.

At first, we’ll need to do a small change to Resources class. Change the type of public TextureRegion player; to Sprite (public Sprite player;) Import the necessary class from libgdx (press alt+enter) and then go to Resources constructor and replace player initialization to:

player = new Sprite(gameSprites.findRegion("player"));

Now go back to GameScreen. After our getPlayer function call, add following commands. The next one assigns the picture to player sprite:

player.set(game.res.player);

And then add the next one:

player.setPosition(
        sizeEvaluator.getBaseScreenX(player.getFieldX()),
        sizeEvaluator.getBaseScreenY(player.getFieldY()));

This one was easy: just set the coordinates of our player sprite to actually match the base where the player is standing. Do not get attached to above part of code. It is not a good idea to assign inner class things in the “outside” code. We will move the sprite image and coordinate setting into Player constructor in next lessons, but right now I want to make it easier to understand.

Looks fine, now let’s actually add the drawing call into our GameScreen render() function. After drawBases() call, write: three lines:

batch.begin();
player.draw(batch);
batch.end();

Our familiar batch begind/end calls and an actual call to draw a player. Run the game a few times. You’ll see that player is located on a new position every time. (Which is preeetty cool). But let’s not dwell on our successes! Instead, we should move forward towards new victories. Let’s make that player move! Make our GameScreen implement InputProcessor:

public class GameScreen extends DefaultScreen implements InputProcessor {

Android Studio is going to tell you that some methods are missing. Press Alt-Enter and choose to add the missing ones automatically. The one you’re going to need is KeyDown. But first, we need to tell our game to accept our GameScreen input. To do this, go to GameScreen constructor and add the following line, right at the end:

Gdx.input.setInputProcessor(this);

In our GameScreen’s dispose method, we need to manually say that the game should abandon GameScreen class as input processor. Add the line

Gdx.input.setInputProcessor(null);

at the end of dispose() method.

Now we’ll be able to track key/mousepresses. Our Keydown function (at least on desktop) will be used to issue movement commands to the player. Sure, most simple thing to do now would be to simply set player’s coordinates on keydown (especially since we have the means). But, remember, logic is split from rendering? What if we are going to have animations between moves (meaning that plainly setting coordinates won’t work and we’ll need to play some animations before player moves)?

We’re still going to implement movement in a simplified way that will need refactoring later, but it’s better if we just adjust the players logical field coordinates inside the screen. Time to add functionality to our GameLogic class. We’re going to add public boolean CheckMove(int fx, int fy) function, which will tell us if player can move to the tile or not. The function should check whether new coordinates are legit. For now it’s going to be really simple: return false if coordinates are out of bounds or true otherwise. In the future, we might extend it.

In the end, our function looks like this:

public boolean CheckMove(int fx, int fy)
{
    return (fx >= 0 &&
            fx <= MAX_BASE_X && fy >= 0 &&
            fy <= MAX_BASE_Y);
}

Since each of our comparisons result in a Boolean, we can simply return comparison result instead of using an if/else logical operator.

The next function will actually save the player coordinates. Let’s name it AssignPlayerPosition, it will accept the new coordinates of a player.

public void AssignPlayerPosition(int fx, int fy)
{
    player.setFieldX(fx);
    player.setFieldY(fy);
}

 

Now let’s go back to our good old GameScreen. In our KeyDown function, let’s add some movehandling code. If we think about it, it does not matter where we move: left,right,bottom,top: the only thing that differs is a coordinate. Therefore, let’s try to move away the move attempts to a separate function. Let’s make a function called AttemptMove, to which we’ll pass the change of coordinates and it will do the rest.

public void AttemptMove(int dx, int dy)
{
    if (logic.CheckMove(player.getFieldX() + dx, player.getFieldY() + dy))
    {
        logic.AssignPlayerPosition(player.getFieldX() + dx, player.getFieldY() + dy);
        player.setPosition(
                sizeEvaluator.getBaseScreenX(player.getFieldX()),
                sizeEvaluator.getBaseScreenY(player.getFieldY()));
    }
}

First, we’re checking whether the new coordinates are legit. If they are, we’re setting a new position of the player on the base. To finalize it, we’re adjusting the position of the player on the screen.

Now we only need to assign proper function calls from our KeyDown function.

@Override
public boolean keyDown(int keycode) {
    switch (keycode)
    {
        case Input.Keys.RIGHT:
            AttemptMove(1, 0);
            break;
        case Input.Keys.LEFT:
            AttemptMove(-1, 0);
            break;
        case Input.Keys.UP:
            AttemptMove(0, 1);
            break;
        case Input.Keys.DOWN:
            AttemptMove(0, -1);
            break;
        default:
            break;
    }

    return false;
}

Each direction key moves our character relative to the field coordinates. So if we press the Right key, the character moves by one tile to the right horizontally (dx = 1), and zero tiles vertically (dy = 0). Similar things happen with other directional keys. Run the game and try pressing the keys. Our hero is moving! It could be the end of tutorial, but try resizing our window: the hero is in a bad place (literally). To change this, we need to refresh player’s screen coordinates after every resize. In our gamescreen, find both instances of player.setPosition calls and move them into separate function RefreshPlayer():

public void RefreshPlayer()
{
    player.setPosition(sizeEvaluator.getBaseScreenX(player.getFieldX()),
            sizeEvaluator.getBaseScreenY(player.getFieldY()));
}

Add RefreshPlayer() call to our resize() function (and make sure to replace the existing setPosition calls in GameScreen constructor and AttemptMove to RefreshPlayer() call). Run the program and try resizing the window! Everything should be working now.

That concludes our hero movement tutorial. Next time we’re start implementing the enemy and think of the way to display/handle his attacks! Stay tuned! Vladimir is out.

Relevant commit: https://github.com/vladimirslav/dodginghero/commit/d02228f6ecb39fdbf98bb4ad54e57b5fb8f29b73

Leave a Reply

Your email address will not be published. Required fields are marked *