Move scripts under Assets/Scripts/ into Components, GameMode, Helper, Network, Enum, Struct and ScriptableObject folders. Pure file moves (plus folder .meta files); no code changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
117 lines
5.1 KiB
C#
117 lines
5.1 KiB
C#
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// One knob that resizes the whole player. Setting <see cref="Scale"/> drives <c>transform.localScale</c>
|
|
/// — which Unity already propagates to the visible mesh, the CharacterController capsule, the per-pose
|
|
/// capsules (written as local values) and the camera target — and additionally rescales the movement,
|
|
/// jump, slide and wall tunables on <see cref="PlayerCharacterController"/> proportionally, so a smaller
|
|
/// or larger player still moves at the same body-relative pace instead of zipping or crawling. Without
|
|
/// this the speeds (units/second, not geometry) stay put and a half-size player feels twice as fast.
|
|
///
|
|
/// The authored values on the controller ARE the scale-1 design values: tune them normally there. They are
|
|
/// captured as the base in Awake and multiplied by Scale at runtime, so the serialized design values are
|
|
/// never overwritten on disk. In the editor only <c>localScale</c> is previewed live (geometry is the only
|
|
/// visual part); the speed rescale happens on play. The skinned mesh collider scales correctly because
|
|
/// <see cref="SkinnedColliderBaker"/> divides the player scale back out of its bake.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
[RequireComponent(typeof(PlayerCharacterController))]
|
|
public class PlayerScaler : MonoBehaviour
|
|
{
|
|
[Tooltip("Uniform size multiplier for the whole player. 1 = authored size. Drives transform.localScale " +
|
|
"(mesh, CharacterController capsule, per-pose capsules and camera target follow automatically) " +
|
|
"and rescales the controller's movement/jump/slide/wall speeds proportionally.")]
|
|
[Min(0.01f)]
|
|
[SerializeField] private float scale = 1f;
|
|
|
|
[Tooltip("Also scale jump height, gravity and the wall top-out hop so the motion is geometrically " +
|
|
"self-similar: identical trajectory shape and air-time, just smaller/larger. Off keeps the " +
|
|
"authored gravity, giving a small player a floatier, relatively higher jump.")]
|
|
[SerializeField] private bool scaleJumpAndGravity = true;
|
|
|
|
/// <summary>Current uniform scale. Use <see cref="SetScale"/> to change it at runtime.</summary>
|
|
public float Scale => scale;
|
|
|
|
private PlayerCharacterController _pcc;
|
|
private bool _captured;
|
|
|
|
// Authored (scale-1) base values, snapshotted once from the controller in Awake. All are world-unit
|
|
// scalars (units, units/s, units/s^2) that do NOT follow transform.localScale; dimensionless ratios
|
|
// (sprint/climb multipliers), angular rates (rotation/turn speed) and times (exit grace) are left
|
|
// untouched — they are already scale-invariant.
|
|
private float _bMove, _bAccel, _bDecel, _bStick, _bSlideAcc, _bMaxSlide;
|
|
private float _bWallOffset, _bWallInset, _bWallReturn, _bWallSens, _bCeilGrab;
|
|
private float _bJump, _bGravity, _bTopOut;
|
|
|
|
void Awake()
|
|
{
|
|
_pcc = GetComponent<PlayerCharacterController>();
|
|
CaptureBase();
|
|
ApplyScale(scale);
|
|
}
|
|
|
|
/// <summary>Sets the scale at runtime (e.g. a grow/shrink mechanic) and re-applies from the base values.</summary>
|
|
public void SetScale(float newScale)
|
|
{
|
|
scale = Mathf.Max(0.01f, newScale);
|
|
if (!_pcc) _pcc = GetComponent<PlayerCharacterController>();
|
|
if (!_captured) CaptureBase();
|
|
ApplyScale(scale);
|
|
}
|
|
|
|
private void CaptureBase()
|
|
{
|
|
if (!_pcc) return;
|
|
_bMove = _pcc.moveSpeed;
|
|
_bAccel = _pcc.acceleration;
|
|
_bDecel = _pcc.deceleration;
|
|
_bStick = _pcc.groundedStickVelocity;
|
|
_bSlideAcc = _pcc.slideAcceleration;
|
|
_bMaxSlide = _pcc.maxSlideSpeed;
|
|
_bWallOffset = _pcc.wallMaxOffset;
|
|
_bWallInset = _pcc.wallMaxInset;
|
|
_bWallReturn = _pcc.wallOffsetReturnSpeed;
|
|
_bWallSens = _pcc.wallOffsetSensitivity;
|
|
_bCeilGrab = _pcc.ceilingGrabDistance;
|
|
_bJump = _pcc.jumpHeight;
|
|
_bGravity = _pcc.gravity;
|
|
_bTopOut = _pcc.wallTopOutBoost;
|
|
_captured = true;
|
|
}
|
|
|
|
private void ApplyScale(float s)
|
|
{
|
|
transform.localScale = Vector3.one * s;
|
|
if (!_pcc) return;
|
|
|
|
_pcc.moveSpeed = _bMove * s;
|
|
_pcc.acceleration = _bAccel * s;
|
|
_pcc.deceleration = _bDecel * s;
|
|
_pcc.groundedStickVelocity = _bStick * s;
|
|
_pcc.slideAcceleration = _bSlideAcc * s;
|
|
_pcc.maxSlideSpeed = _bMaxSlide * s;
|
|
_pcc.wallMaxOffset = _bWallOffset * s;
|
|
_pcc.wallMaxInset = _bWallInset * s;
|
|
_pcc.wallOffsetReturnSpeed = _bWallReturn * s;
|
|
_pcc.wallOffsetSensitivity = _bWallSens * s;
|
|
_pcc.ceilingGrabDistance = _bCeilGrab * s;
|
|
|
|
if (scaleJumpAndGravity)
|
|
{
|
|
_pcc.jumpHeight = _bJump * s;
|
|
_pcc.gravity = _bGravity * s;
|
|
_pcc.wallTopOutBoost = _bTopOut * s;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void OnValidate()
|
|
{
|
|
// Live geometry preview in edit mode only; the speed rescale runs at play time from the authored
|
|
// base so the design values on the controller are never serialized in their scaled form.
|
|
if (!Application.isPlaying)
|
|
transform.localScale = Vector3.one * Mathf.Max(0.01f, scale);
|
|
}
|
|
#endif
|
|
}
|