Setting up multiple character cameras in Unreal Engine 5
This guide will show you how to add any number of cameras to a ACharacter
derived class and switch between them.
The full code is found in the MyCharacter
header and cpp files from here with get_next
function defined here.
High level overview
- Derive a class from
ACharacter
to hold the cameras - Define an active camera state enum and add it as a member variable
- Define a
constexpr
struct of camera properties - Add a TArray<UCameraComponent> member for the cameras
- Add a TArray<USpringArmComponent> member. Spring arms are used for third person cameras to prevent them from clipping through geometry.
- Add member functions for camera switching
Step by step process
Includes
The following headers are needed. The stdlib headers provide enum manipulation functions.
#include <utility>
#include <type_traits>
#include "Camera/CameraComponent.h"
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "MyCharacter.generated.h"
Creating the character class
UCLASS()
class SANDBOX_API AMyCharacter
: public ACharacter {
GENERATED_BODY()
}
The camera mode enum
Add an enumerator for each camera, plus a final MAX
enumerator.
The MAX
enumerator represents the number of cameras.
Access its value at compile or run time by casting to the underlying type with std::to_underlying
.
UENUM(BlueprintType)
enum class ECharacterCameraMode : uint8 {
FirstPerson UMETA(DisplayName = "First Person"),
ThirdPerson UMETA(DisplayName = "Third Person"),
MAX UMETA(Hidden)
};
To help cycle through the active camera, write a function to cycle through the enum values.
template <typename Enum, auto MAX_VALUE = Enum::MAX>
Enum get_next(Enum current) {
auto const next{std::to_underlying(current) + 1};
static constexpr auto MAX{std::to_underlying(MAX_VALUE)};
using Underlying = std::underlying_type_t<Enum>;
return (next >= MAX) ? static_cast<Enum>(Underlying{0}) : static_cast<Enum>(next);
}
Compile time camera configuration
Define a constexpr
struct to create a compile time camera configuration.
struct FCameraConfig {
constexpr FCameraConfig(ECharacterCameraMode camera_mode,
char const* component_name,
bool needs_spring_arm,
bool use_pawn_control_rotation)
: camera_mode(camera_mode)
, camera_index(std::to_underlying(camera_mode))
, component_name(component_name)
, needs_spring_arm(needs_spring_arm)
, use_pawn_control_rotation(use_pawn_control_rotation) {}
ECharacterCameraMode camera_mode;
std::underlying_type_t<ECharacterCameraMode> camera_index;
char const* component_name;
bool needs_spring_arm;
bool use_pawn_control_rotation;
};
Create a constexpr
array of MAX
camera configurations.
namespace ml::AMyCharacter {
inline static constexpr int32 camera_count{static_cast<int32>(ECharacterCameraMode::MAX)};
inline static constexpr FCameraConfig camera_configs[camera_count] = {
{ECharacterCameraMode::FirstPerson, "Camera", false, true},
{ECharacterCameraMode::ThirdPerson, "ThirdPersonCamera", true, true}};
}
Write a consteval
function to calculate the required number of spring arms.
namespace ml::AMyCharacter {
consteval int32 count_required_spring_arms() {
int32 count{0};
for (auto const& config : camera_configs) {
if (config.needs_spring_arm) {
++count;
}
}
return count;
}
}
Class members
Add the camera class data members along with the member functions for changing camera.
Unreal component names require FName
for their name however FName
is not a constexpr
type.
Use a char const*
and convert it to TCHAR*
later with Unreal’s ANSI_TO_TCHAR
macro.
public:
static constexpr int32 camera_count{ml::AMyCharacter::camera_count};
static constexpr int32 spring_arm_count{
ml::AMyCharacter::count_required_spring_arms()};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
TArray<UCameraComponent*> cameras{};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
TArray<USpringArmComponent*> spring_arms{};
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
ECharacterCameraMode camera_mode{ECharacterCameraMode::FirstPerson};
UFUNCTION()
void cycle_camera();
private:
virtual void handle_death();
void disable_all_cameras();
void change_camera_to(ECharacterCameraMode mode);
Class implementation
Constructor
Initialise the camera and spring arm arrays to nullptr
.
Loop through each camera config and initialise the UCameraComponent
(and the USpringArmComponent
if needed) and attach them to the character.
Other properties can be added by either adding data to FCameraConfig
or creating a child Blueprint class and editing them in the class’s viewport in the Unreal Editor.
AMyCharacter::AMyCharacter() {
PrimaryActorTick.bCanEverTick = true;
// Initialise arrays
cameras.Init(nullptr, camera_count);
spring_arms.Init(nullptr, spring_arm_count);
// Create cameras using configurations
int32 spring_arm_index{0};
for (auto const& config : ml::AMyCharacter::camera_configs) {
auto& camera_component{cameras[config.camera_index]};
camera_component =
CreateDefaultSubobject<UCameraComponent>(
ANSI_TO_TCHAR(config.component_name));
camera_component->bUsePawnControlRotation =
config.use_pawn_control_rotation;
if (config.needs_spring_arm) {
auto& spring_arm{spring_arms[spring_arm_index]};
spring_arm = CreateDefaultSubobject<USpringArmComponent>(
*FString::Printf(TEXT("SpringArm_%s"), *camera_component->GetName()));
spring_arm->SetupAttachment(RootComponent);
camera_component->SetupAttachment(spring_arm);
++spring_arm_index;
} else {
camera_component->SetupAttachment(RootComponent);
}
}
}
Disabling all cameras
void AMyCharacter::disable_all_cameras() {
for (auto* camera : cameras) {
if (camera) {
camera->SetActive(false);
}
}
}
Changing camera
To change the camera:
- Disable all cameras
- Get the desired camera index with
std::to_underlying(mode)
- Enable the desired camera with
SetActive(true)
or enable a default camera if the index is out of range
void AMyCharacter::change_camera_to(ECharacterCameraMode mode) {
camera_mode = mode;
disable_all_cameras();
constexpr auto default_index{std::to_underlying(ECharacterCameraMode::FirstPerson)};
auto const camera_index{std::to_underlying(camera_mode)};
if (cameras.IsValidIndex(camera_index) && cameras[camera_index]) {
cameras[camera_index]->SetActive(true);
} else {
if (cameras.IsValidIndex(default_index)) {
cameras[default_index]->SetActive(true);
}
}
}
Cycling to the next camera state
Cycle to the next camera with:
void AMyCharacter::cycle_camera() {
change_camera_to(get_next(camera_mode));
}
Conclusion
Setting up cameras requires initialising UCameraComponent
s and using SetActive
to toggle between them.
With some compile time code, we can easily handle any number of cameras.