Trim comments and use implicit null checks for Unity objects
Reduce comments to non-obvious "why" notes and convert UnityEngine.Object null checks to implicit bool (if (obj) / if (!obj)). Input System devices (Mouse.current, Keyboard.current) and plain arrays keep explicit != null. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
3da613e1a1
commit
f758c96dc5
8 files changed
+220
-427
No files matched your search
@@ -4,49 +4,36 @@ using UnityEngine.InputSystem;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Simple first/third-person style character controller driven by a
|
||||
/// <see cref="CharacterController"/>: WASD for horizontal movement and Space to jump.
|
||||
/// CharacterController-based player: camera-relative WASD movement, manual gravity
|
||||
/// and jumping, sliding off too-steep slopes/edges, and a wall-climb mode.
|
||||
///
|
||||
/// Movement can be camera-relative (W moves away from the camera) so it feels
|
||||
/// natural with the fixed scene camera. Gravity and jumping are integrated
|
||||
/// manually because CharacterController does not apply physics on its own.
|
||||
/// Wall-climb (entered on wall/ceiling contact) uses wall-relative controls: W
|
||||
/// moves toward the wall and climbs up once against it, S backs off and detaches,
|
||||
/// A/D strafe along the wall. Gravity and jumping are disabled while climbing;
|
||||
/// reaching the top edge mantles the character over onto the surface.
|
||||
///
|
||||
/// Touching a wall or ceiling enters a wall-climb mode with
|
||||
/// wall-relative controls: W moves slowly toward the wall and climbs up once
|
||||
/// fully against it, S backs slowly away and detaches once far enough, and A/D
|
||||
/// strafe left/right along the wall. Gravity and jumping are ignored while
|
||||
/// climbing. Climbing up past the top edge mantles the character over onto the
|
||||
/// surface instead of dropping back down.
|
||||
///
|
||||
/// The script reads input through the new Input System (Keyboard.current) when it
|
||||
/// is enabled, and falls back to the legacy Input Manager otherwise. The project
|
||||
/// currently runs the new Input System only.
|
||||
///
|
||||
/// Attach this to the Player GameObject (the one that owns the CharacterController).
|
||||
///
|
||||
/// Note: the class is intentionally named PlayerCharacterController to avoid a
|
||||
/// name clash with Unity.VisualScripting's example "PlayerController" type.
|
||||
/// Named PlayerCharacterController to avoid a clash with Unity.VisualScripting's
|
||||
/// example "PlayerController".
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
[DisallowMultipleComponent]
|
||||
public class PlayerCharacterController : MonoBehaviour
|
||||
{
|
||||
[Header("Movement")]
|
||||
[Tooltip("Horizontal movement speed in units per second.")]
|
||||
[Tooltip("Horizontal movement speed (units/second).")]
|
||||
public float moveSpeed = 4f;
|
||||
|
||||
[Tooltip("If ON, WASD is interpreted relative to the camera (W = away from " +
|
||||
"the camera). If OFF, WASD moves along the world X/Z axes.")]
|
||||
[Tooltip("Interpret WASD relative to the camera (off = world X/Z axes).")]
|
||||
public bool cameraRelative = true;
|
||||
|
||||
[Tooltip("Camera used for camera-relative movement. Empty = Camera.main.")]
|
||||
public Transform cameraTransform;
|
||||
|
||||
[Header("Rotation")]
|
||||
[Tooltip("If ON, the character smoothly turns to face its movement direction.")]
|
||||
[Tooltip("Smoothly turn the character to face its movement direction.")]
|
||||
public bool rotateTowardMovement = true;
|
||||
|
||||
[Tooltip("Turn speed in degrees per second (used when 'Rotate Toward Movement' is on).")]
|
||||
[Tooltip("Turn speed in degrees per second.")]
|
||||
public float rotationSpeed = 720f;
|
||||
|
||||
[Header("Jump & Gravity")]
|
||||
@@ -56,111 +43,93 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
[Tooltip("Gravity in units per second squared (negative = downward).")]
|
||||
public float gravity = -19.62f;
|
||||
|
||||
[Tooltip("Small downward velocity kept while grounded so the controller " +
|
||||
"stays firmly on the ground (and isGrounded stays reliable).")]
|
||||
[Tooltip("Small downward velocity kept while grounded for reliable ground contact.")]
|
||||
public float groundedStickVelocity = -2f;
|
||||
|
||||
[Header("Slope Sliding")]
|
||||
[Tooltip("If ON, the character slides down surfaces steeper than the " +
|
||||
"CharacterController's Slope Limit (and off sharp edges/corners) " +
|
||||
"instead of balancing on them.")]
|
||||
[Tooltip("Slide down surfaces steeper than the CharacterController's Slope Limit instead of balancing on them.")]
|
||||
public bool slideDownSteepSlopes = true;
|
||||
|
||||
[Tooltip("How quickly the slide accelerates while on a too-steep surface " +
|
||||
"(units per second squared).")]
|
||||
[Tooltip("How quickly the slide accelerates (units/second squared).")]
|
||||
public float slideAcceleration = 20f;
|
||||
|
||||
[Tooltip("Maximum downhill slide speed (units per second).")]
|
||||
[Tooltip("Maximum downhill slide speed (units/second).")]
|
||||
public float maxSlideSpeed = 8f;
|
||||
|
||||
[Tooltip("How much steering control the player keeps while sliding " +
|
||||
"(0 = none, 1 = full normal control).")]
|
||||
[Tooltip("Steering control kept while sliding (0 = none, 1 = full).")]
|
||||
[Range(0f, 1f)]
|
||||
public float slideControl = 0.3f;
|
||||
|
||||
[Header("Wall Climb")]
|
||||
[Tooltip("If ON, the character enters a wall-climb mode after touching a " +
|
||||
"wall or ceiling for a short time: it sticks to the surface, moves " +
|
||||
"at reduced speed and ignores gravity and jumping until contact is lost.")]
|
||||
[Tooltip("Enter wall-climb mode on wall/ceiling contact: stick to the surface, move at reduced speed, ignore gravity and jumping.")]
|
||||
public bool enableWallClimb = true;
|
||||
|
||||
[Tooltip("Minimum surface angle from 'up' (in degrees) for a contact to " +
|
||||
"count as a climbable wall or ceiling. Surfaces between the slope " +
|
||||
"limit and this angle stay (sliding) slopes; at or above this they " +
|
||||
"are treated as walls/ceilings.")]
|
||||
[Tooltip("Minimum surface angle from 'up' (degrees) that counts as a climbable wall/ceiling rather than a slope.")]
|
||||
[Range(50f, 180f)]
|
||||
public float wallMinAngle = 80f;
|
||||
|
||||
[Tooltip("Speed multiplier for climbing up (W when against the wall) and " +
|
||||
"strafing left/right (A/D) while wall-climbing (0.5 = half speed).")]
|
||||
[Tooltip("Speed multiplier for climbing up and strafing while wall-climbing (0.5 = half speed).")]
|
||||
[Range(0f, 1f)]
|
||||
public float wallClimbSpeedMultiplier = 0.5f;
|
||||
|
||||
[Tooltip("Speed (units per second) for moving toward (W) or away from (S) the " +
|
||||
"wall while climbing. Deliberately slow so approaching/detaching is gentle.")]
|
||||
[Tooltip("Slow speed for moving toward (W) or away from (S) the wall while climbing.")]
|
||||
public float wallApproachSpeed = 0.5f;
|
||||
|
||||
[Tooltip("Gap (in units) below which the character counts as fully against " +
|
||||
"the wall, so pressing forward (W) climbs up instead of moving closer.")]
|
||||
[Tooltip("Gap below which the character counts as fully against the wall (W then climbs up).")]
|
||||
public float wallContactDistance = 0.05f;
|
||||
|
||||
[Tooltip("Gap (in units) from the wall at which holding back (S) leaves " +
|
||||
"wall-climb mode.")]
|
||||
[Tooltip("Gap from the wall at which holding back (S) leaves wall-climb mode.")]
|
||||
public float wallDetachDistance = 0.25f;
|
||||
|
||||
[Tooltip("Grace period (seconds) of lost contact tolerated before leaving " +
|
||||
"wall-climb mode (prevents flicker on bumpy geometry/edges).")]
|
||||
[Tooltip("Grace period (seconds) of lost contact tolerated before leaving wall-climb mode.")]
|
||||
public float wallExitGrace = 0.15f;
|
||||
|
||||
/// <summary>
|
||||
/// When false, movement/jump input is ignored (the player stands still), but
|
||||
/// gravity and ground contact keep running so the character stays grounded.
|
||||
/// Toggled by the GameModeManager (e.g. disabled in paint mode).
|
||||
/// When false, movement/jump input is ignored but gravity and ground contact
|
||||
/// keep running. Toggled by the GameModeManager.
|
||||
/// </summary>
|
||||
public bool ControlEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>True while the character is in wall-climb mode (stuck to a wall/ceiling).</summary>
|
||||
/// <summary>True while in wall-climb mode.</summary>
|
||||
public bool IsWallClimbing => _isWallClimbing;
|
||||
|
||||
private CharacterController _controller;
|
||||
private float _verticalVelocity;
|
||||
|
||||
// Ground contact normal collected from the latest Move (used for slope sliding).
|
||||
// Ground contact normal from the latest Move (for slope sliding).
|
||||
private Vector3 _groundNormal = Vector3.up;
|
||||
private bool _groundNormalUpdated;
|
||||
private float _slideSpeed;
|
||||
|
||||
// Wall-climb state.
|
||||
private bool _isWallClimbing;
|
||||
private Vector3 _wallNormal; // surface normal of the climbed wall/ceiling
|
||||
private Vector3 _wallNormal;
|
||||
private Vector3 _wallNormalAccum; // summed wall normals during the latest Move
|
||||
private bool _wallContactThisFrame; // any wall/ceiling touched during the latest Move
|
||||
private float _lostContactTimer; // continuous lost-contact time while climbing
|
||||
private readonly RaycastHit[] _wallProbe = new RaycastHit[8]; // buffer for the wall-distance probe
|
||||
private bool _wallContactThisFrame;
|
||||
private float _lostContactTimer;
|
||||
private readonly RaycastHit[] _wallProbe = new RaycastHit[8];
|
||||
|
||||
// Mantle (top-out) state: climbing up past the wall's top edge pulls the
|
||||
// character up and over onto the surface instead of dropping back down.
|
||||
private bool _mantling; // a mantle is currently in progress
|
||||
private bool _mantleClearedFeet; // phase 1 done: whole capsule is above the edge
|
||||
private float _mantleOverDist; // phase 2 forward distance moved over the top
|
||||
private float _mantleTimer; // safety cap so a mantle can never run forever
|
||||
private float _reentryCooldown; // blocks re-grabbing the wall right after topping out
|
||||
// Mantle (top-out) state.
|
||||
private bool _mantling;
|
||||
private bool _mantleClearedFeet; // phase 1 done: capsule fully above the edge
|
||||
private float _mantleOverDist; // phase 2 forward distance over the top
|
||||
private float _mantleTimer; // safety cap
|
||||
private float _reentryCooldown; // blocks re-grabbing right after topping out
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_controller = GetComponent<CharacterController>();
|
||||
if (cameraTransform == null && Camera.main != null)
|
||||
if (!cameraTransform && Camera.main)
|
||||
cameraTransform = Camera.main.transform;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// --- Read input -----------------------------------------------------
|
||||
float h = 0f, v = 0f;
|
||||
bool jumpPressed = false;
|
||||
|
||||
// Skip input entirely when control is disabled (e.g. paint mode); gravity
|
||||
// and grounding below still run so the player stays planted on the ground.
|
||||
// Skip input when control is disabled (e.g. paint mode); gravity and
|
||||
// grounding below still run so the player stays planted.
|
||||
if (ControlEnabled)
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
@@ -180,13 +149,12 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
#endif
|
||||
}
|
||||
|
||||
// --- Build the horizontal move direction ----------------------------
|
||||
Vector3 inputDir = new Vector3(h, 0f, v);
|
||||
if (inputDir.sqrMagnitude > 1f)
|
||||
inputDir.Normalize();
|
||||
|
||||
Vector3 moveDir;
|
||||
if (cameraRelative && cameraTransform != null)
|
||||
if (cameraRelative && cameraTransform)
|
||||
{
|
||||
// Project the camera axes onto the ground plane so movement stays horizontal.
|
||||
Vector3 forward = cameraTransform.forward;
|
||||
@@ -202,14 +170,10 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
moveDir = inputDir;
|
||||
}
|
||||
|
||||
// --- Wall-climb state machine ---------------------------------------
|
||||
// Evaluates the wall/ceiling contact gathered during the previous Move,
|
||||
// then enters/leaves wall-climb mode accordingly.
|
||||
UpdateWallClimbState();
|
||||
|
||||
// Latch the wall normal for this frame's movement, then reset the
|
||||
// per-frame contact accumulators (OnControllerColliderHit refills them
|
||||
// during the Move calls below / above).
|
||||
// Latch the wall normal for this frame, then reset the per-frame contact
|
||||
// accumulators (OnControllerColliderHit refills them during Move).
|
||||
if (_wallContactThisFrame && _wallNormalAccum.sqrMagnitude > 1e-6f)
|
||||
_wallNormal = _wallNormalAccum.normalized;
|
||||
_wallContactThisFrame = false;
|
||||
@@ -217,64 +181,51 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
|
||||
if (_isWallClimbing)
|
||||
{
|
||||
// While climbing: wall-relative WASD, no gravity, no jump, no slope
|
||||
// sliding, no forced alignment. Done for this frame.
|
||||
ApplyWallClimbMovement(h, v, Time.deltaTime);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Evaluate the ground slope (for sliding) ------------------------
|
||||
// The ground normal is gathered in OnControllerColliderHit during the
|
||||
// previous Move. If it is steeper than the controller's Slope Limit, the
|
||||
// surface is too steep to stand on (this also covers balancing on a sharp
|
||||
// box edge/corner, whose contact normal is steep).
|
||||
// A ground normal steeper than the slope limit is too steep to stand on
|
||||
// (also covers balancing on a sharp box edge).
|
||||
float slopeAngle = Vector3.Angle(_groundNormal, Vector3.up);
|
||||
bool onSteepSlope = slideDownSteepSlopes
|
||||
&& _controller.isGrounded
|
||||
&& slopeAngle > _controller.slopeLimit + 0.01f;
|
||||
|
||||
// --- Gravity & jumping ---------------------------------------------
|
||||
if (_controller.isGrounded)
|
||||
{
|
||||
// Reset accumulated fall speed and keep a small downward bias on the ground.
|
||||
if (_verticalVelocity < 0f)
|
||||
_verticalVelocity = groundedStickVelocity;
|
||||
|
||||
// No jumping off a slope that is too steep to stand on.
|
||||
// No jumping off a slope too steep to stand on.
|
||||
if (jumpPressed && !onSteepSlope)
|
||||
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
|
||||
}
|
||||
|
||||
_verticalVelocity += gravity * Time.deltaTime;
|
||||
|
||||
// --- Slope sliding --------------------------------------------------
|
||||
Vector3 slideVelocity = Vector3.zero;
|
||||
if (onSteepSlope)
|
||||
{
|
||||
// Steepest-descent direction along the slope surface.
|
||||
Vector3 slideDir = Vector3.ProjectOnPlane(Vector3.down, _groundNormal).normalized;
|
||||
// Accelerate the slide up to the maximum slide speed.
|
||||
_slideSpeed = Mathf.Min(_slideSpeed + slideAcceleration * Time.deltaTime, maxSlideSpeed);
|
||||
slideVelocity = slideDir * _slideSpeed;
|
||||
// While sliding the player keeps only reduced steering control.
|
||||
moveDir *= slideControl;
|
||||
moveDir *= slideControl; // reduced steering while sliding
|
||||
}
|
||||
else
|
||||
{
|
||||
_slideSpeed = 0f;
|
||||
}
|
||||
|
||||
// --- Apply movement -------------------------------------------------
|
||||
Vector3 velocity = moveDir * moveSpeed + slideVelocity + Vector3.up * _verticalVelocity;
|
||||
|
||||
// Reset the per-frame ground-normal collection, then Move (which fires
|
||||
// OnControllerColliderHit), then fall back to "up" if nothing was hit.
|
||||
// Move fires OnControllerColliderHit, which refills the ground normal;
|
||||
// fall back to "up" if nothing was hit.
|
||||
_groundNormalUpdated = false;
|
||||
_controller.Move(velocity * Time.deltaTime);
|
||||
if (!_groundNormalUpdated)
|
||||
_groundNormal = Vector3.up;
|
||||
|
||||
// --- Rotate toward the movement direction ---------------------------
|
||||
if (rotateTowardMovement && moveDir.sqrMagnitude > 0.0001f)
|
||||
{
|
||||
Quaternion target = Quaternion.LookRotation(moveDir, Vector3.up);
|
||||
@@ -284,14 +235,11 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drives entry into wall-climb mode: any wall/ceiling contact enters the mode
|
||||
/// immediately (or bails out when the feature/control is disabled). Leaving the
|
||||
/// mode while climbing is handled in <see cref="ApplyWallClimbMovement"/>
|
||||
/// (raycast-based).
|
||||
/// Enters wall-climb mode on any wall/ceiling contact. Leaving while climbing
|
||||
/// is handled in <see cref="ApplyWallClimbMovement"/>.
|
||||
/// </summary>
|
||||
private void UpdateWallClimbState()
|
||||
{
|
||||
// Cool down the post-mantle re-grab lock (runs whether climbing or not).
|
||||
if (_reentryCooldown > 0f)
|
||||
_reentryCooldown -= Time.deltaTime;
|
||||
|
||||
@@ -302,37 +250,29 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// The cooldown prevents instantly re-grabbing the edge we just mantled over.
|
||||
if (!_isWallClimbing && _wallContactThisFrame && _reentryCooldown <= 0f)
|
||||
{
|
||||
// Any wall/ceiling contact enters wall-climb mode immediately. The
|
||||
// cooldown above prevents instantly re-grabbing the edge we just
|
||||
// mantled over (which would otherwise cause a climb/drop flicker).
|
||||
_isWallClimbing = true;
|
||||
_lostContactTimer = 0f;
|
||||
_verticalVelocity = 0f;
|
||||
_slideSpeed = 0f;
|
||||
}
|
||||
// While climbing, exit is decided in ApplyWallClimbMovement (intentional
|
||||
// back-off with S, or losing the wall after the grace period).
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the character along the climbed surface using wall-relative WASD:
|
||||
/// W moves slowly toward the wall and, once fully against it, climbs up;
|
||||
/// S moves slowly away and detaches once far enough; A/D strafe left/right
|
||||
/// along the wall. The current wall normal and gap are measured each frame by
|
||||
/// a raycast toward the surface. No gravity, no jump, no forced alignment.
|
||||
/// Wall-relative movement while climbing: W approaches the wall then climbs up,
|
||||
/// S backs off and detaches, A/D strafe along the wall. The wall normal and gap
|
||||
/// are measured each frame by a raycast. No gravity, jump or forced alignment.
|
||||
/// </summary>
|
||||
private void ApplyWallClimbMovement(float h, float v, float dt)
|
||||
{
|
||||
// A mantle (top-out) overrides normal climbing until it finishes.
|
||||
if (_mantling)
|
||||
{
|
||||
MantleOverEdge(dt);
|
||||
return;
|
||||
}
|
||||
|
||||
// Probe the wall ahead to refresh its normal and our distance to it.
|
||||
bool found = ProbeWall(out Vector3 probedNormal, out float gap);
|
||||
if (found)
|
||||
{
|
||||
@@ -341,9 +281,8 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
// The body-center ray lost the wall. If we were climbing up (W) and the
|
||||
// wall still exists down at the feet, we have reached the top edge:
|
||||
// mantle up and over onto the surface instead of dropping.
|
||||
// The center ray lost the wall. If we are climbing up (W) and the wall
|
||||
// still exists at foot level, we reached the top edge -> mantle over.
|
||||
if (v > 0f && WallInFront(_controller.height * 0.5f))
|
||||
{
|
||||
_mantling = true;
|
||||
@@ -354,7 +293,7 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise the wall is genuinely gone: leave after the grace period.
|
||||
// Wall genuinely gone: leave after the grace period.
|
||||
_lostContactTimer += dt;
|
||||
gap = wallDetachDistance + 1f; // treat as "far" so it cannot block exit
|
||||
if (_lostContactTimer >= wallExitGrace)
|
||||
@@ -364,31 +303,30 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// Build a basis on the wall plane: "up along the wall" and "right".
|
||||
// Basis on the wall plane: "up along the wall" and "right".
|
||||
Vector3 climbUp = Vector3.ProjectOnPlane(Vector3.up, _wallNormal);
|
||||
if (climbUp.sqrMagnitude < 1e-6f) // degenerate on a flat ceiling: fall back to facing
|
||||
if (climbUp.sqrMagnitude < 1e-6f) // degenerate on a flat ceiling
|
||||
climbUp = Vector3.ProjectOnPlane(transform.forward, _wallNormal);
|
||||
climbUp.Normalize();
|
||||
Vector3 right = Vector3.Cross(_wallNormal, climbUp).normalized;
|
||||
|
||||
float climbSpeed = moveSpeed * wallClimbSpeedMultiplier;
|
||||
|
||||
// A/D always strafe left/right along the wall.
|
||||
Vector3 velocity = right * (h * climbSpeed);
|
||||
Vector3 velocity = right * (h * climbSpeed); // A/D strafe
|
||||
|
||||
bool detach = false;
|
||||
if (v > 0f)
|
||||
{
|
||||
// W: move closer to the wall while there is a gap, otherwise climb up.
|
||||
// W: approach the wall while there is a gap, otherwise climb up.
|
||||
if (gap > wallContactDistance)
|
||||
velocity += -_wallNormal * (v * wallApproachSpeed); // slow approach
|
||||
velocity += -_wallNormal * (v * wallApproachSpeed);
|
||||
else
|
||||
velocity += climbUp * (v * climbSpeed); // climb up
|
||||
velocity += climbUp * (v * climbSpeed);
|
||||
}
|
||||
else if (v < 0f)
|
||||
{
|
||||
// S: slowly back away from the wall; detach once far enough.
|
||||
velocity += -_wallNormal * (v * wallApproachSpeed); // v < 0 => away from wall
|
||||
// S: back away (v < 0 => away from wall); detach once far enough.
|
||||
velocity += -_wallNormal * (v * wallApproachSpeed);
|
||||
if (gap >= wallDetachDistance)
|
||||
detach = true;
|
||||
}
|
||||
@@ -400,11 +338,8 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray from the character center toward the wall (opposite the wall
|
||||
/// normal) to find the current surface normal and the gap between the
|
||||
/// controller's side and the wall. The player's own colliders (controller and
|
||||
/// the paintable mesh collider) are skipped. Returns false if no wall is found
|
||||
/// within reach.
|
||||
/// Raycasts from the character center toward the wall to find the current
|
||||
/// normal and the gap to it. Our own colliders are skipped. False = no wall.
|
||||
/// </summary>
|
||||
private bool ProbeWall(out Vector3 normal, out float gap)
|
||||
{
|
||||
@@ -422,8 +357,7 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
bool found = false;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// Skip our own hierarchy (the CharacterController and the child mesh collider).
|
||||
if (_wallProbe[i].collider.transform.IsChildOf(transform))
|
||||
if (_wallProbe[i].collider.transform.IsChildOf(transform)) // skip our own colliders
|
||||
continue;
|
||||
if (_wallProbe[i].distance < best)
|
||||
{
|
||||
@@ -439,18 +373,15 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pulls the character up and over the wall's top edge onto the surface.
|
||||
/// Phase 1 rises along the wall face until the whole capsule is above the
|
||||
/// edge; phase 2 moves forward over the top, after which wall-climb is left
|
||||
/// and gravity settles the character onto the surface. A short re-grab
|
||||
/// cooldown is armed so the freshly passed edge does not re-trigger a climb.
|
||||
/// Pulls the character up and over the wall's top edge: phase 1 rises until the
|
||||
/// capsule clears the edge, phase 2 moves forward onto the surface, then exits.
|
||||
/// A short re-grab cooldown stops the passed edge from re-triggering a climb.
|
||||
/// </summary>
|
||||
private void MantleOverEdge(float dt)
|
||||
{
|
||||
_mantleTimer += dt;
|
||||
float speed = moveSpeed * wallClimbSpeedMultiplier;
|
||||
|
||||
// Forward direction over the edge: horizontal, opposite the wall normal.
|
||||
Vector3 forward = -_wallNormal;
|
||||
forward.y = 0f;
|
||||
forward = forward.sqrMagnitude > 1e-6f ? forward.normalized : transform.forward;
|
||||
@@ -475,7 +406,7 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: never mantle indefinitely (e.g. if blocked by other geometry).
|
||||
// Safety: never mantle indefinitely (e.g. blocked by other geometry).
|
||||
if (_mantleTimer >= 2f)
|
||||
{
|
||||
_reentryCooldown = 0.3f;
|
||||
@@ -484,9 +415,8 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Casts a horizontal ray toward the wall from a point <paramref name="downOffset"/>
|
||||
/// below the controller center, ignoring our own colliders. Used to tell a top
|
||||
/// edge (wall still present lower down) from the wall being truly gone.
|
||||
/// Horizontal ray toward the wall from a point below the center. Distinguishes a
|
||||
/// top edge (wall still present lower down) from the wall being truly gone.
|
||||
/// </summary>
|
||||
private bool WallInFront(float downOffset)
|
||||
{
|
||||
@@ -517,14 +447,11 @@ public class PlayerCharacterController : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects contact normals during <see cref="CharacterController.Move"/>.
|
||||
/// Wall/ceiling contacts (angle at or above <see cref="wallMinAngle"/>) feed
|
||||
/// the wall-climb detector; among the upward-facing contacts the flattest one
|
||||
/// is kept as the ground normal the character is actually standing on.
|
||||
/// Collects contact normals during Move: wall/ceiling contacts feed the
|
||||
/// wall-climb detector, the flattest upward contact becomes the ground normal.
|
||||
/// </summary>
|
||||
private void OnControllerColliderHit(ControllerColliderHit hit)
|
||||
{
|
||||
// Detect walls/ceilings for wall-climbing (steep enough to not be a slope).
|
||||
if (enableWallClimb)
|
||||
{
|
||||
float angle = Vector3.Angle(hit.normal, Vector3.up);
|
||||
|
||||
Reference in new issue
Block a user