Daedalic Entertainment has been known for critically acclaimed adventure games like Deponia and The Pillars of the Earth for more than a decade. For quite a while, we have been exploring other genres as well, as in the third person adventure State of Mind or another third person action adventure prototype we are going to talk about.
For these games, Unreal Engine 4 was the obvious choice for us, as it empowers the whole team to ensure maximum quality of the final product, from programmer to artist to game designer.
In this post, we will discuss dynamic cameras in third-person action adventures. The goal is to design a camera that highlights key exploration areas of each level and facilitates close-quarters combat, while never interfering with player intent. The latter is extremely important, as bad camera movement, as perceived by the player, immediately leads to upset or even frustrated players.
Unreal Engine 4 already provides a very flexible built-in system for defining arbitrary camera modifiers, which may or may not apply relative view translations and rotations. This way, different gameplay elements can modify the camera without interfering with each other.
We have been expanding on this camera model to be able to:
- Modify the camera while the player is visiting specific areas of the level
- Automatically change camera distance and field of view depending on the current pitch angle
- Focus ability targets selected by the player
- Rotate in the current movement direction of the player character
- Change camera properties during combat
- Ensure that line of sight between camera and player character is never occluded by level obstacles
- Always let the player override the dynamic camera with player input
Our dynamic camera may change properties such as:
- Field of view
- Camera distance
- Depth of field
- Camera angles
- Relative camera position
Some of these properties, such as field of view or depth of field, can already be changed through built-in engine types like FPostProcessSettings, while we have added others, such as dynamic camera distance.
All of these requirements are based on the famous talk “50 Camera Mistakes in Journey” by John Nesky, which definitely is a must-see for everyone involved with this topic.
Camera System Architecture
The final point of view of the player in each frame is determined by the camera responsibility chain of Unreal. The camera, the player controller, as well as the player camera manager are all involved with finding the camera position and angle in every moment of the game. The player camera manager allows further camera modifiers to register and modify the final point of view.
The base class of all of our camera modifiers, UHoatCameraModifier, extends the built-in UCameraModifier. It provides shared functionality for all camera modifiers, such as a cooldown period after each player input, disabling dynamic camera behavior to ensure that player intent always has priority, as well as smooth transitions in and out of camera effects like camera distance changes or depth of field changes.
All specific modifiers extend UHoatCameraModifier, and most of them refer to a custom struct called FHoatCameraInfo. This struct stores all properties that might be changed by one of our modifiers, such as field of view or camera distance, and this provides a common interface across all Blueprints for specifying camera behavior.
Camera Spring Arm
We could just use camera modifiers for changing the distance between the character and the camera. However, in that case, we would lose the advantage of the engine built-in USpringArmComponent, which ensures a specific distance from camera to player character unless the line of sight between both is occluded by an obstacle. In that case, the spring arm will automatically pull the camera closer to the character in order to keep line of sight.
The default camera distance can be changed through the Socket Offset of the USpringArmComponent. We’ve learned that it makes life for everyone a lot easier if we ensure that the whole team uses this socket offset only for setting the camera distance. If we’d allow anyone to change the camera transform itself, e.g. in Z direction, it becomes harder to understand the calculation that leads to the final camera position in game.
As we didn’t want to lose spring arm functionality, we created a custom UHoatCameraSpringArmComponent extending USpringArmComponent. Our component allows an arbitrary number of camera modifiers to change the spring arm length, and applies the results every frame while preserving basic spring arm functionality.
Camera Angles, Field of View, Camera Lag
Many other important default camera properties are already being changed by Unreal Engine 4 itself: You can specify the default field of view at the camera component as desired.
The player camera manager allows you to specify minimum and maximum pitch angles for the camera.
The spring arm component in turn also provides an option for enabling camera lag. You can enable this feature by enabling Camera Lag and Camera Rotation Lag and specifying the respective speeds in order to create what is sometimes called a rubber band camera: Each time the character starts moving in any direction after having stood still for a short while, the camera won’t follow immediately but wait a short moment to provide a less abrupt game experience. Consequently, it will also take a moment to stop moving after the player character has stopped moving.
Pitch-Based Camera Modifiers
All other features of our dynamic camera are based on camera modifiers. In other words, they are implemented in classes deriving from UHoatCameraModifier.
This first one allows various camera properties to change based on the current camera pitch angle. Usually, the camera pitch angle is changed directly through player input (e.g. by using the Y axis of the right stick on the gamepad). We wanted to change other camera properties as well each time the player tilted the camera, such as distance and consequently field of view. As it was always one of our main goals to provide maximum flexibility to our designers, we exposed UCurveFloat asset pointers here to allow defining arbitrary camera behavior derived from player camera pitch input.
Camera Modification Volumes
In some cases, level designers want to highlight very important level areas, e.g. when leaving a cave and confronting the player with a vast, open space. In other cases, puzzles or other game mechanics require an improved overview of the immediate surroundings of the player.
For both of these cases, we provide AHoatCameraModificationVolumes to be placed anywhere in the level.
These volumes are providing an FHoatCameraInfo property set along with a transition time, which both are applied by a camera modifier again.
Overlapping volumes are not supported yet, but can easily be added.
Our game features both ranged and close-quarters combat, the latter of which requires the player to get a good understanding of their immediate surroundings, including enemies attacking from behind. At the same time, we want to make it easier for the player to focus on the enemies at hand, instead of getting distracted by the view of the horizon.
We are a using a camera modifier again, applying an FHoatCameraInfo with increased camera distance and stronger depth of field while the player is in combat.
Focusing Walk Direction
One of the more basic requirements of our dynamic camera was to automatically focus the walk direction of the player character. The less the player needs to adjust the camera themselves, the better, and focusing the walk direction of the player character is something the player most likely would do themselves anyway, and they’d do so all the time.
This is why we’ve added a camera modifier for doing so without player input, experimenting with parameters such as rotating the camera only if the player is not standing still. If the player wants to rotate the camera, they should always have priority over our dynamic system. It is very frustrating to rotate the camera to observe an interesting spot nearby, only to see the camera slowly snap back automatically shortly after.
Focusing Ability Targets
Our player character has a handful of useful abilities he can throw at his enemies. While selecting a target with the right stick on their gamepad, the game enters slow motion and snaps the camera to the current selection.
This was one of our earliest features during development, so it was initially implemented without a camera modifier. Later, we learned that his target selection interfered with our dynamic camera system, so we created a camera modifier for this one as well, allowing designers to specify parameters such as camera rotation speed and snap speed.
Keeping Line of Sight
The last camera modifier we’ve added ensures that the camera doesn’t lose line of sight to the character. The default behavior of our camera spring arm is already able to fulfill this requirement by pulling the camera closer to the character.
Another way to approach this requirement is to have the camera swing around any obstacles that might be occluding the player character. In order to detect any possible obstacles, we are shooting multiple rays in increasing angles from the camera to the left and right of the character. These “whisker” raycasts, as introduced in “50 Camera Mistakes in Journey”, will detect any obstacle that might occlude the character in the immediate future, but before the character is actually occluded. If any obstacle is found, the camera slowly rotates around the character in order to keep line of sight. We expose properties for designers such as camera rotation speed, the step size in radians between two whisker raycasts, and how far ahead we should be looking.
Clearly, it doesn’t make sense to have the camera both pull closer and rotate around obstacles. Thus, we’ve introduced an additional collision channel called CameraRotateAround. For any given obstacle, no more than one of both collision channels should have its response enabled.
The camera responsibility chain of Unreal Engine 4 provides a solid and very flexible foundation for creating complex dynamic third person camera behaviors.
With a slightly modified camera spring arm component and several additional camera modifiers, we were able to develop a dynamic camera that ensures an enjoyable player experience, while highlighting key level areas and still always prioritize player intent.
As always, feel free to share any thoughts and improvements in the comments below. The full source is available at GitHub. Thank you for reading!