#include <ComposableCameraFocusPullNode.h>

Inherits: UComposableCameraCameraNodeBase

Dynamically drives the camera pose's FocusDistance from the distance to a target actor. Single-responsibility node — it only touches FocusDistance. Everything else DoF needs (aperture, blade count, filmback, and the PhysicalCameraBlendWeight that gates whether DoF is applied at all) is expected to come from an upstream LensNode. The intended composition: ... → LensNode(FocalLength, Aperture, BlendWeight=1, FocusDistance=-1) → FocusPullNode(drives FocusDistance from PivotActor) → ... LensNode's FocusDistance = -1 sentinel is the "leave for downstream" signal that pairs cleanly with this node. If LensNode instead writes a concrete focus distance, FocusPullNode will overwrite it (last writer wins on the pose) — both work, the sentinel just makes the intent obvious at read time.

Without any LensNode upstream, the pose's default PhysicalCameraBlendWeight is 0 — ApplyPhysicalCameraSettings() will not route FocusDistance into the post-process DoF slots regardless of what this node writes. Add a LensNode (or at minimum wire PhysicalCameraBlendWeight > 0 some other way) or the node is a no-op at the renderer level.

Target resolution matches the plugin's CollisionPushNode / OcclusionFadeNode pattern (PivotActor + bone/socket + Z offset) so the same context-parameter wiring flows to all three. Damping is optional and uses the standard interpolator system (SpringDamper / IIR / SimpleSpring) so the focus-pull rate matches the project's broader camera tuning.

Per-tick formula: TargetPoint = Resolve(PivotActor, BoneName | PivotZOffset) CameraFwd = OutCameraPose.Rotation.Vector() Depth = (TargetPoint - OutCameraPose.Position) · CameraFwd if Depth <= 0: pass-through this tick (target is behind camera) Raw = Depth + FocusDistanceOffset if bClampFocusDistance: Raw = FMath::Clamp(Raw, Clamp.Min, Clamp.Max) if FocusInterpolator: Raw = Interp.Run(LastFocus → Raw, DeltaTime) OutCameraPose.FocusDistance = Raw FocusDistance is camera-space depth (distance along the view axis), NOT Euclidean distance — that's what ApplyPhysicalCameraSettings and the renderer's DoF system consume. For an off-axis target the two diverge significantly (10 m @ 45° has depth ~7 m), so projecting onto the camera forward is the correct reduction.

