JM / LAB Work in progress

Graves Learning Adventures / A06

Build Piper's Camera System

Build first-person, third-person, rear-view, zoom, and world-locked camera behavior.

Journeys
4
Forges
14
Status
Available

Piper’s camera is not a single lens on a longer or shorter stick. It is a small state system with three durable views, one temporary rear view, two body presentations, and explicit rules for movement, rotation, collision, and return.

This Adventure builds that system on top of the accepted character lab. Work in PiperCharacterLab, preserve the imported movement and animation graphs, and compile after every Forge. By the end, C, X, V, and the mouse wheel will each have one clear job.

Before you begin, complete Adventure 5 — Establish Piper’s Character Lab. It creates the reusable BP_PlayerCharacterBase, its BP_PiperCharacter child, BP_PlayerControllerBase, L_PlayerSystems, the camera enum and variables, the input assets, and both starting camera rigs used below.

Journey 1 — Give First Person a Body

Forge 1 — Add the separate arms mesh

Open BP_PlayerCharacterBase. In the Components panel, select the Capsule Component, add a Skeletal Mesh component, and name it:

FirstPersonArms

It must be a sibling of the main Mesh and CameraBoom, not a child of either:

Capsule Component
├── Mesh
├── CameraBoom
│   └── FollowCamera
├── RearViewBoom
│   └── RearViewCamera
└── FirstPersonArms

First Person Arms Component

The reusable component is named for its role and remains a sibling of the character’s camera rigs and main Mesh.

Select the existing main Mesh and record its exact relative Location, Rotation, and Scale. Apply the same relative transform to FirstPersonArms, then assign the vendor SK_Arms from Content/Sample/Meshes.

Leave the arms visible just long enough to confirm that their shoulders and bones align with the full body. Then set:

SettingValue
Collision PresetsNoCollision
Only Owner SeeEnabled
Cast ShadowDisabled
First Person Primitive TypeFirst Person
VisibleDisabled

First Person Arms No Collision

The separate arms do not participate in collision.

First Person Arms Owner Only

Only the owning player’s Camera can see the first-person arms.

Select FollowCamera and enable its first-person rendering settings:

SettingValue
Enable First Person Field Of ViewEnabled
First Person Field Of View90
Enable First Person ScaleEnabled
First Person Scale0.6

First Person Camera Fov Scale

The shared Camera uses a dedicated first-person field of view and scale.

Compile and save.

The full body remains Piper’s world representation. The separate arms exist only for the owning player’s first-person presentation.

Forge 2 — Reuse the full-body animation

Open the Construction Script. Follow the vendor’s existing execution chain to its final executed node. Do not delete or reorganize the modular setup.

Append:

Vendor construction route
→ Set Leader Pose Component
    Target: FirstPersonArms
    New Leader Bone Component: Mesh
    Force Update: false
    In Follower Should Tick Pose: false

Leave the arms’ Animation Class set to None. They consume the main Mesh’s bone transforms instead of running a second Animation Blueprint.

Place only the appended nodes inside:

First-Person Arms Leader Pose

Compile and save.

Discovery — One animation, several modular pieces

Leader Pose lets a follower mesh reuse the leader’s evaluated bone transforms. That is lighter and safer here than duplicating locomotion logic for the arms. The skeletons must be compatible and their relative transforms must align.

Forge 3 — Create SetFirstPersonPresentation

Create a function in BP_PlayerCharacterBase:

SetFirstPersonPresentation

Add one Boolean input:

Enabled

Its contract is:

  • true: hide the full world body from the owning camera and show the separate arms.
  • false: restore the world body and hide the separate arms.

Build the white execution route:

SetFirstPersonPresentation(Enabled)
→ Mesh: Set Owner No See
    New Owner No See: Enabled
→ For Each Loop
    Loop Body
    → Cast Array Element to Primitive Component
    → Set Owner No See
        New Owner No See: Enabled
→ Completed
→ FirstPersonArms: Set Visibility
    New Visibility: Enabled
    Propagate to Children: true

Add Get Children Components as a pure data node:

Get Children Components
    Target: Mesh
    Include All Descendants: true
└──→ For Each Loop.Array

Connect the loop’s Array Element to the Cast’s Object data pin. Enabled feeds both Owner No See values and the arms’ New Visibility.

The recursive child loop matters because hair, clothing, body sections, and accessories sit beneath the imported main Mesh. The arms are a sibling, so they remain outside that loop.

Compile and save. The function will not affect Play until a camera transition calls it.

