A custom modifier is a class derived from UComposableCameraModifierBase that mutates a specific node class's parameters before evaluation. Modifiers are authored in Blueprint most of the time — the base class is Blueprintable and most modifier effects are small enough that the BP overhead is irrelevant. This page covers both BP and C++ flows, and when to reach for either.
When to write a modifier¶
Write a modifier when the effect is:
- Conditional — only applied in certain gameplay states (sprinting, aiming, stunned, in debug camera).
- A parameter tweak — it mutates an existing node's fields rather than adding new per-frame work.
- Targeted at a specific node class —
FieldOfViewNode.FOV,ControlRotateNode.HorizontalSpeed,PivotDampingNode.Interpolator.Speed.
If the effect is always on, author it as a node configuration. If the effect needs new per-frame logic that no existing node provides, write a new node.
Non-transient cameras only
Modifiers are resolved on non-transient cameras only. Cinematic intros and short-lived overlays activated with bIsTransient = true skip modifier resolution entirely. If a cinematic needs to be modifier-aware, clear bIsTransient on its activation params.
The authoring model¶
There are two classes you'll work with:
UComposableCameraModifierBase — the thing that does the work. Declares NodeClass and ApplyModifier(Node). Abstract, Blueprintable.
UComposableCameraNodeModifierDataAsset — the Content-Browser wrapper that groups modifiers and adds routing metadata (priority, camera tags, enter/exit transition overrides). This is the asset gameplay code adds and removes.
You almost always author both: a modifier class (BP or C++) that defines the effect, and a data asset that references one or more modifier instances and ties them to a tag + priority.
Blueprint authoring — the common path¶
Step 1: create a modifier Blueprint.
Content Browser → right-click → Blueprint Class → search for ComposableCameraModifierBase. Name it something like BP_SprintFOVBump.
Step 2: set NodeClass on the Class Default Object.
Open the BP, find NodeClass in the Details panel, set it to the node class you want to target (e.g. FieldOfViewNode). This is a CDO-level configuration — not something you set per-instance.
Step 3: implement ApplyModifier as a BP event.
The event gets a Node reference typed as UComposableCameraCameraNodeBase. Cast it to your concrete node class and mutate its parameters:
Event ApplyModifier (Node)
→ Cast to FieldOfViewNode
→ Set FieldOfView (Target: Cast result, New Value: 95.0)
That's the entire modifier. Compile and save.
Step 4: create the data asset wrapper.
Content Browser → right-click → Miscellaneous → Data Asset → pick ComposableCameraNodeModifierDataAsset. Or use the direct entry under Composable Camera System → Node Modifier Data Asset (purple thumbnail).
In the asset:
- Add your
BP_SprintFOVBumpmodifier to theModifiersarray. - Set
Priority(higher wins when two groups target the same node class on the same camera). - Set
CameraTags(e.g.Gameplay.ThirdPerson— the modifier applies to any camera whose type asset carries this tag). - Optionally set
OverrideEnterTransition/OverrideExitTransitionfor the reactivation blend when the modifier is added/removed.
Step 5: add and remove at runtime.
From gameplay Blueprint or C++:
AComposableCameraPlayerCameraManager* PCM =
UComposableCameraBlueprintLibrary::GetComposableCameraPlayerCameraManager(WorldContext, /*PlayerIndex*/ 0);
UComposableCameraBlueprintLibrary::AddModifier(WorldContext, PCM, SprintModifierAsset);
// ... later:
UComposableCameraBlueprintLibrary::RemoveModifier(WorldContext, PCM, SprintModifierAsset);
The PCM's UComposableCameraModifierManager handles tag matching, priority resolution, and any reactivation blend.
C++ authoring — when to reach for it¶
Most modifiers do not need C++. Reach for it when:
- The effect is performance-sensitive and runs on many cameras (the BP VM overhead per activation is not per-frame, but you still want C++ if you're allocating in the effect).
- The effect is reusable across projects and you want it shipped in a module rather than as a Blueprint asset.
- The effect needs non-trivial math that's painful to express in BP graph form.
The C++ pattern:
// SprintFOVBumpModifier.h
#pragma once
#include "CoreMinimal.h"
#include "Modifiers/ComposableCameraModifierBase.h"
#include "SprintFOVBumpModifier.generated.h"
UCLASS(meta = (DisplayName = "Sprint FOV Bump"))
class MYPROJECT_API USprintFOVBumpModifier : public UComposableCameraModifierBase
{
GENERATED_BODY()
public:
USprintFOVBumpModifier();
UPROPERTY(EditAnywhere, Category = "Sprint")
float SprintFOV = 95.0f;
protected:
virtual void ApplyModifier_Implementation(
UComposableCameraCameraNodeBase* Node) override;
};
// SprintFOVBumpModifier.cpp
#include "SprintFOVBumpModifier.h"
#include "Nodes/ComposableCameraFieldOfViewNode.h"
USprintFOVBumpModifier::USprintFOVBumpModifier()
{
NodeClass = UComposableCameraFieldOfViewNode::StaticClass();
}
void USprintFOVBumpModifier::ApplyModifier_Implementation(
UComposableCameraCameraNodeBase* Node)
{
if (UComposableCameraFieldOfViewNode* FOVNode =
Cast<UComposableCameraFieldOfViewNode>(Node))
{
FOVNode->FieldOfView = SprintFOV;
}
}
Then author a UComposableCameraNodeModifierDataAsset the same way as the BP flow, adding a default-constructed USprintFOVBumpModifier to its Modifiers array.
Priority and tag semantics (restated — these bite)¶
Two modifier groups can both target the same camera and the same node class. When that happens:
- Only the higher-priority group's modifier fires for that node class on that camera.
- Priority is per-(camera, node class) pair — a group can win on
FieldOfViewNodeand lose onControlRotateNodesimultaneously. - There is no stacking within a single
NodeClass.
If you want additive effects on the same node class (sprint +10° FOV and zoom −5° FOV), compose them inside a single modifier's ApplyModifier — don't rely on the manager to stack.
Camera tag matching uses FGameplayTagContainer::HasAny, not HasAll:
- A group tagged
Gameplay.ThirdPersonapplies to any camera whose tag set containsGameplay.ThirdPerson, regardless of what other tags the camera carries. - Empty
CameraTagsmeans "applies to all cameras" — use sparingly, it's an easy way to accidentally tweak a cutscene camera.
Reactivation transition overrides¶
Adding or removing a modifier group can cause the running camera to reactivate (because its effective modifier for some node class changed). That reactivation picks a transition through this chain:
- If adding →
OverrideEnterTransitionon the incoming group, if set. - If removing →
OverrideExitTransitionon the outgoing group, if set. - Otherwise → the five-tier resolution chain, starting from the target camera's
EnterTransition.
Use the overrides surgically — they exist so a specific modifier effect can blend differently from the camera's default, without forcing a transition table entry or a caller-supplied override at every add/remove site.
Folder placement¶
| File | Location |
|---|---|
| Modifier class header | Source/ComposableCameraSystem/Public/Modifiers/MyModifier.h |
| Modifier class source | Source/ComposableCameraSystem/Private/Modifiers/MyModifier.cpp |
For project-side modifiers, mirror the Public/Modifiers / Private/Modifiers layout in your project module.
Hot-path rule¶
ApplyModifier runs at activation time, not every frame. Allocations are allowed here — cache a reference, allocate a buffer, whatever the effect requires. But the node you mutate will be ticked every frame afterward, so don't set it up in a way that causes the node's tick to allocate. Prefer scalar assignments over array reallocations on the target node's fields.
Testing¶
- Construct the modifier asset in the editor and add it to a simple modifier data asset.
- In PIE, activate a camera whose tag set matches the modifier's
CameraTags. - Call
AddModifier/RemoveModifierfrom a console command or input mapping. - Visually verify the effect, and (optionally) assert on the node's parameter values via a Gameplay Debugger extension.
For C++ modifiers, also write a unit-style test that constructs the modifier, constructs the target node, calls ApplyModifier directly, and asserts on the mutated fields. No PCM required for that path.
Verifying with showdebug¶
Open showdebug composablecamera during PIE while the modifier is active. Under Effective Modifiers, you should see your modifier listed:
[Camera Node] ComposableCameraFieldOfViewNode:
[Modifier] BP_SprintFOVBump_C from [Asset] DA_SprintFOVModifier with priority 10
When the modifier is removed, the entry disappears. This is the quickest way to confirm tag matching and priority resolution are working as expected.
Worked example: sprint FOV bump¶
This section walks through the full end-to-end modifier authoring flow — a sprint FOV bump that widens the active gameplay camera's field of view to 95° when the player sprints, blending smoothly in both directions.
Prerequisites¶
You need a gameplay camera with a FieldOfViewNode in its chain (the Follow Camera tutorial produces one). The camera's type asset must carry a CameraTag of Gameplay.ThirdPerson.
Step 1 — create the modifier class (Blueprint)¶
Content Browser → right-click → Blueprint Class → search for ComposableCameraModifierBase. Name it BP_SprintFOVBump.
Open it. In the Class Defaults panel, set NodeClass to ComposableCameraFieldOfViewNode. Override Apply Modifier in the Event Graph:
Event ApplyModifier (Node)
→ Cast to ComposableCameraFieldOfViewNode
→ Set FieldOfView = 95.0
Compile and save.
Parameterize the FOV
For a more reusable modifier, add a UPROPERTY(EditAnywhere) variable SprintFOV (default 95.0) on the Blueprint and use it instead of a hardcoded value. Then each data asset that references the modifier can set a different sprint FOV without duplicating the class.
Step 2 — create the data asset wrapper¶
Content Browser → right-click → Composable Camera System → Node Modifier Data Asset. Name it DA_SprintFOVModifier.
| Field | Value | Why |
|---|---|---|
| Modifiers | One entry: BP_SprintFOVBump |
The modifier class from step 1 |
| Priority | 10 |
Wins over any lower-priority group also targeting FieldOfViewNode. Typical gameplay modifiers live in the 1–20 range. |
| CameraTags | Gameplay.ThirdPerson |
Only applies to cameras whose type asset carries this tag. Keeps cutscene cameras unaffected. |
| OverrideEnterTransition | (optional) An InertializedTransition with TransitionDuration = 0.25 |
A quick blend in when the modifier activates. |
| OverrideExitTransition | (optional) An InertializedTransition with TransitionDuration = 0.35 |
A slightly longer blend out — the return to normal FOV feels smoother if it's slower than the snap in. |
Step 3 — wire up sprint input¶
Open your character (or player controller) Blueprint:
On Sprint Started
└─> Get Composable Camera Player Camera Manager (Index 0) ─┐
└─> Add Modifier (PCM: ↑, Modifier Asset: DA_SprintFOVModifier)
On Sprint Ended
└─> Get Composable Camera Player Camera Manager (Index 0) ─┐
└─> Remove Modifier (PCM: ↑, Modifier Asset: DA_SprintFOVModifier)
Step 4 — play and verify¶
Enter PIE and sprint. You should see a smooth inertialized blend from the default FOV to 95° as the modifier kicks in, the wider FOV hold for the duration of the sprint, and a smooth blend back when sprint ends.
Common pitfalls¶
- Modifier doesn't apply — camera has no matching tag. Check
CameraTagon the type asset andCameraTagson the data asset. EmptyCameraTagsmeans "all cameras" (risky); a specific tag means the camera must carry it. - Modifier applies but snaps instead of blends. The camera's
EnterTransitionis null, and the data asset'sOverrideEnterTransitionis also null — so the reactivation is a hard cut. Set one of them. - Two modifiers fight — wrong one wins. Higher
Prioritywins per(camera, node class). There is no stacking. If you need additive effects on the same node class, compose them inside a singleApplyModifier. - Modifier applies to the cutscene camera.
CameraTagsis empty, so it matches everything. Set it to a specific gameplay tag. - Transient camera ignores the modifier. Transient cameras skip modifier resolution entirely. Clear
bIsTransienton the activation params if the camera needs to be modifier-aware.
See also: Modifiers Catalog for the exact field semantics; Concepts → Modifiers for the full lifecycle and resolution model; Custom Nodes if the effect needs per-frame work rather than a parameter tweak.