Cinematic Camera

I Want

In the process of my game, I started to think about an advanced camera system. I wanted to define camera bounding boxes in which a certain type of camera is active. But this was not enough for me as I wanted them stacked. And I wanted to attach these camera bounding boxes as well on moving targets like enemies.

Why?

Well, I was thinking of a cool intro where the hero falls down a deep hole. I wanted to zoom close in the beginning and zoom out in the middle part and zoom in on the landing. Furthermore, I wanted to overlay camera bounding boxes to zoom in on certain objects or when I enter or leave a room or level. I was as well thinking of a centered camera for a squared room to see everything in it and as soon I leave this room I wanted my follow camera back. So it’s complicated. And then I thought OK would be cool to have a zoom-in if enemies approach me to give that more focus and this camera bounding box has to follow the enemy… of course.

So it’s all about the cinematic effects I guess.

How?

I think the best is a visualization of my idea.

I tried to describe it in the level itself. The long light blue bounding box across the full room contains a follow camera and is on the bottom of the stack that’s why it’s numbered with a ‘0’. The middle camera bounding box is darker blue and is on top of the following camera and contains a centered camera, which means inside this box the camera will not follow the character but point to the center of the bound box. This box has the priority ‘1’ and is on top of the priority ‘0’. On top of this, we have a yellow bounding box with a zoomed-in centered camera with the priority ‘2’. The higher the number of the bounding box camera the higher its priority. On the right, we see a yellow bounding box with priority ‘1’ which is higher than ‘0’ with a follow camera with a different zoom.

In Action

Now in action

This is pretty neat isn’t it?

Behind the Scenes

OK, I try to explain it with some pseudo-code. Let’s assume we have all the camera bounding boxes and their camera type. Furthermore, we can load these camera types on the fly.

public void updateCamera(float tpf, Vector2 playerPosition) {
    stream()
            .filter(cam -> cam.getAabb().contains(playerPosition))
            .sorted(Comparator.comparingInt(CameraAabbData::getOrder).reversed())
            .findFirst()
            .filter(cam -> cam.getMyCamera() != currentCam)
            .ifPresent(cam -> {
                LOG.debug("Enter camera area with cam data type: " + cam.getType() + " properties: " + cam.getProperties());
                if (cam.getMyCamera() == null) {
                    cam.setMyCamera((MyCamera) feather.instance(cam.getType()));
                }
                currentCam = cam.getMyCamera();
                currentCam.setAabb(cam.getAabb());
                currentCam.setProperties(cam.getProperties());
            });
    currentCam.update(tpf);
}

So in short

  • We check if the player is in the camera AABB
  • We sort the camera AABB due to its order
  • We just take the first, which means the top one (it’s ordered)
  • The next check is to avoid that we set the camera properties on every frame
    • And if there is a camera AABB we check if we already instantiated the camera (caching)
    • We set the current camera and its properties
  • Last but not least we update the current camera on every frame
    • A lerp function smooths the switch from one zoom to another
    • A follow camera for example updates its position depending on the character’s movement
    • Update other dynamic features of the current camera

The object “cam” is just a data holder and often referred as a DTO.