Trial — Verify the presentation function

  • Enabled drives both Owner No See setters.
  • Get Children Components includes all descendants.
  • Each child is cast to Primitive Component before Owner No See is called.
  • Loop Completed controls the arms’ Set Visibility.
  • Propagate to Children is enabled.
  • The Blueprint compiles.

Journey 2 — Build the Shared First/Third-Person Camera

Forge 4 — Create the two durable shared-camera modes

Create EnterFirstPerson:

EnterFirstPerson
→ CameraMode = FirstPerson
→ DesiredArmLength = 0

Do not change body visibility here. The camera will move smoothly, and the Event Tick will change presentation only when it crosses the visual threshold.

Create EnterFreeThirdPerson:

EnterFreeThirdPerson
→ WasFirstPerson = (CameraMode == FirstPerson)
→ CameraMode = FreeThirdPerson
→ DesiredArmLength =
    Select(
        WasFirstPerson
        ? ThirdPersonEntryDistance
        : Clamp(DesiredArmLength,
                ThirdPersonMinimumDistance,
                ThirdPersonMaximumDistance)
    )
→ Use Controller Rotation Yaw = false
→ Character Movement: Orient Rotation to Movement = true
→ Character Movement: Rotation Rate = (Roll 0, Pitch 0, Yaw 500)

Capture WasFirstPerson before overwriting CameraMode. Leaving first person requests the noticeable 240-centimeter entry distance. Returning from another third-person state preserves the existing desired distance, clamped between 180 and 650.

Forge 5 — Align steering before special views

Create:

AlignControllerYawToActor

The function entry executes Set Control Rotation. Build its New Rotation data separately:

Get Actor Rotation
→ Break Rotator
→ Make Rotator
    Roll: 0
    Pitch: 0
    Yaw: Actor Yaw
└──→ Set Control Rotation.New Rotation

Get Controller.Return Value
└──→ Set Control Rotation.Target

The rotation, Break, Make, and Controller getter nodes are pure; only Set Control Rotation receives the function’s white execution wire.

Rear and locked views let mouse yaw steer Piper. Aligning the controller to the actor first prevents a stale controller heading from snapping her when that steering policy begins.

Forge 6 — Build the temporary rear view

Create EnterRearView:

EnterRearView
→ RearViewSourceMode = CameraMode
→ RearViewActive = true
→ SetFirstPersonPresentation(false)
→ FirstPersonVisualsActive = false
→ FollowCamera: Set Active(false)
→ RearViewCamera: Set Active(true)
→ AlignControllerYawToActor
→ Use Controller Rotation Yaw = true
→ Character Movement: Orient Rotation to Movement = false

Leave the Camera Component Reset inputs disabled.

Create ExitRearView:

ExitRearView
→ RearViewCamera: Set Active(false)
→ FollowCamera: Set Active(true)
→ RearViewActive = false
→ Switch on RearViewSourceMode

Complete the legal return paths:

FirstPerson
→ SetFirstPersonPresentation(true)
→ FirstPersonVisualsActive = true
→ Use Controller Rotation Yaw = true
→ Orient Rotation to Movement = false

FreeThirdPerson
→ SetFirstPersonPresentation(false)
→ FirstPersonVisualsActive = false
→ Use Controller Rotation Yaw = false
→ Orient Rotation to Movement = true

Leave LockedThirdPerson unconnected. Rear view cannot begin there.

Create ToggleRearView:

if RearViewActive:
    ExitRearView
else if CameraMode != LockedThirdPerson:
    EnterRearView

Create ExitRearViewIfActive:

if RearViewActive:
    ExitRearView

Exit Rear View If Active

This helper leaves an inactive camera alone and exits rear view only when that overlay is actually active.

Toggle Rear View

ToggleRearView exits an active rear view or enters it only from an eligible shared-camera mode.

Rear view is an overlay state. It remembers whether it came from first or free third person without changing CameraMode.

Forge 7 — Connect C and X

Create TogglePrimaryCameraMode:

TogglePrimaryCameraMode
→ ExitRearViewIfActive
→ Switch on CameraMode
    FirstPerson       → EnterFreeThirdPerson
    FreeThirdPerson   → EnterFirstPerson
    LockedThirdPerson → no connection

Primary Camera Toggle Safe Exit

C safely closes an active rear-view overlay before switching between First Person and Free Third Person.

In BP_PlayerControllerBase, add two Enhanced Action Event routes. Use Started, not Triggered. Place one Get Controlled Pawn getter for each route and connect its Return Value to the Cast’s Object pin:

