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
+3 -2
View File
@@ -9476,14 +9476,15 @@ MonoBehaviour:
m_EditorClassIdentifier: ProjectM.Client::ProjectM.Client.PrototypeCameraRig m_EditorClassIdentifier: ProjectM.Client::ProjectM.Client.PrototypeCameraRig
Pitch: 45 Pitch: 45
Yaw: 0 Yaw: 0
Distance: 16 Distance: 13
TargetHeight: 1 TargetHeight: 1
Orthographic: 0 Orthographic: 0
FieldOfView: 55 FieldOfView: 55
OrthoSize: 10 OrthoSize: 10
FollowSharpness: 8 FollowSharpness: 8
FallbackTarget: {x: 3, y: 0, z: 4} FallbackTarget: {x: 3, y: 0, z: 4}
AimLeadDistance: 2.5 AimLeadDistance: 1
LeadSharpness: 3
--- !u!1001 &331498972 --- !u!1001 &331498972
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -63,7 +63,7 @@ namespace ProjectM.Client
[Range(-180f, 180f)] public float Yaw = 0f; [Range(-180f, 180f)] public float Yaw = 0f;
[Header("Framing")] [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.")] [Tooltip("Raise the look-at point off the ground toward the character's centre of mass.")]
public float TargetHeight = 1f; public float TargetHeight = 1f;
@@ -79,11 +79,16 @@ namespace ProjectM.Client
public Vector3 FallbackTarget = new Vector3(3f, 0f, 4f); public Vector3 FallbackTarget = new Vector3(3f, 0f, 4f);
[Header("Movement look-ahead")] [Header("Movement look-ahead")]
[Tooltip("Shift the framed point this many world units toward where the player is aiming (0 = off). " + [Tooltip("Lead the framed point this many world units toward the player's MOVEMENT direction " +
"Leads the camera toward the cursor / stick so aiming feels grounded. Smoothed by FollowSharpness.")] "(0 = off, fully centred like Diablo / PoE). Kept subtle and eased in/out by LeadSharpness " +
[Range(0f, 8f)] public float AimLeadDistance = 2.5f; "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; Camera _cam;
Vector3 _leadOffset; // smoothed look-ahead offset (world units), eased toward the desired lead each frame
void Awake() => _cam = GetComponent<Camera>(); void Awake() => _cam = GetComponent<Camera>();
@@ -102,14 +107,19 @@ namespace ProjectM.Client
Vector3 target = HasTarget ? (Vector3)TargetWorldPos : FallbackTarget; Vector3 target = HasTarget ? (Vector3)TargetWorldPos : FallbackTarget;
target.y += TargetHeight; target.y += TargetHeight;
// Movement look-ahead: lead the framed point toward where the player is MOVING (not aiming). // Movement look-ahead, smoothed INDEPENDENTLY of the follow. We lead toward MOVEMENT (not aim):
// Leading toward AIM coupled the camera to the cursor: turning to face a near-cursor panned the cam, // leading toward AIM coupled the cam to the cursor and made the aim swim. The desired lead snaps
// which re-projected the live mouse ray -> the aim swam (worst near the player). Smoothed by FollowSharpness. // 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) if (AimLeadDistance > 0f && HasTarget && math.lengthsq(TargetMoveDir) > 1e-6f)
{ {
float2 f = math.normalize(TargetMoveDir); 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); var rot = Quaternion.Euler(Pitch, Yaw, 0f);
Vector3 desired = target - (rot * Vector3.forward) * Distance; Vector3 desired = target - (rot * Vector3.forward) * Distance;