#include <ComposableCameraHitchcockZoomNode.h>
Inherits:
UComposableCameraCameraNodeBase
The Hitchcock Zoom (also known as the Vertigo effect, dolly zoom, or trombone shot): the camera dollies along its view axis while the FOV changes in the opposite direction, such that the target subject keeps roughly the same on-screen size while the background perspective warps dramatically.
Per-tick math: let InitialDistance = |UpstreamPos_0 - TargetPoint_0| let InitialFOV = InitialFOVOverride if > 0 else OutCameraPose.GetEffectiveFieldOfView() let LockConstant = InitialDistance * tan(radians(InitialFOV / 2))
NormalizedTime = min(ElapsedTime / Duration, 1) // Once-only: clamps Direction = (UpstreamPos - TargetPoint).SafeNormal()
if Driver == FromFOVDelta: FOV(t) = InitialFOV + FOVDeltaCurve(NormalizedTime) Dist(t) = LockConstant / tan(radians(FOV(t) / 2)) else: // FromDistanceDelta Dist(t) = InitialDistance + DistanceDeltaCurve(NormalizedTime) FOV(t) = 2 * degrees(atan(LockConstant / Dist(t)))
OutPose.Position = TargetPoint + Direction * Dist(t) OutPose.FieldOfView = FOV(t) OutPose.FocalLength = -1 // sentinel: FOV-mode authoritative Curve convention — additive delta, Y(0) = 0. Both curves express the change from the captured initial state, not the absolute trajectory. A curve of Y(0)=0, Y(1)=-30 on FOVDeltaCurve says "narrow the FOV by 30 degrees over the duration", regardless of whether InitialFOV was 60 or
- This keeps curves portable across cameras and guarantees the first tick outputs
InitialFOV/InitialDistanceexactly — no seam at t=0.
Direction is resampled every tick from the upstream pose, not frozen at activation. This lets an upstream LookAt / CameraOffset continue to steer the view direction during the effect — Hitchcock only owns the radial distance + FOV, leaving rotation composable with the rest of the chain.
FOV ownership. The node writes FieldOfView and clears FocalLength to -1 (pose's "FOV-mode" sentinel). If an upstream LensNode is in the chain, set its bOverrideFieldOfViewFromFocalLength to false so it doesn't fight for FOV authorship — LensNode's focal length / aperture / blade count still flow through, but FOV stays under this node's control. Alternatively, place HitchcockZoom after any FOV-writing node and it will simply overwrite them (last writer wins on the pose).
PlayMode is implicit: Once. There is no Loop / PingPong — authors who need a cyclic dolly zoom can drive the node externally (via re- activation or by repeatedly resetting the camera context).
Public Attributes¶
| Return | Name | Description |
|---|---|---|
TObjectPtr< AActor > |
PivotActor |
The subject the effect locks on. Camera dollies along the camera→subject axis; FOV compensates so this subject stays the same on-screen size. Required — the node is a pass-through with a warning when null. |
bool |
bUseBoneForDetection |
When true, target point is the named bone / socket on PivotActor's skeletal mesh (if 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 bone can't be found). Typical 50–80 to land on chest / head rather than foot. |
float |
InitialFOVOverride |
Baseline FOV (degrees) captured as InitialFOV on the first tick. When > 0, this value wins regardless of what the upstream pose carried for FieldOfView — the typical case for camera type assets that have no FieldOfViewNode / LensNode upstream and would otherwise inherit a renderer default on the first tick. |
EComposableCameraHitchcockZoomDriver |
Driver |
Which authored curve drives the effect. The other quantity is solved from the lock constant captured on the first tick. |
TObjectPtr< UCurveFloat > |
FOVDeltaCurve |
Additive FOV delta (degrees) over normalized time. X ∈ [0, 1], Y is DELTA from InitialFOV — author Y(0) = 0 so the first tick preserves InitialFOV exactly. Positive Y widens the FOV, negative narrows (the classic "zoom in as the camera dollies out" is a curve with negative Y values). |
TObjectPtr< UCurveFloat > |
DistanceDeltaCurve |
Additive distance delta (world units) over normalized time. X ∈ [0, 1], Y is DELTA from InitialDistance — author Y(0) = 0. Positive Y dollies the camera back (away from subject), negative Y pushes in. |
float |
Duration |
Seconds the effect takes to play from start to end state. After Duration elapses, the curves are evaluated at NormalizedTime = 1 and the pose freezes at the final state for as long as the node remains active. |
bool |
bEnable |
Master toggle. When false, the node is a pass-through this tick (no camera dolly, no FOV override). Useful for Blueprint-gated effects: trigger the activation + enable on cue, disable on cut. |
bool |
bClampCameraDistance |
When true, the derived camera distance is clamped to CameraDistanceClamp. Prevents pathological curve shapes from flying the camera off to kilometres away (very narrow FOV) or plunging into / past the subject (very wide FOV). |
FFloatInterval |
CameraDistanceClamp |
Min/max on the camera-to-subject distance when bClampCameraDistance is true. Min should be larger than the subject's extent so the camera doesn't end up inside the actor; Max bounds the "infinite |
PivotActor¶
TObjectPtr< AActor > PivotActor { nullptr }
The subject the effect locks on. Camera dollies along the camera→subject axis; FOV compensates so this subject stays the same on-screen size. Required — the node is a pass-through with a warning when null.
bUseBoneForDetection¶
bool bUseBoneForDetection { false }
When true, target point is the named bone / socket on PivotActor's skeletal mesh (if 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 bone can't be found). Typical 50–80 to land on chest / head rather than foot.
InitialFOVOverride¶
float InitialFOVOverride { -1.f }
Baseline FOV (degrees) captured as InitialFOV on the first tick. When > 0, this value wins regardless of what the upstream pose carried for FieldOfView — the typical case for camera type assets that have no FieldOfViewNode / LensNode upstream and would otherwise inherit a renderer default on the first tick.
When ≤ 0 (the default -1 sentinel matches the plugin's FOV-mode sentinel in LensNode and [FComposableCameraPose](../structs/FComposableCameraPose.md#fcomposablecamerapose)), falls back to OutCameraPose.GetEffectiveFieldOfView() as read from the upstream chain — the previous behaviour.
Only consulted on the first tick the node captures state. After that, LockConstant is frozen against whichever FOV was used, and subsequent ticks derive FOV from the Driver curve.
Driver¶
EComposableCameraHitchcockZoomDriver Driver { }
Which authored curve drives the effect. The other quantity is solved from the lock constant captured on the first tick.
FOVDeltaCurve¶
TObjectPtr< UCurveFloat > FOVDeltaCurve { nullptr }
Additive FOV delta (degrees) over normalized time. X ∈ [0, 1], Y is DELTA from InitialFOV — author Y(0) = 0 so the first tick preserves InitialFOV exactly. Positive Y widens the FOV, negative narrows (the classic "zoom in as the camera dollies out" is a curve with negative Y values).
A null curve is treated as identically zero — the node then leaves FOV at InitialFOV and camera at InitialDistance for the full duration, which is useful as a placeholder during blockout.
DistanceDeltaCurve¶
TObjectPtr< UCurveFloat > DistanceDeltaCurve { nullptr }
Additive distance delta (world units) over normalized time. X ∈ [0, 1], Y is DELTA from InitialDistance — author Y(0) = 0. Positive Y dollies the camera back (away from subject), negative Y pushes in.
Duration¶
float Duration { 3.f }
Seconds the effect takes to play from start to end state. After Duration elapses, the curves are evaluated at NormalizedTime = 1 and the pose freezes at the final state for as long as the node remains active.
bEnable¶
bool bEnable { true }
Master toggle. When false, the node is a pass-through this tick (no camera dolly, no FOV override). Useful for Blueprint-gated effects: trigger the activation + enable on cue, disable on cut.
bClampCameraDistance¶
bool bClampCameraDistance { false }
When true, the derived camera distance is clamped to CameraDistanceClamp. Prevents pathological curve shapes from flying the camera off to kilometres away (very narrow FOV) or plunging into / past the subject (very wide FOV).
CameraDistanceClamp¶
FFloatInterval CameraDistanceClamp { 50.f, 10000.f }
Min/max on the camera-to-subject distance when bClampCameraDistance is true. Min should be larger than the subject's extent so the camera doesn't end up inside the actor; Max bounds the "infinite corridor" look that a near-zero derived FOV produces.
Public Methods¶
| Return | Name | Description |
|---|---|---|
UComposableCameraHitchcockZoomNode 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. |
UComposableCameraHitchcockZoomNode¶
inline
inline UComposableCameraHitchcockZoomNode()
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 |
|---|---|---|
float |
ElapsedTime |
Elapsed seconds since the effect first ran. Drives NormalizedTime. |
double |
InitialDistance |
Captured on the first tick the node actually runs (PivotActor resolved, bEnable true). FOV is the effective FOV of the upstream pose (handles FOV-mode / FocalLength-mode conversion via GetEffectiveFieldOfView). |
double |
InitialFOVDegrees |
|
double |
LockConstant |
Invariant preserved for the full effect duration: distance * tan(radians(FOV / 2)). Captured from the initial distance and FOV on the first tick. |
bool |
bHasCapturedInitialState |
Cleared in OnInitialize; set true once the initial state has been captured. Re-activation resets via OnInitialize. |
FVector |
DebugTargetPoint |
|
FVector |
DebugCameraPosition |
|
FRotator |
DebugCameraRotation |
|
double |
DebugCurrentDistance |
|
double |
DebugCurrentFOV |
|
bool |
bDebugDrivenThisTick |
ElapsedTime¶
float ElapsedTime { 0.f }
Elapsed seconds since the effect first ran. Drives NormalizedTime.
InitialDistance¶
double InitialDistance { 0.0 }
Captured on the first tick the node actually runs (PivotActor resolved, bEnable true). FOV is the effective FOV of the upstream pose (handles FOV-mode / FocalLength-mode conversion via GetEffectiveFieldOfView).
InitialFOVDegrees¶
double InitialFOVDegrees { 0.0 }
LockConstant¶
double LockConstant { 0.0 }
Invariant preserved for the full effect duration: distance * tan(radians(FOV / 2)). Captured from the initial distance and FOV on the first tick.
bHasCapturedInitialState¶
bool bHasCapturedInitialState { false }
Cleared in OnInitialize; set true once the initial state has been captured. Re-activation resets via OnInitialize.
DebugTargetPoint¶
FVector DebugTargetPoint { FVector::ZeroVector }
DebugCameraPosition¶
FVector DebugCameraPosition { FVector::ZeroVector }
DebugCameraRotation¶
FRotator DebugCameraRotation { FRotator::ZeroRotator }
DebugCurrentDistance¶
double DebugCurrentDistance { 0.0 }
DebugCurrentFOV¶
double DebugCurrentFOV { 0.0 }
bDebugDrivenThisTick¶
bool bDebugDrivenThisTick { false }
Private Methods¶
| Return | Name | Description |
|---|---|---|
bool |
ResolveTargetPoint const |
Resolve the target world location from PivotActor + BoneName / PivotZOffset. Returns false when PivotActor is null. |
ResolveTargetPoint¶
const
bool ResolveTargetPoint(FVector & OutTargetPoint) const
Resolve the target world location from PivotActor + BoneName / PivotZOffset. Returns false when PivotActor is null.