Moving a character around the map 4 – Camera, Viewport


Code for this tutorial is on this git branch:

git clone -b First_signs_of_movement https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


Viewport

A viewport is Libgdx’s way of managing your camera’s width and height. Although we will not be resizing our viewport during the game, it means that you do not need to worry about different sizes of screens.

We are using a specific Libgdx viewport – the StretchViewport.

import com.badlogic.gdx.utils.viewport.StretchViewport;

From the documentation:

A ScalingViewport that uses Scaling.stretch so it does not keep the aspect ratio, the world is scaled to take the whole screen

When you run the game on your computer, you can resize the window and see how everything updates to the correct size. This is the viewport managing the camera/graphics for you.

large small

You can see from these two (by the size of the title text) that one is zoomed and one is not, but the viewport has dealt with the resizing fine.

ZombieGame extends MyGame

When we launch the game, the ZombieGame class creates our viewport:

@Override
protected void initViewport() {
   // Total view width
   float viewWidth =32;
   float viewHeight = Math.round(Gdx.graphics.getHeight()/(Gdx.graphics.getWidth()/viewWidth));

   // World width, in squares. Always 100 wide.
   worldWidth = 32 * 3;
   worldHeight = (int) Math.floor(viewHeight);
   
   setViewport(new StretchViewport(viewWidth,viewHeight));
}

initViewport() is an abstract method, so you have no choice but to implement it.

Our first two lines are to set the width and height of our viewport.

float viewWidth =32;
float viewHeight = Math.round(Gdx.graphics.getHeight()/(Gdx.graphics.getWidth()/viewWidth));

The extra logic there is to maintain the ratio we want.

The next two lines are to set the size of the actual world that the player walks around in.

worldWidth = 32 * 3;
worldHeight = (int) Math.floor(viewHeight);

That *3 means that the world is going to be three times larger than the size of the viewport. If you want to prove that to yourself – set the worldWidth to just be 32, and you will see that it is the same size as the viewport.

Try fiddling with these variables and see the effects it creates. The wider your view, the further out you are zoomed.

Finally, all that’s left to do is create and set the viewport so the game knows about it.

setViewport(new StretchViewport(viewWidth,viewHeight));

What about the camera?

The StretchViewport creates its own instance of a camera – an OrthographicCamera.

An OrthographicCamera is one which projects 3D images into 2D. This image from Wikipedia should hopefully show you what is meant by that:

By Yuri Raysper - Has drawn from the bookThis vector image was created with Inkscape., Public Domain, https://commons.wikimedia.org/w/index.php?curid=1484146
By Yuri Raysper – Has drawn from the book This vector image was created with Inkscape., Public Domain, https://commons.wikimedia.org/w/index.php?curid=1484146

So it is good for 2D games – in the case of 3D games (outside the scope of this tutorial series), it would not be used.

All we want this camera to do is follow the player, to always be looking down on him. Therefore, we need a reference to this camera and then to update it every time the player moves.

PlayerInputSystem

When we create our PlayerInputSystem, we create a reference to the camera:

public PlayerInputSystem(MainScreen screen) {
		this.screen = screen;
		camera = (OrthographicCamera) ZombieGame.getViewport().getCamera();
	}

Now, we can update it when we want.

Why do we keep our reference in the PlayerInputSystem? This is so we can directly access the camera when pressing buttons. For example:

@Override
public boolean keyDown(int keycode) {
   if (keycode == Keys.R) {
      screen.gameController.createMap();
      screen.gameController.setRunning(true);
   }
   else if (keycode == Keys.Q) {
      screen.gameController.endGame();
   }
   
   float moveAmount = 1.0f;
   
   if (keycode == Keys.UP) {
      camera.position.y += moveAmount;
   } else if (keycode == Keys.DOWN) {
      camera.position.y -= moveAmount;
   } else if (keycode == Keys.LEFT) {
      camera.position.x -= moveAmount;
   } else if (keycode == Keys.RIGHT) {
      camera.position.x += moveAmount;
   } else if (keycode == Keys.Z) {
      camera.zoom += moveAmount;
   } else if (keycode == Keys.X) {
      camera.zoom -= moveAmount;
   }
   
   updateCamera();
   
   return false;
}

You may recognise this as code we were playing with in the first round of tutorials. This is why we have the camera here – however, you may choose to move it. It slightly irritates me having the camera here, as the PlayerInputSystem class should deal with input and nothing else. It may move in a future tutorial.


Aside: You may have also noticed that this code doesn’t work any more. I can’t zoom! I can’t navigate left and right! Is it broken? Actually, every time the game updates now, it centres the camera above the player. So this code runs, but you never see the effects of it. To get it working again, comment out everything in the updateCamera() method. Though this will, of course, stop the camera from centering over the player.


UpdateCamera() is where the action happens:

public void updateCamera() {
   if (camera == null || camera.position == null || getPlayer() == null) {
      return;
   }

   camera.position.x = getPlayer().getX();
   camera.position.y = getPlayer().getY();

}