IA_TogglePrimaryCamera — Started
→ Cast to BP_PlayerCharacterBase
→ TogglePrimaryCameraMode

IA_ToggleRearView — Started
→ Cast to BP_PlayerCharacterBase
→ ToggleRearView

Compile and save both Blueprints.

Forge 8 — Build mouse-wheel zoom

Create a function:

HandleCameraZoom

Add one Float input:

AxisValue

Begin by calling ExitRearViewIfActive, then route positive and negative values separately. Zero does nothing.

For AxisValue > 0:

Switch on CameraMode

FreeThirdPerson:
    if DesiredArmLength > ThirdPersonMinimumDistance:
        DesiredArmLength =
            Clamp(
                DesiredArmLength - ZoomInStep,
                ThirdPersonMinimumDistance,
                ThirdPersonMaximumDistance
            )
    else:
        EnterFirstPerson

FirstPerson:
    no connection

LockedThirdPerson:
    no connection

For AxisValue < 0:

Switch on CameraMode

FirstPerson:
    EnterFreeThirdPerson

FreeThirdPerson:
    OutwardStep =
        MapRangeClamped(
            DesiredArmLength,
            180, 650,
            80, 25
        )

    DesiredArmLength =
        Clamp(
            DesiredArmLength + OutwardStep,
            180,
            650
        )

LockedThirdPerson:
    no connection

The changing outward step makes the camera move away quickly when it is close to Piper and more delicately near its maximum distance.

In BP_PlayerControllerBase, connect Get Controlled Pawn.Return Value to the Cast’s Object data pin, then build:

IA_CameraZoom — Triggered
→ Cast to BP_PlayerCharacterBase
→ HandleCameraZoom
    AxisValue: Action Value

IA_CameraZoom is Axis1D, so its Action Value is already a Float. Do not add a Break or conversion node.

If the physical wheel direction is reversed on the test machine, add a Negate modifier to the Mouse Wheel Axis mapping. Keep one graph contract instead of rewiring the behavior.

Camera Zoom

The complete zoom function leaves rear view first, then handles inward and outward requests by camera mode.

Forge 9 — Change body presentation at the real threshold

Return to the Event Tick route that interpolates CameraBoom.TargetArmLength.

After Set Target Arm Length, continue only when rear view is inactive:

Set Target Arm Length
→ Branch
    Condition: NOT RearViewActive
    True:
        Switch on CameraMode

Read the boom’s current Target Arm Length—the interpolated physical value, not DesiredArmLength.

For FirstPerson:

if CurrentArmLength <= FirstPersonVisualThreshold
and FirstPersonVisualsActive == false:
    SetFirstPersonPresentation(true)
    Orient Rotation to Movement = false
    Use Controller Rotation Yaw = true
    FirstPersonVisualsActive = true

Keep that order. Hide the world body before changing rotation policy so Piper does not visibly snap toward the camera immediately before the lens enters her head position.

For FreeThirdPerson:

if CurrentArmLength >= FirstPersonVisualThreshold
and FirstPersonVisualsActive == true:
    SetFirstPersonPresentation(false)
    FirstPersonVisualsActive = false

Leave LockedThirdPerson unconnected. Rear view is guarded because its entry and exit functions explicitly own presentation.

Rear View Threshold Guard

The presentation threshold is suspended while rear view owns the character’s visible state.

First Person Presentation Threshold

The interpolated Spring Arm length determines when the world body and first-person arms exchange visibility.

Trial — Cross the shared-camera boundary

Test in a fresh Play session:

  • The camera begins in Free Third Person around 360 centimeters away.
  • Scrolling inward reaches 180; the next inward notch enters First Person.
  • Leaving First Person requests 240 centimeters.
  • The world body disappears only after the camera reaches the threshold.
  • The separate arms appear and animate while walking and jumping.
  • Returning outward restores the world body and hides the arms.
  • C alternates only First Person and Free Third Person.
  • X enters and exits rear view from either mode and restores the exact source presentation.
  • C or the mouse wheel safely leaves rear view before continuing.

The arms are a functional baseline. Their final composition can be refined later without changing the camera state contract.

Journey 3 — Prove Free-Camera Collision

Forge 10 — Keep the Spring Arm contract intact

Select CameraBoom and reconfirm:

SettingValue
Do Collision TestEnabled
Probe Size12
Probe ChannelCamera
Camera LagDisabled
Camera Rotation LagDisabled

