Docs: reduce over-documentation across gameplay scripts

Trim restated narration, dead commented-out code, and oversized class
headers; keep all why-comments (authority, timing, edge cases), tooltips,
and serialization caveats.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5 committed 2026-07-02 10:08:28 +02:00
1 parent 882d30797e
commit 066e308cde
21 files changed
+478 -902

No files matched your search

+36 -65
View File
@@ -8,26 +8,16 @@ using UnityEngine.InputSystem;
#endif
/// <summary>
/// Sits next to the <see cref="PlayerCharacterController"/> and holds a list of <see cref="PlayerPose"/>s
/// plus the active index. The pose system owns all character animation — the Animator is left empty —
/// so it drives locomotion itself: standing still (or airborne / on a wall / on the ceiling) holds the
/// active pose's idle clip, walking on the ground plays that pose's optional walk clip, and sprinting
/// on the ground plays its optional run clip. Either locomotion clip is optional; when a pose leaves
/// one null, the idle pose is simply held in that state.
/// Holds a list of <see cref="PlayerPose"/>s plus the active index. The pose system owns ALL character
/// animation (the Animator is left empty): standing / airborne / wall / ceiling holds the active pose's
/// idle clip, walking/sprinting on the ground plays its optional walk/run clip (unset = idle held).
///
/// One key cycles to the next pose (wrapping), and sprinting in any state (ground, air, wall, ceiling)
/// snaps the selection back to the default pose. The selection is held continuously, so cycling or the
/// sprint reset shows up immediately instead of only once the player comes to a stop.
///
/// Clips are played through a tiny <see cref="PlayableGraph"/> (a two-input
/// <see cref="AnimationMixerPlayable"/> on an <see cref="AnimationPlayableOutput"/>) that drives the
/// Animator directly. Switching clip — between poses, or between idle/walk/run within a pose —
/// crossfades the outgoing and incoming clips over <see cref="poseBlendDuration"/> instead of snapping.
/// Each pose also carries its own CharacterController capsule (center Y / radius / height): the active
/// pose's dimensions are eased toward and written through the controller so a low pose (ball, star) can
/// crawl under furniture the default standing capsule is too tall for. A per-pose camera y-offset is
/// likewise eased and routed to the controller's camera target, framing each pose for the
/// ThirdPersonCamera without moving the body.
/// One key cycles poses (wrapping); sprinting in any state snaps back to the default pose, held
/// continuously so the reset shows up immediately. Clips play through a tiny <see cref="PlayableGraph"/>
/// (two-input mixer on an <see cref="AnimationPlayableOutput"/>) that crossfades every clip switch over
/// <see cref="poseBlendDuration"/>. Each pose also carries its own CharacterController capsule (eased,
/// so a low pose can crawl under furniture) and per-pose camera y-offsets (third-person framing and
/// first-person eye).
/// </summary>
[RequireComponent(typeof(PlayerCharacterController))]
[DisallowMultipleComponent]
@@ -83,11 +73,9 @@ public class PlayerPoseController : MonoBehaviour
/// <summary>Locomotion category that selects which clip the active pose plays.</summary>
public enum Locomotion { Idle, Walk, Run, Fall }
// Network drive: a remote avatar's PlayerCharacterController and CharacterController are disabled
// (NetworkTransform owns its transform), so its movement-state flags are dead. Instead of reading
// local input/physics, such an avatar takes its pose index and locomotion category from the owner
// via SetNetworkState (pushed by PlayerAnimationSync). _currentLocomotion is the category resolved
// this frame, published by the owner for replication.
// Network drive: a remote avatar's controllers are disabled (NetworkTransform owns the transform),
// so its movement flags are dead - pose index + locomotion come from the owner via SetNetworkState.
// _currentLocomotion is the category resolved this frame, published by the owner for replication.
private bool _networkControlled;
private Locomotion _networkLoco;
private Locomotion _currentLocomotion;
@@ -118,13 +106,10 @@ public class PlayerPoseController : MonoBehaviour
private float _capCenterY, _capRadius, _capHeight;
private float _capBlend;
// Rest pose snapshot of the whole Animator subtree, taken in Awake before the Animator first
// evaluates. An AnimationClipPlayable only writes the bones its curves cover, so a clip that
// doesn't key a bone (an empty clip like the T-pose, or a partial one) would otherwise leave that
// bone frozen on the previous animation. Restoring this before each pose start makes every pose
// define the full body. The controller's model-offset transform (meshOffsetTarget) is explicitly
// dropped from this set in CaptureRestPose — with the humanoid rig the Animator sits on that node,
// so it is the root of this subtree, and resetting it would wipe the wall-lean / ground raise.
// Rest-pose snapshot of the Animator subtree, taken before the Animator first evaluates. A clip only
// writes the bones its curves cover, so un-keyed bones would stay frozen on the previous animation;
// restoring rest first makes every pose define the full body. The controller's meshOffsetTarget is
// dropped from the set (see CaptureRestPose) so the wall-lean / ground raise is never wiped.
private Transform[] _restBones;
private Vector3[] _restPos;
private Quaternion[] _restRot;
@@ -175,11 +160,10 @@ public class PlayerPoseController : MonoBehaviour
void Update()
{
// Resolution freeze: hold the currently-evaluated pose. Keep the graph playing (so it re-asserts the
// frozen frame every render, even under Animator culling) but zero the mixer's speed so clip time
// stops advancing, and skip everything below so no locomotion/rest-reset moves a bone. The per-clip
// speeds PlayPose writes onto the mixer INPUTS are unaffected; only the mixer's own multiplier is
// zeroed here and restored on the first un-frozen frame just below.
// Resolution freeze: keep the graph playing (it re-asserts the frozen frame every render, even
// under Animator culling) but zero the MIXER's speed so clip time stops advancing; skip everything
// below so no locomotion/rest-reset moves a bone. The per-input clip speeds are untouched; the
// mixer multiplier is restored on the first un-frozen frame just below.
if (Frozen)
{
if (_graph.IsValid())
@@ -192,8 +176,7 @@ public class PlayerPoseController : MonoBehaviour
if (_mixer.IsValid() && PlayableExtensions.GetSpeed(_mixer) == 0d)
PlayableExtensions.SetSpeed(_mixer, 1d);
// Local control: the cycle key and the sprint reset choose the pose. A network-driven (remote)
// avatar skips this — its pose index is pushed in from the owner via SetNetworkState.
// Local control only - a remote's pose index is pushed in via SetNetworkState.
if (!_networkControlled)
{
if (CycleKeyPressed())
@@ -207,12 +190,9 @@ public class PlayerPoseController : MonoBehaviour
PlayerPose pose = ActivePose();
// Resolve the locomotion category (idle / walk / run / fall) and pick its clip. Locally the
// category comes from the controller's movement state; on a remote avatar it is the replicated
// value. It is stored so the owner's PlayerAnimationSync can publish it. ClipFor falls back to
// the idle clip for an unset optional clip, so a remote avatar (same pose asset) resolves to
// exactly the clip the owner shows; PlayPose crossfades on any change and hands the rig back
// only when there is no clip at all (empty list / null idle clip).
// Resolve the locomotion category and its clip: locally from the controller's movement state, on
// a remote the replicated value. ClipFor falls back to idle for unset optional clips, so a remote
// (same pose asset) resolves to exactly the clip the owner shows.
_currentLocomotion = _networkControlled ? _networkLoco : ResolveLocalLocomotion();
AnimationClip clip = ClipFor(pose, _currentLocomotion, out float speed);
PlayPose(clip, speed);
@@ -222,10 +202,8 @@ public class PlayerPoseController : MonoBehaviour
if (_networkControlled)
return;
// CharacterController capsule: each pose carries its own center Y / radius / height so a low pose
// (ball, star) can crawl where the default standing capsule is too tall. Eased toward the active
// pose's dimensions when the pose changes so the collider resizes smoothly instead of popping; a
// pose that leaves them unset (height/radius <= 0) keeps the default capsule.
// Per-pose collision capsule so a low pose can crawl where the standing capsule is too tall;
// unset dimensions (<= 0) keep the default capsule.
bool hasCapsule = pose && pose.ControllerHeight > 0f && pose.ControllerRadius > 0f;
float capCenterY = hasCapsule ? pose.ControllerCenterY : _player.BaseCapsuleCenterY;
float capRadius = hasCapsule ? pose.ControllerRadius : _player.BaseCapsuleRadius;
@@ -240,9 +218,8 @@ public class PlayerPoseController : MonoBehaviour
: cameraPoseTarget;
_player.SetCameraTargetLocalYOffset(_cameraPoseOffset);
// First-person eye: a separate per-pose offset, eased the same way but NOT routed to the shared
// camera target (that would also move the ThirdPersonCamera). The FirstPersonController reads this
// and applies it to its own eye position only.
// Separate per-pose first-person eye offset - NOT routed to the shared camera target (that would
// also move the ThirdPersonCamera); the FirstPersonController reads it directly.
float firstPersonTarget = pose ? pose.FirstPersonCameraTargetYOffset : 0f;
_firstPersonCameraOffset = cameraOffsetSpeed > 0f
? Mathf.MoveTowards(_firstPersonCameraOffset, firstPersonTarget, cameraOffsetSpeed * Time.deltaTime)
@@ -364,7 +341,7 @@ public class PlayerPoseController : MonoBehaviour
private bool CycleKeyPressed()
{
if (TypingGuard.IsTyping) return false; // don't cycle pose while typing in a text field
if (TypingGuard.IsTyping) return false;
#if ENABLE_INPUT_SYSTEM
var keyboard = Keyboard.current;
return keyboard != null && keyboard[cycleKey].wasPressedThisFrame;
@@ -396,16 +373,13 @@ public class PlayerPoseController : MonoBehaviour
AdvanceBlend(Time.deltaTime);
// Set every frame (not just on clip change) so tuning a pose's speed slider live in the inspector
// takes effect immediately and re-selecting the same clip keeps its speed. The outgoing pose on
// input 0 keeps the speed it carried, so a crossfade fades it out at its own pace.
// Set every frame so live speed-slider tuning takes effect immediately; the outgoing pose on
// input 0 keeps the speed it carried, fading out at its own pace.
if (_toPlayable.IsValid())
PlayableExtensions.SetSpeed(_toPlayable, speed);
// Snap un-keyed bones to rest every frame, so each mixer input blends relative to rest: a
// partial or empty clip (e.g. the T-pose) defines the whole body, and a crossfade interpolates
// both poses against the same baseline. The graph overrides the keyed bones, weighted, when it
// evaluates later this frame.
// Snap un-keyed bones to rest every frame so each mixer input blends relative to the same
// baseline; the graph then overrides the keyed bones when it evaluates later this frame.
ResetToRestPose();
if (!_graph.IsPlaying())
@@ -516,12 +490,9 @@ public class PlayerPoseController : MonoBehaviour
_restScale[i] = t.localScale;
}
// Drop the model-offset transform from the reset set so the per-frame ResetToRestPose never
// touches it: the wall-lean / ground raise (PlayerCharacterController.SetMeshLean) is its sole
// writer. With the humanoid rig the Animator shares this node, putting it in the captured subtree;
// resetting it each frame is exactly what was wiping the offsets. Nulling the slot makes
// ResetToRestPose's null-guard skip it. _player is resolved (and meshOffsetTarget too, since the
// controller's Awake runs first at its lower execution order) before this runs.
// Drop the model-offset transform from the reset set: the wall-lean / ground raise (SetMeshLean)
// is its sole writer, and with the humanoid rig the Animator shares this node - resetting it each
// frame was exactly what wiped the offsets. Nulling the slot makes ResetToRestPose skip it.
Transform offsetTarget = _player ? _player.MeshOffsetTarget : null;
if (offsetTarget)
{