Dynamic Third-Person Camera

View it on GitHub.png

The goal of my thesis is to architect a camera system which delivers smart camera movement that's aesthetically pleasing, targeted at third-person games.

This project is a work in progress. But, here I'll talk about my current progress, ongoing work, and future plans.

Note: pardon the jitteriness of videos, it is because of the recording software. I'm working on smoother video recordings.

( Video showcasing the follow camera, with proportional controller and collision avoidance )

As you can see in the video, the third-person camera follows the player (BB8), trying to aid movement by showing what's ahead. When the player jumps, the camera acts differently, lagging behind to exaggerate the change in verticality. The camera also tries to avoid collisions with the environment.

System Architecture

Before I dive into the architecture of my camera system, let me talk about two fundamental concepts that form the basis of communication between its components: Camera Context and Camera State.

CameraSystem ClassDiagram-I.PNG
( Class Diagram of Camera Context and Camera State )

As the name suggests, the Camera Context has all the contextual information about the camera which can be essential to the camera system,

  • Anchor game object: a pointer to the player whom the camera is following

  • Raycast callback: a pointer to the raycast method, implemented on the game side

  • Camera collision radius: the radius of the camera's collider

  • Sphere collision callback: pointer to the collision check method, implemented on the game side

  • Camera state last frame: camera's state on the previous frame

Camera State contains essentially every property of the camera which can be tweaked by the camera system. The system can smoothly interpolate between two camera states if needed, too.

The Camera Manager is responsible for updating a Camera Behavior, executing a set of Camera Constraints on it and finally moving the actual camera with a Camera Motion Controller.

CameraManager UpdateCode.PNG
( Camera Manager's update method )

A Camera Behavior returns an updated camera state. The behavior can be highly customized as well as coded by a designer. Here are some behaviors I programmed:

  • Degrees of freedom: An abstract-behavior which can tweak polar coordinates, local offsets and FOV of the camera.

  • Follow: based on user input, it changes the polar coordinates of the camera to ensure it follows the player.

  • Free-look: it enables a spectator to fly around easily. My debug camera uses this behavior.

( Showcasing degrees of freedom camera behavior )

Camera Constraints are a set of rules which can be applied on a camera state, returned by the active camera behavior. If the current state does not satisfy a given constraint, the constraint will update the state such that it is acceptable. All constraints are applied according to the painter's algorithm: from lowest to highest priority.

Each camera behavior has tags indicating names of the constraints it has to satisfy. As an example, the free-look behavior doesn't need to satisfy any constraints, but the follow behavior has to satisfy all of the constraints given below.

You can see all three constraints working together in the video below,

( Showcasing camera constraints getting applied on follow behavior )

After all the constraints are executed, we end up with a camera state which is considered to be the goal state. Here the Camera Motion Controller comes in the picture, which is in charge of moving the camera to this goal state. A motion controller could be as simple as snapping the camera's position to the destination (which is the default motion controller provided by my system), but a designer can code any complex motion controllers as it fits the game.

An example of such a custom motion controller is the Proportional Controller, which treats horizontal and vertical motions of the camera differently. The camera looks ahead when the player is moving horizontally, but the camera lags behind when the player is moving vertically.

( Showcasing a motion controller: proportional controller )

Here is the code, if you would like to look at it:

CameraManager ProportionalController.PNG
( Proportional motion controller's move camera method )

Thus, the camera gets moved after going through all three components of the system: camera behavior, camera constraints, and camera motion controller. Here is the class diagram showing their relation to the camera manager:

CameraSystem ClassDiagram-II.PNG
( Class diagram for Camera Manager )

Modifying the Cone Raycast

This is a work in progress. It intends to improve collision avoidance in two ways.

First improvement: if you notice the debug view of collision avoidance with original cone raycast constraint (in the left image), the raycasts are fired towards the camera from the player at the same altitude, but at different rotations, roughly forming a shape of a two-dimensional cone. This means if there is an obstacle right above/below the camera, the system won't notice it. In such scenarios, the camera won't try to avoid that geometry until it starts colliding with it the next frame.

ConeRaycast FailCase.PNG
( Cone Raycast: not able to sense the roof right above the camera )
ConeRaycast (Modified).PNG
( Modified Cone Raycast: red lines indicate obstacle geometry )

In the modified version of the cone raycast, as displayed in the right image, the raycasts are not only fired at different rotations but also at different altitudes, relative to the camera's polar coordinates, forming a shape of a three-dimensional cone. This makes sensing the roof right above the camera more precise, as indicated by red debug lines.

The following video demos the camera's behavior in such cases.

( Modified Cone Raycast )

If we project the target point of each raycast on the near-plane of the camera, it forms concentric rings; which are illustrated in the image on right side. Each of these ray has a weight assigned to it - the highest weight is represented by red color and the lowest is white colored. When a ray intersects an object, the ray suggests a reduction in the radius polar coordinate. The resultant reduction is calculated as a weighted average of all suggested reductions, which is the amount by which the camera finally moves closer to the player.

One interesting thing I'm doing here is that these weights react based on the velocity of the camera. So if the camera is moving north-east relative to its current position, the higher weights will be shifted towards that direction. This approach lets the camera's motion influence the weighted average!

Raycast Weights.PNG
( Weights of modified cone raycast )

The blue-green line represents the velocity of the camera, projected on its near-plane, and the colored dots represents the weights of the raycasts.

If you want to take a look at the code for calculating reduction in the radius, it is right below:

ModifiedConeRaycast Execute.PNG
( Execute method of camera constraint, the Modified Cone Raycast )

The second improvement will be changing the camera's altitude and rotation, too, based on the raycasts which report contact with geometry. Previously, I was changing just the radius polar coordinate to avoid collisions. Pairing this technique with a camera behavior which rotates the camera toward the direction of the player's velocity will allow the camera to operate entirely on its own, without any user input; that's the goal. Of course, in doing so, it will have to make sure that the player still feels in control of the camera, whenever they wish to change its position.

This work is still in progress, but the video below provides a small glimpse into the expected behavior once the second improvement is implemented.

( Expected camera behavior after modifying the cone raycast )

Future Plans

  • Supporting an OBB (oriented bounding box): I want to provide an engine side implementation for Raycast and Sphere Collision callbacks which can work with any geometry, as long as its bounds are specified as OBB.

  • Efficient raycast: As of now all the raycasts use a step-and-sample approach to function. I want to implement a ray-plane equation approach instead, to provide much faster and accurate raycasts.

  • Complex testing scenes: Since the camera design is heavily driven by the kind of game being developed and its level layout, I want to work with scenes which use simple geometry, but provide complex test cases. I'm planning to implement one of the following,

  1. Replicate a section of a level from a Tomb Raider game.

  2. Write a loader for maps of Quake or any other accessible game.

  • Camera sequencer: A tool which enables developers to sequence properties of a camera to create cutscenes.


© 2019 by Neesarg Banglawala.

  • Email_white_bg_512x512
  • LinkedIn - White Circle
  • Twitter - White Circle
  • github_white_bg_600px