We have a bit of code safety first, just to make sure we aren’t going to null-pointer before we’ve set up all our objects.

Then we simply set the camera’s location to be that of the player. We don’t need to worry about translating this into the correct on-screen co-ordinate, because the viewport deals with that for us.

Finally

That is pretty much it for now! The final thing I wanted to highlight was the viewport’s project/unproject functionality, which makes converting from screen co-ordinates to map co-ordinates very easy. I already wrote about this in the previous tutorial – go back to it if you missed it – but I wanted to highlight that this is another positive for the viewport.



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.

Moving a character around the map 3 – Input Processing


Code for this tutorial is on this git branch:

git clone -b First_signs_of_movement https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


Next we turn to the PlayerInputSystem, our class which implements Libgdx’s InputProcessor.

When we touch the screen, we want the following to happen:

  • Detect the co-ordinates of the screen that we touched/clicked
  • Convert these to map co-ordinates
  • Work out which tile we have touched based on these map co-ordinates
  • Implement some basic logic to stop you touching off-map

ZombieGamePlayerOnScreen

Most of this logic is built-in to Libgdx. When you touch/click the screen, the method touchDown() is called. Let’s take a look at our implementation:

@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {

   if (!screen.gameController.isRunning()) {
      return false;
   }

   currentTouch.x = screenX;
   currentTouch.y = screenY;
   
   convertScreenToWorldCoordinates(currentTouch);

   int x = (int) currentTouch.x;
   int y = (int) currentTouch.y;

   Pair selectedTile = screen.gameController.getWorld().getMap()[x][y];
   /*
    *
    * Pretty much all commands are done here...
    *
    */

   getPlayer().setDestination(selectedTile);

   return false;

}

First up, we check if the game is running. If not, we do nothing. What does this mean? Basically we just want to make sure input is not processed before we’re ready for it. We try to access elements of our map – what if we haven’t created it yet? This is just a precaution for that.

Next, we create a vector and pass it off to be converted:

convertScreenToWorldCoordinates(currentTouch);

Let’s take a look at that method:

private Vector3 convertScreenToWorldCoordinates(Vector3 touch) {
   ZombieGame.getViewport().unproject(touch);
   
   if (touch.x < 0 ){
      touch.x = 0;
   } else if (touch.x > screen.gameController.getWorld().getWidth()) {
      touch.x = screen.gameController.getWorld().getWidth();
   }

   if (touch.y < 0) {
      touch.y = 0;
   } else if (touch.y > screen.gameController.getWorld().getHeight()) {
      touch.y = screen.gameController.getWorld().getHeight();
   }

   return touch;
}

The core part of this is just the unproject() method call. This is built into Libgdx and according to the documentation: “Transforms the specified screen coordinate to world coordinates.”

Next, we make sure the co-ordinates not off-map. You may have noticed that you cannot make the player character walk off the map. In fact, you can never set its destination to be off-map. This is where we make sure of that.

Now we return to touchDown():

int x = (int) currentTouch.x;
int y = (int) currentTouch.y;

Pair selectedTile = screen.gameController.getWorld().getMap()[x][y];

We convert to ints so that we can directly access the tiles of the map, and we ask for the tile (Pair) corresponding to those co-ordinates.


Aside: you might want to round these floats first before casting them to ints. I didn’t bother – but keep in mind that casting from float to int isn’t quite the same as rounding – it doesn’t round to the nearest number, it just discards everything after the decimal point. If your game requires this precision, use Math.round() first.


Now we’ve got a Pair to work with. In Dinowar, we next have a bunch of logic depending on which Pair you’ve clicked. Did you select your a tile belonging to your own base? Did you select a tile belonging to an opponent’s base? Did you touch water? Depending on all these, different logic is applied.

Move dinosaurs around based on which tile was touched
Move dinosaurs around based on which tile was touched

For us now, all we want to do is tell the player where to go:

/*
 *
 * Pretty much all commands are done here...
 *
 */

getPlayer().setDestination(selectedTile);

In later tutorials, we’ll flesh out the logic here. What if you touch a zombie? Maybe we should fire a bullet in that situation, rather than walk towards it.

Now you’ve got an idea of how we’re selecting where to move around the map. Next up, we’ll take a look at the camera and viewport, to make sure that we always track the player’s movement.



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.

Moving a character around the map 2 – Movement and Orientation


Code for this tutorial is on this git branch:

git clone -b First_signs_of_movement https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


If you have got the project up and running, you should be looking at a character moving around the screen when you click.

ZombieGamePlayerOnScreen

If you can not get the project to run, post a message at the bottom and we’ll sort it out.

PlayerCharacter

The PlayerCharacter is where we have our logic which deals with orientation and movement. We also draw our sprite here – it draws itself to a SpriteBatch when requested.

Movement and orientation is all done with vector logic, using Libgdx’s built-in Vector2 class.

When we set up the PlayerCharacter, we give him a random start position. The code to pick a random spot is quite straightforward for now – anywhere on the map which is classed as land.

