Making the camera follow the character


Code for this tutorial is on this git branch:

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

Have a look here for help setting it up


Keeping the camera centred on the player – intelligently

This tutorial can be used independently to the ongoing series, as this is probably a useful bit of code for a lot of top-down 2D games.

In previous tutorials, the camera has been following the player around the screen, always keeping the camera centred over the player. However, when the player moves towards the edge of the game world, the edge of the map and beyond is visible. This looks pretty awful.

What we want is for the camera to follow the player around, but never get to the edge of the map. So when they player gets within a certain distance of the edge of the map, the camera stops following.

Code

float x = getPlayer().getX();
float y = getPlayer().getY();
// We divide the viewPort sizes by 2, because the player
// is always in the middle of the screen - with half of the
// screen above it, half below, half to the left side of it,
// half to the right

// You can extract these out (as they are in the full project)
// These are always the same, so only need to be calculated once
float halfViewPortWidth = camera.viewportWidth / 2;
float halfViewPortHeight = camera.viewportHeight / 2;

if (x < halfViewPortWidth) {
    camera.position.x = halfViewPortWidth;
} else if (x > ZombieGame.getWorldWidth() - halfViewPortWidth) {
    camera.position.x = ZombieGame.getWorldWidth() - halfViewPortWidth;
} else {
    camera.position.x = x;
}

if (y < halfViewPortHeight) {
    camera.position.y = halfViewPortHeight;
} else if (y > ZombieGame.getWorldHeight() - halfViewPortHeight) {
    camera.position.y = ZombieGame.getWorldHeight() - halfViewPortHeight;
} else {
    camera.position.y = y;
}

When the player is in the middle of the map, the camera will track normally. However, if the player steps outside the artificial boundaries then the camera will stay in a position in which it can never see off the map.

In the above code, we check if the player is too far to the left of the map, or too far to the right. If so, we limit where the camera can be on the X-axis. Otherwise, we just set the camera’s X parameter to match that of the player.

Similarly, we check if the player is too far towards the top or bottom of the map, and do the same.

In this way, the camera can track along one axis but not the other, or both at the same time – which will happen when the player is in a corner.



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 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.