Spring Arm Collision Settings

The Spring Arm tests against the Camera channel with the accepted probe size.

Camera Lag Disabled

Camera and rotation lag remain disabled so collision behavior can be judged directly.

Confirm that the lab’s walls, floors, stairs, platforms, and overhead forms use collision that blocks the Camera channel.

The Spring Arm may shorten its actual reach around geometry. It must not overwrite DesiredArmLength; that value records where the player wanted the camera before the obstruction.

Trial — Run the collision route

In Free Third Person:

  1. Back Piper toward a wall.
  2. Orbit until geometry would sit between Piper and the camera.
  3. Walk beneath and beside the spiral stairs.
  4. Approach the irregular elevated platforms from above and below.
  5. Zoom while an obstruction is forcing the boom inward.
  6. Move away from the obstruction.

Confirm:

  • The camera retracts instead of passing through geometry.
  • It returns toward the requested distance after the obstruction clears.
  • Collision-driven retraction does not trigger First Person.
  • The outward desired distance survives while the physical boom is compressed.
  • Movement, orbit, jump, and presentation remain correct.

Journey 4 — Add the World-Locked Camera

Forge 11 — Create BP_WorldLockedCamera

In Content/Graves/Characters/Player/Piper, create an Actor Blueprint:

BP_WorldLockedCamera

Add one Camera Component named LockedCamera, make it the root, and disable Auto Activate. Do not place a permanent instance in the Level. The PlayerController will spawn one reusable instance when needed.

Locked Camera Auto Activate Off

The world-locked Camera stays inactive until the PlayerController deliberately owns the view.

Forge 12 — Capture and own the locked view

In BP_PlayerControllerBase, create:

VariableTypeDefault
LockedCameraReferenceBP_WorldLockedCamera Object ReferenceNone
WorldLookInputLockedBooleanfalse

WorldLookInputLocked records that the controller is using the fixed world view. Do not call Set Ignore Look Input. Mouse yaw still needs to steer Piper; pitch will be gated in the character’s look graph.

Create ActivateWorldLockedCamera with a BP_PlayerCharacterBase Object Reference input named PlayerCharacter:

World Locked Input Details

The controller function receives the reusable player-character type through the role-based PlayerCharacter input.

ActivateWorldLockedCamera(PlayerCharacter)
→ WorldLookInputLocked = true
→ Set View Target with Blend
    New View Target: LockedCameraReference
    Blend Time: 0.35
    Blend Func: Ease In Out
    Blend Exp: 2.0
    Lock Outgoing: true
→ PlayerCharacter.CameraMode = LockedThirdPerson

Activate World Locked Camera Player Character

The activation route gives the locked Camera ownership of the view and records the durable camera mode on the supplied player character.

Create EnterWorldLockedCamera(PlayerCharacter):

  1. Get the Player Camera Manager.
  2. Read its current Camera Location and Camera Rotation.
  3. Test LockedCameraReference with Is Valid.
  4. On the valid path, move the existing locked-camera Actor to the captured transform, then call ActivateWorldLockedCamera(PlayerCharacter).
  5. On the invalid path, make a Transform from the captured location and rotation, spawn BP_WorldLockedCamera, store the return value in LockedCameraReference, then call the same activation function.

Use the shared activation function on both paths. Do not try to merge two execution wires into one node input.

Create ExitWorldLockedCamera(PlayerCharacter):

Branch on WorldLookInputLocked

True:
    WorldLookInputLocked = false
    → Set View Target with Blend to PlayerCharacter

False:
    Set View Target with Blend to PlayerCharacter

Use a separate blend node on each Branch path with the same 0.35, Ease In Out, exponent 2.0, and Lock Outgoing settings.

Forge 13 — Make movement and look camera-safe

In BP_PlayerCharacterBase, define the shared condition:

CameraOwnedHeading =
    (CameraMode == LockedThirdPerson)
    OR RearViewActive

Preserve the imported IA_Move event, Action Value connections, and two Add Movement Input calls.

For left/right World Direction, use a Vector Select:

Select stateVector
FalseExisting controller-yaw Right Vector
TrueGet Actor Right Vector

For forward/back World Direction:

Select stateVector
FalseExisting controller-yaw Forward Vector
TrueGet Actor Forward Vector

Use CameraOwnedHeading as the Select index.

In the imported look route:

IA_Look — Triggered
→ Add Controller Yaw Input
→ Branch
    Condition: NOT CameraOwnedHeading
    True:
        Add Controller Pitch Input
    False:
        no connection

