Aim Stage Variants
Categories:
The Aim stage owns state.rotation for each tick. Two variants ship with GS_PhantomCam; other gems (notably gs_performer) register additional variants through the same IAimStage interface. All variants share a target-routing front-end via the CamTargetMode enum documented in Stage Pipeline.
Aim does not implement state inheritance by design — inheritance is body-only. The destination cam’s aim runs fresh on the inherited body pose.

Contents
- Aim-Origin Convention
- Decoupled Ideal Rotation
- DefaultLookAtAim
- ClampedLookAim
- Extension Surface
- See Also
Aim-Origin Convention
A subtle but important rule: every Aim variant uses ctx.camInitialTM.GetTranslation() as the aim origin — the cam’s current world position at the start of the tick — not state.position (which the Body just wrote this tick).
Aiming from a position the cam hasn’t moved to yet would produce a frame-delayed look-at vector. Using camInitialTM keeps the aim ray geometry consistent with where the cam is actually rendered from.
Author your own aim variants the same way.
Decoupled Ideal Rotation
Same convention as the Body stage. Aim variants hold an internal m_idealRotation slerp working value, separate from state.rotation. Downstream rotation-modifying additives (Noise, Impulse, Tug aim listener) can perturb state.rotation without poisoning next tick’s slerp trajectory.
A m_lastValidRotation field preserves the last non-degenerate rotation for zero-forward recovery (the case where the cam ends up coincident with its look-at target and the forward vector collapses).
DefaultLookAtAim
Straightforward look-at. Aims the camera at a target (with optional offset), slerp-damped.
Kinematic model
lookAtPoint = targetTM.translation + ApplyOffset(offset, targetTM, isRelative)
state.lookAtPoint = lookAtPoint // sidecar — for downstream Reposition consumers
aimOrigin = ctx.camInitialTM.translation // NOT state.position
forward = (lookAtPoint - aimOrigin).GetNormalizedSafe()
if (forward.GetLengthSq() < epsilon):
targetRot = m_lastValidRotation // zero-forward recovery
else:
targetRot = Quaternion::CreateLookAt(forward, +Z)
m_lastValidRotation = targetRot
if (ctx.snapThisFrame || !m_hasIdeal):
m_idealRotation = targetRot
m_hasIdeal = true
else:
alpha = HalflifeAlpha(m_halflife, dt)
m_idealRotation = m_idealRotation.Slerp(targetRot, alpha)
state.rotation = m_idealRotation
Authored fields
| Field | Default | Purpose |
|---|---|---|
m_targetMode | Transform | Look-at target routing. |
m_overrideEntity | — | When m_targetMode == Override. |
m_groupTargetName | "" | When m_targetMode == GroupTarget. |
m_offset | (0, 0, 0) | Offset applied to look-at point. |
m_offsetIsRelative | false | World axes (false) or target-relative basis (true). |
m_halflife | 0.1 | Slerp halflife. |
ClampedLookAim
Aims at a target clamped within a pitch / yaw envelope relative to a starting forward captured on first evaluation. Replacement for the retired ClampedLook_PhantomCamComponent.
Used for first-person and constrained-look scenarios — e.g. the cam can rotate ±45° pitch and ±90° yaw from its initial facing, but not beyond.
Two clamp modes
bool m_localSpace;
| Mode | Behavior |
|---|---|
false (world-space, default) | Yaw clamps around world +Z, pitch clamps in the world-vertical plane. Yaw / pitch reconstruction from quaternion. |
true (local-space pivot) | Pivot relative to m_startingForward. Useful when the cam should rotate within a local envelope independent of world axes. |
Kinematic model (world-space mode)
On first Evaluate or ctx.snapThisFrame:
m_startingForward = ctx.camInitialTM.GetRotation() // capture once
lookAtPoint = targetTM.translation + offset
forward = (lookAtPoint - ctx.camInitialTM.translation).GetNormalizedSafe()
// Convert forward to yaw / pitch.
yaw = atan2(forward.y, forward.x)
pitch = asin(forward.z)
// Clamp to envelope (degrees → radians for comparison).
yaw = clamp(yaw, degToRad(m_minRelClamp.z), degToRad(m_maxRelClamp.z))
pitch = clamp(pitch, degToRad(m_minRelClamp.x), degToRad(m_maxRelClamp.x))
// Reconstruct quaternion from clamped angles.
clampedForward = (cos(pitch)*cos(yaw), cos(pitch)*sin(yaw), sin(pitch))
targetRot = LookAtQuaternion(clampedForward, +Z)
// Slerp damping with decoupled m_idealRotation.
state.rotation = Slerp(m_idealRotation, targetRot, HalflifeAlpha(m_halflife, dt))
Authored fields
| Field | Default | Purpose |
|---|---|---|
m_targetMode | Transform | Look-at target routing. |
m_overrideEntity / m_groupTargetName | — | Mode-dependent. |
m_offset | (0, 0, 0) | Look-at offset. |
m_offsetIsRelative | false | Basis. |
m_minRelClamp | (-45, 0, -90) | Lower bound — x = pitch min, z = yaw min. (Layout mirrors the legacy component for scene-data migration.) |
m_maxRelClamp | (45, 0, 90) | Upper bound — x = pitch max, z = yaw max. |
m_localSpace | false | Clamp mode (world-space or local-space pivot). |
m_halflife | 0.1 | Slerp halflife. |
Runtime state
| Field | Purpose |
|---|---|
m_startingForward | Captured on first Evaluate. The “origin” relative to which the clamp envelope is anchored. |
m_hasStartingForward | True after first capture. |
m_idealRotation | Decoupled slerp working value. |
m_hasIdeal | True after first ideal write. |
m_lastValidRotation | Last non-degenerate rotation, used for zero-forward recovery. |
Extension Surface
Other gems can ship additional aim stage variants. The editor’s type-picker on the cam’s m_aimSlot enumerates all derived IAimStage classes registered through SerializeContext. gs_performer, for example, registers aim stages that read performer head / eye tracking state.
Convention for authoring an aim variant in another gem:
- Header in
Code/Include/<GemName>/Stages/Variants/<MyAim>.h. - Source in
Code/Source/Stages/Variants/<MyAim>.cpp. - Use
AZ_RTTI(<MyAim>, "{UUID}", IAimStage)so the picker finds it. - Reflect with the
FindClassDataguard. - Honor the aim-origin convention — use
ctx.camInitialTM.translation, notstate.position. - Use the decoupled ideal rotation pattern if you want downstream Noise / Impulse / Tug aim listener to behave well.
See Also
- Stage Pipeline — per-tick orchestration, interfaces, conventions.
- Body Stage Variants
- Additive Stage Variants
- Group Targets —
CamTargetMode::GroupTargetresolution. - The Basics: Stage Composition Recipes
Get GS_PhantomCam
GS_PhantomCam — Explore this gem on the product page and add it to your project.