using UnityEngine;
///
/// One knob that resizes the whole player. Setting drives transform.localScale
/// — 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 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 localScale is previewed live (geometry is the only
/// visual part); the speed rescale happens on play. The skinned mesh collider scales correctly because
/// divides the player scale back out of its bake.
///
[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;
/// Current uniform scale. Use to change it at runtime.
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();
CaptureBase();
ApplyScale(scale);
}
/// Sets the scale at runtime (e.g. a grow/shrink mechanic) and re-applies from the base values.
public void SetScale(float newScale)
{
scale = Mathf.Max(0.01f, newScale);
if (!_pcc) _pcc = GetComponent();
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
}