Yaw stays live so the fixed or rear-facing shot can show Piper turning. Pitch is suppressed so an inactive camera cannot accumulate hidden tilt.

Camera Safe Movement

Movement selects controller-relative axes for player-controlled cameras and actor-relative axes for rear and locked views.

Camera Safe Look

Yaw remains active in every view, while pitch is blocked when rear or world-locked cameras own the shot.

Forge 14 — Enter and leave the locked mode exactly

Create EnterLockedThirdPerson:

EnterLockedThirdPerson
→ SetFirstPersonPresentation(false)
→ FirstPersonVisualsActive = false
→ Cast Controller to BP_PlayerControllerBase
→ EnterWorldLockedCamera(self)
→ AlignControllerYawToActor
→ Use Controller Rotation Yaw = true
→ Orient Rotation to Movement = false

Create EnterFirstPersonFromLocked:

EnterFirstPersonFromLocked
→ Cast Controller to BP_PlayerControllerBase
→ ExitWorldLockedCamera(self)
→ Set Timer by Function Name
    Object: self
    Function Name: EnterFirstPerson
    Time: LockedToFirstPersonOverlapDelay
    Looping: false
    Max Once Per Frame: false

The 0.03-second timer overlaps the return to Piper with the 0.35-second blend. The existing threshold logic still owns the moment when body presentation changes.

Create ExitLockedToFreeThirdPerson:

ExitLockedToFreeThirdPerson
→ Cast Controller to BP_PlayerControllerBase
→ ExitWorldLockedCamera(self)
→ EnterFreeThirdPerson

Create ApplyLockedCameraToggle:

if CameraMode != LockedThirdPerson:
    LockedCameraSourceMode = CameraMode
    EnterLockedThirdPerson
else:
    Switch on LockedCameraSourceMode
        FirstPerson      → EnterFirstPersonFromLocked
        FreeThirdPerson  → ExitLockedToFreeThirdPerson
        LockedThirdPerson → no connection

Create ToggleLockedThirdPerson:

ToggleLockedThirdPerson
→ ExitRearViewIfActive
→ ApplyLockedCameraToggle

Apply Locked Camera Toggle

The locked-camera toggle saves the source mode on entry and chooses the matching return path on exit.

Toggle Locked Third Person

The public toggle leaves rear view before applying the locked-camera transition.

In BP_PlayerControllerBase, connect Get Controlled Pawn.Return Value to the Cast’s Object data pin, then build:

IA_ToggleWorldLockedCamera — Started
→ Cast to BP_PlayerCharacterBase
→ ToggleLockedThirdPerson

Compile and save both Blueprints.

Final Trial — Run the camera contract

Begin a fresh Play session in L_PlayerSystems and deliberately mix the controls:

ControlContract
CToggle First Person ↔ Free Third Person; leave rear view first; do nothing while locked
XToggle rear view from First or Free; return to the saved source presentation
VLeave rear view if needed; enter world-locked view; return to the saved source mode
Mouse WheelLeave rear view if needed; perform the accepted inward/outward zoom; do nothing while locked

Confirm:

  • The lab starts in Free Third Person at the expected distance.
  • Repeated C presses alternate only First and Free Third Person.
  • X works from both eligible modes without floating first-person arms.
  • C, V, and the mouse wheel safely cancel rear view before continuing.
  • V captures the currently resolved view and leaves that Camera Actor stationary while Piper moves.
  • A second V returns to the exact mode from which locked view began.
  • Rear and locked movement use Piper’s actor axes.
  • Mouse yaw steers Piper in rear and locked views; pitch does not accumulate there.
  • Camera collision and desired zoom distance recover correctly after every transition.
  • No transition creates a duplicate locked-camera Actor.
  • No camera, body presentation, or rotation state remains stuck.
  • Both Blueprints compile and save, and Play produces no Blueprint runtime errors.

Runtime Free Third Person Video

Free Third Person follows Piper from behind while preserving a readable view of the test course.

Runtime First Person Video

First Person hides the third-person body while the dedicated first-person arm remains visible during movement.

Runtime Rear View

Rear view turns the shot toward Piper while movement and yaw continue to follow the rear-view contract.

Runtime World Locked Near Video

Runtime World Locked Far Video

Piper moves from the nearby platform to the distant steps while the world-locked camera remains fixed.

The camera system is complete when every view has an exact entry, an exact exit, and a return path that remembers where Piper came from. That is what makes the system reusable: not the number of cameras, but the clarity of its state.