Camera system¶
Assets/Scripts/Camera, namespace HoseBoy. Five components, plus the vendored
HelloMarioFramework.FreeLookHelper that owns the third-person rig. Cinemachine 3.1.6.
For the design rationale see Camera.
Partly unwired
CameraZone, FirstPersonLook and PlayerCameraDirector are present in
Scenes/demo.unity. FirstPersonBodyYaw, TrueFirstPersonBody and
PerspectiveShiftTransition are referenced from nowhere. Without FirstPersonBodyYaw the
body does not turn with the first-person view; without TrueFirstPersonBody the camera sits
inside the character's head.
PlayerCameraDirector¶
The single owner of which camera the player is looking through. [DefaultExecutionOrder(-20)],
ahead of the look and hose scripts that read the current mode. Exposes a static Instance and
Mode CurrentMode (FirstPerson / ThirdPerson).
Mode resolution¶
flowchart TD
Start[Refresh] --> D{default mode}
D --> Z{zoneCount > 0?}
Z -->|yes| TP[ThirdPerson]
Z -->|no| Keep[keep default]
TP --> Dlg{in dialogue and<br/>thirdPersonDuringDialogue?}
Keep --> Dlg
Dlg -->|yes| TP2[ThirdPerson]
Dlg -->|no| Target[target mode]
TP2 --> Target
Target --> C{changed since<br/>last applied?}
C -->|no| Stop[do nothing]
C -->|yes| S{transition available<br/>and not first apply?}
S -->|yes| Cover[shift.Play → Apply at cut point]
S -->|no| Apply[Apply immediately]
The first application deliberately does not play a transition — spawning into a town should not wipe the screen at the player.
What Apply does¶
One mode switch drives six things:
| Target | First person | Third person |
|---|---|---|
firstPersonRig.enabled |
on | off |
thirdPersonRig.enabled |
off | on |
FirstPersonLook.SetActive |
true | false |
TrueFirstPersonBody.SetFirstPerson |
true | false |
HoseArmAim.SetWeight |
firstPersonArmWeight (1) |
thirdPersonArmWeight (0.7) |
HoseAimReticle.SetSuppressed / crosshair |
reticle off, crosshair on | reticle on, crosshair off |
Leaving first person also calls AlignFreeLookBehindPlayer(), which parks the free-look rig's
axes at m_XAxis = 0 / m_YAxis = 0.5 so it comes back directly behind the player instead of
at whatever angle it was abandoned at.
Blend time¶
Awake reaches into Camera.main's CinemachineBrain and overrides DefaultBlend.Time — to
switchBlend (0.35) normally, or to 0 when a PerspectiveShiftTransition is assigned, since
a covered swap should be a hard cut. Cinemachine's default of 2 seconds is long enough to lose
your bearings in.
Dialogue coupling¶
Subscribes to DialogueManager.OnDialogueStartEvent / OnDialogueEndEvent in OnEnable and
forces third person for the duration when thirdPersonDuringDialogue is set. This is the only
coupling between the camera and dialogue systems, and it goes through static events, so neither
holds a reference to the other.
CameraZone¶
[RequireComponent(typeof(Collider))]. A trigger volume that requests third person while the
player is inside.
Counted, not toggled. OnTriggerEnter calls EnterZone() and OnTriggerExit calls
ExitZone(), and the director keeps a zoneCount, so a town built from several overlapping
boxes has no seams. Each zone guards with its own holding flag so a re-entry cannot
double-count, and OnDisable releases the count if the zone is switched off while occupied.
Player detection is other.GetComponentInParent<HelloMarioFramework.Player>() != null —
matched on the component, not a tag, because the player object in this project is untagged.
Reset sets the collider to trigger; OnDrawGizmosSelected draws the volume.
FirstPersonLook¶
[DefaultExecutionOrder(-5)]. Owns yaw and pitch and writes them straight onto the first-person
Cinemachine rig's transform.
The order is pinned between two things: BoneFollower poses the skeleton at −10, and
CinemachineBrain samples the rigs at 0. Reading the head bone earlier gives a stale pose;
writing the rig later costs a frame of lag.
- Eye position rides the head bone:
headBone.position + headBone.rotation * eyeOffset. Built from position and rotation only, neverTransformPoint, becauseTrueFirstPersonBodycollapses the head bone's scale to nearly zero andTransformPointwould drag the eye offset down with it. - Rotation is
Quaternion.Euler(pitch, yaw, 0), slerped a little towards the head bone's own rotation byanimationInfluence(0.12) so the walk has weight without being sickening. - One action, two devices.
lookActioncarries both a per-frame mouse pixel delta and a stick axis, which need completely different scaling. The component inspectsaction.activeControlto decide which the player last used, then applies eithermouseSensitivity(0.08 deg/unit) orstickSensitivity(220 deg/s ×deltaTime). - Handover continuity.
SetActive(true)seeds yaw and pitch fromCamera.main's current euler angles, so picking up first person never snaps to a different heading.
Public: IsActive, Yaw, Pitch, SetActive(bool).
FirstPersonBodyYaw¶
[DefaultExecutionOrder(200)], [RequireComponent(typeof(Rigidbody))]. Turns the body to match
where first person is looking, with body.MoveRotation in FixedUpdate.
Split out from FirstPersonLook because the two halves need opposite execution orders: the
camera must be written before CinemachineBrain reads it (−5), and the body must be turned
after Player.FixedUpdate (200), which otherwise steers the body towards whatever direction it
is walking.
TrueFirstPersonBody¶
[DefaultExecutionOrder(-3)] — after BoneFollower (−10), the look (−5) and the arm solve (−4).
Keeps the real animated character on screen in first person instead of swapping in a viewmodel.
The head is removed by collapsing its bone to hiddenScale (0.0001), not by disabling a
renderer, because the character is one skinned mesh and a renderer toggle would take the whole
body with it. extraHiddenBones handles anything else that pokes into view.
The scale is re-stamped every LateUpdate, since the animation rewrites the pose first. Original
scales are captured in Awake and restored on exit and on OnDisable.
It also disables the framework's HideNearCamera component while in first person — that shrinks
the whole player near the camera, which fights this directly. HideNearCamera is added by
Player.Start at runtime, so it is looked up lazily rather than serialised.
PerspectiveShiftTransition¶
A thin wrapper over the EasyTransitions plugin (Assets/Plugins/EasyTransitions).
Subscribes to TransitionManager.onTransitionCutPointReached, runs atCutPoint while the screen
is covered, and clears its running flag on onTransitionEnd. Returns false if it is already
running or there is no TransitionSettings asset / TransitionManager in the scene — which is
exactly what makes the director fall back to an uncovered swap.
Assign any TransitionSettings asset, for example EasyTransitions › Transitions › Fade.
FreeLookHelper (vendored)¶
Assets/HelloMarioFramework/Script/FreeLookHelper.cs, MIT, part of Hello Mario Framework. Owns
the CinemachineFreeLook third-person rig.
Relevant behaviour:
Awakeforces everyCinemachineBrainin the scene toUpdateMethods.LateUpdate. This is why the whole execution-order ladder is built around a brain at order 0 inLateUpdate.- Creates a
CameraFollowTargetdummy transform that lerps towards the player at 15/s, and aCameraLookAtchild positioned at the player's hat attach height. - Handles zoom (reshaping the three orbits along fitted parabolas), a centre-camera button, and
recentering options loaded from
OptionsSave. WarpCameraFixandVictoryZoomare framework features not currently used by hoseboy.