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 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-28 21:39:41 +02:00
1 parent 0834419a56
commit 62cf0eae04
2 files changed
+58 -8

No files matched your search

+9 -1
View File
@@ -194,7 +194,15 @@ public class GameModeManager : MonoBehaviour
if (cameraInput) if (cameraInput)
{ {
var vcam = cameraInput.GetComponent<CinemachineCamera>(); var vcam = cameraInput.GetComponent<CinemachineCamera>();
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) if (firstPerson)
+49 -7
View File
@@ -45,18 +45,29 @@ public class ThirdPersonOrbitInput : MonoBehaviour
[Tooltip("Mouse wheel zooms the camera in and out (changes orbit radius).")] [Tooltip("Mouse wheel zooms the camera in and out (changes orbit radius).")]
public bool enableZoom = true; 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; public float zoomSpeed = 0.5f;
[Tooltip("Eases the orbit radius toward the zoom target (higher = snappier, 0 = instant).")] [Tooltip("Eases the orbit radius toward the zoom target (higher = snappier, 0 = instant).")]
public float zoomSmoothing = 10f; 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; 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; 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")] [Header("Cursor")]
[Tooltip("Lock and hide the cursor while the camera is actively rotating.")] [Tooltip("Lock and hide the cursor while the camera is actively rotating.")]
public bool manageCursor = true; public bool manageCursor = true;
@@ -91,6 +102,27 @@ public class ThirdPersonOrbitInput : MonoBehaviour
void OnDisable() => SetCursorLocked(false); void OnDisable() => SetCursorLocked(false);
/// <summary>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.</summary>
float CurrentScale()
{
if (!scaleWithTarget || !_orbit) return 1f;
Transform t = _orbit.FollowTarget;
return t ? Mathf.Max(0.0001f, t.lossyScale.x) : 1f;
}
/// <summary>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.</summary>
public void ResetZoom()
{
if (!_orbit) return;
_targetRadius = defaultRadius * CurrentScale();
_orbit.Radius = _targetRadius;
_zoomInit = true;
}
void Update() void Update()
{ {
if (!_orbit) if (!_orbit)
@@ -129,20 +161,30 @@ public class ThirdPersonOrbitInput : MonoBehaviour
{ {
if (!_zoomInit) 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; _zoomInit = true;
} }
float scale = CurrentScale();
// Shift+wheel is reserved for the paint brush resize (MeshPainter), so the orbit camera // 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. // ignores the wheel while Shift is held - otherwise it would zoom and resize at once.
float scroll = ShiftHeld() ? 0f : ReadScroll(); float scroll = ShiftHeld() ? 0f : ReadScroll();
if (!Mathf.Approximately(scroll, 0f)) if (!Mathf.Approximately(scroll, 0f))
{ {
// One wheel notch = one zoom step (sign only), independent of the // One wheel notch = one zoom step (sign only), independent of the platform's raw wheel
// platform's raw wheel magnitude. Scrolling up zooms in. // magnitude. Scrolling up zooms in. Step scales with the player so the number of notches to
_targetRadius = Mathf.Clamp(_targetRadius - Mathf.Sign(scroll) * zoomSpeed, minRadius, maxRadius); // 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). // Ease the actual radius toward the target (frame-rate independent).
_orbit.Radius = zoomSmoothing > 0f _orbit.Radius = zoomSmoothing > 0f
? Mathf.Lerp(_orbit.Radius, _targetRadius, 1f - Mathf.Exp(-zoomSmoothing * Time.deltaTime)) ? Mathf.Lerp(_orbit.Radius, _targetRadius, 1f - Mathf.Exp(-zoomSmoothing * Time.deltaTime))