From 62cf0eae04d150eb120075ce50b5d8b3db49617f Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Sun, 28 Jun 2026 21:39:41 +0200 Subject: [PATCH] Scale third-person orbit zoom range and default by player size The orbit radius (min/max/default and per-notch step) was in absolute world units and ignored the player's localScale, so a small Hider started too far out and a large Hunter too close and could not zoom out far enough. Multiply range, step and default radius by the follow target's lossyScale (PlayerScaler keeps the body uniformly scaled), making the inspector fields size-1 design values that frame every role body-relative. Re-clamp every frame so a runtime grow/shrink stays in range. GameModeManager.BindLocalPlayer calls ResetZoom after rebinding so each new avatar starts at its scaled default zoom. Co-Authored-By: Claude Opus 4.8 --- Assets/Scripts/GameModeManager.cs | 10 ++++- Assets/Scripts/ThirdPersonOrbitInput.cs | 56 +++++++++++++++++++++---- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/Assets/Scripts/GameModeManager.cs b/Assets/Scripts/GameModeManager.cs index 9cf7597..21f3113 100644 --- a/Assets/Scripts/GameModeManager.cs +++ b/Assets/Scripts/GameModeManager.cs @@ -194,7 +194,15 @@ public class GameModeManager : MonoBehaviour if (cameraInput) { var vcam = cameraInput.GetComponent(); - if (vcam) { vcam.Follow = camTarget; vcam.LookAt = camTarget; } + if (vcam) + { + vcam.Follow = camTarget; + vcam.LookAt = camTarget; + // Follow is now the new avatar's target, so its lossyScale is live - start the camera at this + // avatar's scaled default zoom (Hider pulls in, Hunter pushes out) instead of inheriting the + // previous avatar's radius. + cameraInput.ResetZoom(); + } } if (firstPerson) diff --git a/Assets/Scripts/ThirdPersonOrbitInput.cs b/Assets/Scripts/ThirdPersonOrbitInput.cs index 514f5ea..b05cdd3 100644 --- a/Assets/Scripts/ThirdPersonOrbitInput.cs +++ b/Assets/Scripts/ThirdPersonOrbitInput.cs @@ -45,18 +45,29 @@ public class ThirdPersonOrbitInput : MonoBehaviour [Tooltip("Mouse wheel zooms the camera in and out (changes orbit radius).")] public bool enableZoom = true; - [Tooltip("World units the orbit radius changes per mouse wheel notch.")] + [Tooltip("Scale the zoom range, step and default radius by the follow target's size (PlayerScaler), " + + "so a small (Hider) or large (Hunter) player is framed at the same body-relative distance. " + + "The radius fields below are then size-1 design values, multiplied by the target's scale at " + + "runtime. Off = absolute world units regardless of player size.")] + public bool scaleWithTarget = true; + + [Tooltip("World units the orbit radius changes per mouse wheel notch (size-1 design value; scaled by " + + "the target when Scale With Target is on).")] public float zoomSpeed = 0.5f; [Tooltip("Eases the orbit radius toward the zoom target (higher = snappier, 0 = instant).")] public float zoomSmoothing = 10f; - [Tooltip("Closest orbit radius (zoom in).")] + [Tooltip("Closest orbit radius / zoom in (size-1 design value; scaled by the target).")] public float minRadius = 1.5f; - [Tooltip("Farthest orbit radius (zoom out).")] + [Tooltip("Farthest orbit radius / zoom out (size-1 design value; scaled by the target).")] public float maxRadius = 8f; + [Tooltip("Orbit radius applied when the camera (re)binds to a player - the starting zoom level " + + "(size-1 design value; scaled by the target). Set so the bound player is comfortably framed.")] + public float defaultRadius = 4.5f; + [Header("Cursor")] [Tooltip("Lock and hide the cursor while the camera is actively rotating.")] public bool manageCursor = true; @@ -91,6 +102,27 @@ public class ThirdPersonOrbitInput : MonoBehaviour void OnDisable() => SetCursorLocked(false); + /// Uniform scale of the current follow target (1 when none, or when Scale With Target is off). + /// The zoom range, step and default radius are multiplied by this so the framing is body-relative across + /// player sizes. PlayerScaler keeps the player uniformly scaled, so a single lossyScale component is exact. + float CurrentScale() + { + if (!scaleWithTarget || !_orbit) return 1f; + Transform t = _orbit.FollowTarget; + return t ? Mathf.Max(0.0001f, t.lossyScale.x) : 1f; + } + + /// Snaps the zoom to the scaled default radius. Called when a new avatar binds (see + /// GameModeManager.BindLocalPlayer) so each player starts at its body-relative default zoom instead of + /// inheriting the previous avatar's radius. + public void ResetZoom() + { + if (!_orbit) return; + _targetRadius = defaultRadius * CurrentScale(); + _orbit.Radius = _targetRadius; + _zoomInit = true; + } + void Update() { if (!_orbit) @@ -129,20 +161,30 @@ public class ThirdPersonOrbitInput : MonoBehaviour { if (!_zoomInit) { - _targetRadius = _orbit.Radius; + // Seed the starting zoom at the scaled default (not the live radius) so a fresh bind frames + // the player body-relative; ResetZoom() does the same on an explicit (re)bind. + _targetRadius = defaultRadius * CurrentScale(); + _orbit.Radius = _targetRadius; _zoomInit = true; } + float scale = CurrentScale(); + // Shift+wheel is reserved for the paint brush resize (MeshPainter), so the orbit camera // ignores the wheel while Shift is held - otherwise it would zoom and resize at once. float scroll = ShiftHeld() ? 0f : ReadScroll(); if (!Mathf.Approximately(scroll, 0f)) { - // One wheel notch = one zoom step (sign only), independent of the - // platform's raw wheel magnitude. Scrolling up zooms in. - _targetRadius = Mathf.Clamp(_targetRadius - Mathf.Sign(scroll) * zoomSpeed, minRadius, maxRadius); + // One wheel notch = one zoom step (sign only), independent of the platform's raw wheel + // magnitude. Scrolling up zooms in. Step scales with the player so the number of notches to + // cross the range is the same at every size. + _targetRadius -= Mathf.Sign(scroll) * zoomSpeed * scale; } + // Clamp every frame against the scaled range so a runtime size change (PlayerScaler grow/shrink) + // keeps the target in-range instead of sticking at a stale absolute bound. + _targetRadius = Mathf.Clamp(_targetRadius, minRadius * scale, maxRadius * scale); + // Ease the actual radius toward the target (frame-rate independent). _orbit.Radius = zoomSmoothing > 0f ? Mathf.Lerp(_orbit.Radius, _targetRadius, 1f - Mathf.Exp(-zoomSmoothing * Time.deltaTime))