First tick after activation bypasses the damping so the initial focus distance snaps to the real depth (avoids a visible focus ramp from whatever the pose's previous FocusDistance was).

Public Attributes

Return Name Description
TObjectPtr< AActor > PivotActor Actor whose distance from the camera drives the focus distance. Typically the player pawn or a narrative focal actor. Required — the node pass-throughs with a warning each tick when unset.
bool bUseBoneForDetection When true, the target point is sampled at the named bone / socket on PivotActor's skeletal mesh (if present and resolvable). Falls back to ActorLocation + PivotZOffset on any failure.
FName BoneName Bone / socket name, sampled when bUseBoneForDetection is true.
float PivotZOffset World-Z offset added to ActorLocation when bUseBoneForDetection is false (or the requested bone can't be found). Typical 50–80 to land on a chest/head target rather than foot.
bool bEnableFocusPull Master toggle. When false, the node is a no-op pass-through this tick — the previous FocusDistance on the pose is preserved. Useful for Blueprint-driven "focus hold" moments (aim down sights, cinematic freeze, etc.) where external logic wants to take over.
float FocusDistanceOffset Constant offset added to the on-axis camera→target depth before clamp and damping. Positive = focus farther along the view axis than the target (e.g. focus slightly past the subject); negative = focus nearer. Applied to the projected depth, not Euclidean distance, so it stays visually consistent regardless of how off-axis the target is.
bool bClampFocusDistance When true, the resolved focus distance is clamped to FocusDistanceClamp.
FFloatInterval FocusDistanceClamp Min/max range applied when bClampFocusDistance is true. Min clamps against micro-distances (rarely useful below ~10 cm; the renderer's near plane usually dominates before that). Max clamps against the "background"-tier distances that would produce no visible DoF anyway.
TObjectPtr< UComposableCameraInterpolatorBase > FocusInterpolator Optional interpolator applied to the focus distance each tick. When null, the node is stateless and the pose's FocusDistance equals the raw (clamped, offset-adjusted) distance every frame — visually this means focus tracks the target with zero lag, which is jittery for fast-moving targets. Pick any of the built-in interpolators (SpringDamper / IIR / SimpleSpring) to get a smooth "focus pull" that matches the camera's broader tuning. First tick after activation bypasses the interpolator to avoid a visible ramp from the previous FocusDistance value.

PivotActor

TObjectPtr< AActor > PivotActor { nullptr }

Actor whose distance from the camera drives the focus distance. Typically the player pawn or a narrative focal actor. Required — the node pass-throughs with a warning each tick when unset.


bUseBoneForDetection

bool bUseBoneForDetection { false }

When true, the target point is sampled at the named bone / socket on PivotActor's skeletal mesh (if present and resolvable). Falls back to ActorLocation + PivotZOffset on any failure.


BoneName

FName BoneName

Bone / socket name, sampled when bUseBoneForDetection is true.


PivotZOffset

float PivotZOffset { 50.f }

World-Z offset added to ActorLocation when bUseBoneForDetection is false (or the requested bone can't be found). Typical 50–80 to land on a chest/head target rather than foot.


bEnableFocusPull

bool bEnableFocusPull { true }

Master toggle. When false, the node is a no-op pass-through this tick — the previous FocusDistance on the pose is preserved. Useful for Blueprint-driven "focus hold" moments (aim down sights, cinematic freeze, etc.) where external logic wants to take over.


FocusDistanceOffset

float FocusDistanceOffset { 0.f }

Constant offset added to the on-axis camera→target depth before clamp and damping. Positive = focus farther along the view axis than the target (e.g. focus slightly past the subject); negative = focus nearer. Applied to the projected depth, not Euclidean distance, so it stays visually consistent regardless of how off-axis the target is.


bClampFocusDistance

bool bClampFocusDistance { false }

When true, the resolved focus distance is clamped to FocusDistanceClamp.


FocusDistanceClamp

FFloatInterval FocusDistanceClamp { 10.f, 100000.f }

Min/max range applied when bClampFocusDistance is true. Min clamps against micro-distances (rarely useful below ~10 cm; the renderer's near plane usually dominates before that). Max clamps against the "background"-tier distances that would produce no visible DoF anyway.


FocusInterpolator

TObjectPtr< UComposableCameraInterpolatorBase > FocusInterpolator

Optional interpolator applied to the focus distance each tick. When null, the node is stateless and the pose's FocusDistance equals the raw (clamped, offset-adjusted) distance every frame — visually this means focus tracks the target with zero lag, which is jittery for fast-moving targets. Pick any of the built-in interpolators (SpringDamper / IIR / SimpleSpring) to get a smooth "focus pull" that matches the camera's broader tuning. First tick after activation bypasses the interpolator to avoid a visible ramp from the previous FocusDistance value.

Public Methods

Return Name Description
UComposableCameraFocusPullNode inline
void OnInitialize_Implementation virtual
void OnTickNode_Implementation virtual
void GetPinDeclarations_Implementation virtual const
void DrawNodeDebug virtual const Called each frame when the CCS.Debug.Viewport CVar is enabled, for every node on the currently running camera. Override to draw world-space debug gizmos via DrawDebugHelpers (DrawDebugSphere, DrawDebugLine, etc.) that visualise this node's runtime state — e.g. a pivot sphere for PivotOffsetNode, a look-at line for LookAtNode, the collision trace for CollisionPushNode, a sampled spline path for SplineNode.

UComposableCameraFocusPullNode

inline

inline UComposableCameraFocusPullNode()

OnInitialize_Implementation

virtual

virtual void OnInitialize_Implementation()

OnTickNode_Implementation

virtual

virtual void OnTickNode_Implementation(float DeltaTime, const FComposableCameraPose & CurrentCameraPose, FComposableCameraPose & OutCameraPose)

GetPinDeclarations_Implementation

virtual const

virtual void GetPinDeclarations_Implementation(TArray< FComposableCameraNodePinDeclaration > & OutPins) const

DrawNodeDebug

virtual const

virtual void DrawNodeDebug(UWorld * World, bool bViewerIsOutsideCamera) const

Called each frame when the CCS.Debug.Viewport CVar is enabled, for every node on the currently running camera. Override to draw world-space debug gizmos via DrawDebugHelpers (DrawDebugSphere, DrawDebugLine, etc.) that visualise this node's runtime state — e.g. a pivot sphere for PivotOffsetNode, a look-at line for LookAtNode, the collision trace for CollisionPushNode, a sampled spline path for SplineNode.

Access the owning camera via OwningCamera and current-frame pin values via the usual GetInputPinValue<T>() / member-read path — this hook fires AFTER TickNode, so pin-backed UPROPERTYs still hold the resolved values from the most recent evaluation.

bViewerIsOutsideCamera mirrors the ticker's frustum-draw flag: true when the viewer is observing the camera from outside (F8 eject, SIE, or CCS.Debug.Viewport.AlwaysShow), false when the player is looking through the camera. Most gizmos (pivot spheres at distant characters, lines to look-at targets, spline polylines far in the world) can ignore this and draw unconditionally. Gizmos that sit AT the camera's own position (e.g. CollisionPushNode's self-collision sphere) should gate on this bool so they don't hermetically seal the player inside the wireframe during live gameplay.

Default implementation does nothing. Compiled out in shipping builds.

Private Attributes

Return Name Description
TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > FocusInterpolator_T Runtime instance built from FocusInterpolator in OnInitialize. Null when no interpolator is configured or the asset's BuildDoubleInterpolator returns null.
double LastSmoothedFocusDistance Persistent smoothed focus distance between frames. Seeded on the first tick from the raw distance so frame-zero doesn't drift in from an arbitrary sentinel.
bool bHasSeededSmoothing Cleared in OnInitialize; set true on first successful tick so re- activation re-seeds.
FVector DebugTargetPoint
FVector DebugCameraPosition
FRotator DebugCameraRotation
double DebugResolvedFocusDistance
bool bDebugWasDrivenThisTick

FocusInterpolator_T

TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > FocusInterpolator_T

Runtime instance built from FocusInterpolator in OnInitialize. Null when no interpolator is configured or the asset's BuildDoubleInterpolator returns null.


LastSmoothedFocusDistance

double LastSmoothedFocusDistance { 0.0 }

Persistent smoothed focus distance between frames. Seeded on the first tick from the raw distance so frame-zero doesn't drift in from an arbitrary sentinel.


bHasSeededSmoothing

bool bHasSeededSmoothing { false }

Cleared in OnInitialize; set true on first successful tick so re- activation re-seeds.


DebugTargetPoint

FVector DebugTargetPoint { FVector::ZeroVector }

DebugCameraPosition

FVector DebugCameraPosition { FVector::ZeroVector }

DebugCameraRotation

FRotator DebugCameraRotation { FRotator::ZeroRotator }

DebugResolvedFocusDistance

double DebugResolvedFocusDistance { 0.0 }

bDebugWasDrivenThisTick

bool bDebugWasDrivenThisTick { false }

Private Methods

Return Name Description
bool ResolveTargetPoint const Resolve the target world location from PivotActor + BoneName / PivotZOffset. Returns false when PivotActor is null. Mirrors OcclusionFadeNode::ResolveTargetPoint.

ResolveTargetPoint

const

bool ResolveTargetPoint(FVector & OutTargetPoint) const

Resolve the target world location from PivotActor + BoneName / PivotZOffset. Returns false when PivotActor is null. Mirrors OcclusionFadeNode::ResolveTargetPoint.