Camera: smooth the walk look-ahead + pull default zoom in (feel)

The movement look-ahead added its 2.5u lead raw onto the framed point and smoothed only the final camera position, so the lead target snapped with the instantaneous input -- start/stop panned the view and reversing direction swung it ~5u (the jarring walk-shift).

Fix with the genre-standard separate view-position technique: ease a dedicated _leadOffset toward the desired lead via a new gentle LeadSharpness knob, independent of FollowSharpness. Cut the lead magnitude 2.5->1.0 (AimLeadDistance, 0 = fully centred like Diablo/PoE) and pull the default zoom 16->13 (~19% closer). Code defaults + the live Game.unity Main Camera values both updated. See 2026-06-11_Camera_Feel_LookAhead_Zoom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-11 12:13:33 -07:00
parent b55bf2bd98
commit 500eebeff8
2 changed files with 21 additions and 10 deletions
@@ -63,7 +63,7 @@ namespace ProjectM.Client
[Range(-180f, 180f)] public float Yaw = 0f;
[Header("Framing")]
[Min(1f)] public float Distance = 16f;
[Min(1f)] public float Distance = 13f;
[Tooltip("Raise the look-at point off the ground toward the character's centre of mass.")]
public float TargetHeight = 1f;
@@ -79,11 +79,16 @@ namespace ProjectM.Client
public Vector3 FallbackTarget = new Vector3(3f, 0f, 4f);
[Header("Movement look-ahead")]
[Tooltip("Shift the framed point this many world units toward where the player is aiming (0 = off). " +
"Leads the camera toward the cursor / stick so aiming feels grounded. Smoothed by FollowSharpness.")]
[Range(0f, 8f)] public float AimLeadDistance = 2.5f;
[Tooltip("Lead the framed point this many world units toward the player's MOVEMENT direction " +
"(0 = off, fully centred like Diablo / PoE). Kept subtle and eased in/out by LeadSharpness " +
"so starting, stopping, or reversing glides instead of bumping the view.")]
[Range(0f, 8f)] public float AimLeadDistance = 1f;
[Tooltip("How fast the look-ahead offset eases toward its target (lower = smoother/slower). " +
"Deliberately gentler than FollowSharpness so the lead glides instead of snapping with input.")]
[Min(0f)] public float LeadSharpness = 3f;
Camera _cam;
Vector3 _leadOffset; // smoothed look-ahead offset (world units), eased toward the desired lead each frame
void Awake() => _cam = GetComponent<Camera>();
@@ -102,14 +107,19 @@ namespace ProjectM.Client
Vector3 target = HasTarget ? (Vector3)TargetWorldPos : FallbackTarget;
target.y += TargetHeight;
// Movement look-ahead: lead the framed point toward where the player is MOVING (not aiming).
// Leading toward AIM coupled the camera to the cursor: turning to face a near-cursor panned the cam,
// which re-projected the live mouse ray -> the aim swam (worst near the player). Smoothed by FollowSharpness.
// Movement look-ahead, smoothed INDEPENDENTLY of the follow. We lead toward MOVEMENT (not aim):
// leading toward AIM coupled the cam to the cursor and made the aim swim. The desired lead snaps
// with the raw input (full while held, zero when released); easing _leadOffset toward it at the
// gentle LeadSharpness rate is what removes the "bump" - start / stop / reverse glide, never pan.
Vector3 desiredLead = Vector3.zero;
if (AimLeadDistance > 0f && HasTarget && math.lengthsq(TargetMoveDir) > 1e-6f)
{
float2 f = math.normalize(TargetMoveDir);
target += new Vector3(f.x, 0f, f.y) * AimLeadDistance;
desiredLead = new Vector3(f.x, 0f, f.y) * AimLeadDistance;
}
float leadK = LeadSharpness <= 0f ? 1f : 1f - Mathf.Exp(-LeadSharpness * Time.deltaTime);
_leadOffset = Vector3.Lerp(_leadOffset, desiredLead, leadK);
target += _leadOffset;
var rot = Quaternion.Euler(Pitch, Yaw, 0f);
Vector3 desired = target - (rot * Vector3.forward) * Distance;