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 }