A modifier changes selected properties on an existing camera node class while a non-transient camera is running. Most modifier assets should use the built-in Node Type override workflow; write a custom modifier class only when a static property copy is not expressive enough.
When to write a modifier¶
Write a modifier when the effect is:
- Conditional: sprinting, aiming, stunned, photo mode, or a debug camera state.
- A parameter tweak: it changes fields on nodes that already exist.
- Targeted at a specific node class:
FieldOfViewNode.FieldOfView,ControlRotateNode.HorizontalSpeed, orPivotDampingNode.Interpolator.
If the effect is always on, author it directly in the camera type. If the effect needs new per-frame behavior, write a new node.
Non-transient cameras only
Modifiers are resolved on non-transient cameras only. Short-lived cameras
activated with bIsTransient = true skip modifier resolution. If a
cinematic needs to be modifier-aware, clear bIsTransient on its activation
params.
The authoring model¶
There are two runtime objects involved:
UComposableCameraNodeModifierDataAsset is the Content Browser asset gameplay
code adds and removes. It owns routing metadata: ApplyMode, priority, camera
tag query, transition overrides, and the list of modifier entries.
UComposableCameraModifierBase is each entry in the asset's Modifiers array.
It either uses Node Type override mode or delegates to a custom modifier class.
Node Type override mode¶
Use this path first. It is designer-friendly, serializes cleanly, and supports both camera reactivation and in-place value transitions.
- Content Browser -> right-click -> Composable Camera System -> Node Modifier Data Asset.
- Add an entry to
Modifiers. - Leave
Use Custom Modifier Classdisabled. - Choose the node class in
Node Template. - Check the properties to override.
- Edit the checked values on the template.
- Set
PriorityandCameraTagQuery.
Only editable, non-transient, non-deprecated top-level node properties are
eligible. Properties marked with NoModifierOverride are intentionally hidden.
Apply mode¶
The asset's ApplyMode controls how an effective-set change reaches the live
camera.
ReactivateCamera is the default. Adding or removing the asset reconstructs the
current type-asset camera, applies the modifier values during construction, and
blends from the old pose to the new pose. Use OverrideEnterTransition and
OverrideExitTransition when this modifier should use a different camera-pose
blend than the camera type's normal transition chain.
ModifyExistingInstance keeps the same camera and node instances alive. The
runtime state writes checked Node Type properties into the live node and can
blend supported values through UComposableCameraModifierTransitionBase. Use
OverrideEnterValueTransition, OverrideReplaceValueTransition, and
OverrideExitValueTransition for these value blends. Leave them unset for
immediate changes, except that a null Replace preserves the legacy priority
handoff rule.
In-place modification is intended for properties that are safe to retune on a live node. It does not add nodes, remove nodes, rebuild the RuntimeDataBlock, or run arbitrary Blueprint modifier code every frame.
Camera tag queries¶
Camera types carry CameraTags. Modifier assets carry CameraTagQuery.
- Empty
CameraTagQuerymatches every camera. - Use
ANY,ALL, andNONEquery groups for gameplay-specific routing. - Legacy modifier
CameraTagsvalues migrate to anANYquery during load.
For example, a sprint FOV modifier might match cameras with
Gameplay.ThirdPerson, while an ADS weapon modifier might match
Gameplay.ADS.
Custom Modifier Class mode¶
Use a custom class when the effect needs procedural logic instead of a checked property copy.
Blueprint¶
- Create a Blueprint class derived from
ComposableCameraModifierBase. - Set
NodeClasson the class defaults to the node class you want to mutate. - Add editable variables for the values your modifier needs.
- Implement
ApplyModifier. - In the data asset entry, enable
Use Custom Modifier Classand assign an instanced object of your Blueprint class.
Example Blueprint flow:
Event ApplyModifier(Node)
-> Cast to FieldOfViewNode
-> Set FieldOfView from SprintFieldOfView
ApplyModifier runs when the camera is built or reactivated. It is not a
per-frame callback.
C++¶
// 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 add an instance of USprintFOVBumpModifier to a
UComposableCameraNodeModifierDataAsset.
Runtime add and remove¶
From Blueprint or C++, call AddModifier and RemoveModifier on
UComposableCameraBlueprintLibrary, or call the same operations directly on an
AComposableCameraPlayerCameraManager.
The PCM's UComposableCameraModifierManager handles tag-query matching,
priority resolution, reactivation, and in-place value ownership.
Priority and stacking¶
Two modifier assets can both target the same camera and the same node class. Node Type entries elect winners per checked property, so disjoint properties can compose on one node class while overlapping properties still use priority.
Custom Modifier Class entries stay on a whole-node lane: if a custom modifier wins a node-class bucket, it is not composed with Node Type entries for that class.
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 and
Private/Modifiers layout in your project module.
Testing¶
- Create the modifier data asset in the editor.
- In PIE, activate a non-transient camera whose
CameraTagssatisfy the asset'sCameraTagQuery. - Call
AddModifierandRemoveModifierfrom a console command, input mapping, or gameplay event. - Check
showdebug camera,CCS.Debug.Panel 1, orCCS.Dump.Camerato confirm the effective modifier and live node values.
For C++ custom modifier classes, also write a unit-style test that constructs
the modifier, constructs the target node, calls ApplyModifier directly, and
asserts on the mutated fields.
Common pitfalls¶
- Modifier does not apply. The camera may be transient, the
CameraTagQuerymay not match, or another active asset may have higher priority for the same checked property. - Modifier snaps. A
ReactivateCameraasset has no modifier transition and the camera transition chain resolves to null. Set a camera transition or useModifyExistingInstancewith a value transition for property-only tuning. - In-place modifier appears ignored. The property may not be eligible for in-place ownership or continuous blending. Use a checked Node Type override on an editable top-level node property.
- Two modifiers fight. Higher
Prioritywins per checked property. Split effects across disjoint Node Type properties when they should compose. - A cutscene camera changes unexpectedly. Empty
CameraTagQuerymatches every camera. Add a stricter query or mark the cutscene activation transient.