Every time the PlayerCharacter’s update() method is called, the player will see whether a destination has been set, see if we are already at the destination, and if not – move towards it and point at it.

Movement

When we click in the PlayerInputSystem, we pass a tile to the PlayerCharacter to set as the destination. We also do this:

diff = destination.asVector().cpy();
diff.sub(pos);

diff is of type Vector2. diff is now a Vector2 representing the vector between the destination and the player’s current position.

Note: we’ve added a method to our Pair class, to convert it to a Vector2.

Next, we update(). First we check that we actually have a destination (if you haven’t clicked yet, it will be null!). Then we check that we aren’t already standing at the destination (or close to it). If either of these things are the case, we do nothing.

Assuming we want to update our position, we do this:

diff.setLength(PLAYER_SPEED);
pos.add(diff);

diff is still a vector pointing in the same direction as before, but with a fixed length (whatever you set PLAYER_SPEED as – try playing with it to see the effect). Our position is also a vector, so we add this ‘diff’ vector to our current position, and it gives us a new vector which is an addition of the two.

Vector addition
A visual representation of adding some vectors. Think of A, B and C as the results of 3 different clicks

Orientation

Ok, we have updated our position. Now for the orientation. All we do is work out the angle between two vectors (our position and destination):

private void updateOrientation() {
   float angle = (float) Math.toDegrees(Math.atan2(destination.x - pos.x, destination.y - pos.y));
   this.orientation = - angle + 90f;
}

Why did we add that +90f at the end? It is just to correct for the player image looking the wrong way. If you just updated the actual image to be pointing another 90 degrees round, you would get the same effect. I probably could have done that in the time in took me to write this explanation…

That’s it! You’ve updated the orientation and position variables. Now, when we are told to draw the player, we have all the information necessary.

public void draw(GameController game, SpriteBatch batch) {
   playerSprite.setCenter(pos.x, pos.y);
   playerSprite.setOrigin(1, 1);
   playerSprite.setRotation(orientation);
   playerSprite.draw(batch);
}

We’re walking on water!

Yes we are. This is the most basic implementation of movement I could put in for now; we don’t even check whether we can proceed without touching water. Once we’ve moved on to later tutorials, we’ll actually be doing path-finding each time we touch somewhere. We will abandon this approach – naively move in a straight line – and the player will walk around bodies of water!

If you fancy a challenge now, and to improve your understanding of how things tie together, why don’t you stop the player from being able to walk on water? It doesn’t require any path-finding.

We’re going to use path-finding instead. So is this logic useless to us?

No! A feature will we add in a few tutorials is to fire bullets at zombies. This logic will be used for that – sprites moving in a straight direction, able to move over land or water – perfect for bullets.

Conclusion

We’ve taken a look at vector logic for moving in straight directions, and orienting sprites to ‘look at’ what we want them to look at.

Next up:

  • Determining which tile we have just touched
  • Having a camera track the player


If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.

Moving a character around the map


Code for this tutorial is on this git branch:

git clone -b First_signs_of_movement https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


Up until now we have generated a random map, and got a camera pointing at it. Now, we move to something more interesting – moving a character around a map.

ZombieGamePlayerOnScreen2

This tutorial is the second time we will download a pre-existing project that I have added to git. It’s the same project that you checked out earlier (assuming you’ve followed along this tutorial), but a different branch.

With this code, you will be able to do the following:

  • Place a character on our map
  • Click somewhere else on the map and have the character travel towards where you clicked
  • Track the character’s movement with the camera

git clone -b First_signs_of_movement https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Check out the branch with the command above. We’re not going to go over every code change, but we’ll look at the core stuff.

The flow of logic

  • GameController creates a player character
  • PlayerInputSystem detects a TouchDown event (a click on a computer, a touch on a touchscreen). Determine what coordinate on the screen was touched. Tells the player to treat this coordinate as its destination
  • GameController update() method is called, in turn calls the update() method of PlayerCharacter
  • PlayerCharacter compares its current position, its destination, and updates its orientation and position
  • GameScreenRenderer draws the map (as it was doing before) and draws the player
  • Repeat!

Getting graphics

man

I’ve provided an image to use for our character, but I didn’t create it. I took it from here:

http://opengameart.org/content/animated-top-down-survivor-player

So all credit for the image goes to ‘rileygombart’, who has made his/her graphics available. These kind of websites are a great source of graphics if you are artistically challenged (as we are – graphics for Dinowar were done by a friend).

I’ve added this graphic to the project and called it ‘man’.

Remember the tutorial on Texture Packing? Well, we will need to rebuild out atlases to include this. Then later we just ask for ‘man’ from the TextureAtlas. If you’re running the project as it is, this should happen automatically when you run the code.

ZombieGamePlayerOnScreen

Hopefully you are seeing something like this when you run the code! If not, do get in touch.

Try clicking around to see how the player sprite behaves. Next up, we’ll go into more detail about the new code.

 



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.