Keep the paint-mode pan on release and scale it by zoom
The middle-mouse pan now persists when the button is released and only glides back to center on leaving paint mode. Pan sensitivity and max distance scale with the orbit zoom distance, so panning stays consistent on screen at any zoom and unchanged at the default zoom. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e8a907a8c
commit
3fa99f32fe
2 files changed
+124
-2
No files matched your search
@@ -1,6 +1,7 @@
|
||||
/// <summary>
|
||||
/// Player stands still, the mesh is painted with the left mouse button. The
|
||||
/// camera orbits only while the right mouse button is held.
|
||||
/// camera orbits only while the right mouse button is held, and the middle
|
||||
/// mouse button pans the framing (a temporary offset to line up the model).
|
||||
/// </summary>
|
||||
public class PaintMode : GameMode
|
||||
{
|
||||
@@ -18,9 +19,19 @@ public class PaintMode : GameMode
|
||||
}
|
||||
|
||||
if (Manager.CameraInput)
|
||||
{
|
||||
Manager.CameraInput.LookMode = ThirdPersonOrbitInput.OrbitLookMode.HoldRightMouseButton;
|
||||
Manager.CameraInput.PanEnabled = true;
|
||||
}
|
||||
|
||||
if (Manager.Painter)
|
||||
Manager.Painter.PaintingEnabled = true;
|
||||
}
|
||||
|
||||
public override void Exit()
|
||||
{
|
||||
// Leaving paint mode releases the middle-mouse pan; the orbit input then eases the framing home.
|
||||
if (Manager.CameraInput)
|
||||
Manager.CameraInput.PanEnabled = false;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,25 @@ public class ThirdPersonOrbitInput : MonoBehaviour
|
||||
"(size-1 design value; scaled by the target). Set so the bound player is comfortably framed.")]
|
||||
public float defaultRadius = 4.5f;
|
||||
|
||||
[Header("Pan (middle mouse)")]
|
||||
[Tooltip("Hold the middle mouse button and drag to pan the camera framing - a temporary offset to bring an " +
|
||||
"awkward spot of the model in front of the lens for painting. The pan stays put on release and only " +
|
||||
"glides back to center when paint mode is left. Enabled only while PaintMode is active.")]
|
||||
public bool enablePan = true;
|
||||
|
||||
[Tooltip("World units the pan offset moves per pixel of mouse drag, measured at the default zoom. It then " +
|
||||
"scales with the live zoom distance so the model slides the same amount on screen per pixel at any " +
|
||||
"zoom (closer in = finer). Body scale is folded in automatically via the orbit radius.")]
|
||||
public float panSensitivity = 0.004f;
|
||||
|
||||
[Tooltip("Farthest the framing can pan from center, measured at the default zoom. It scales with the zoom " +
|
||||
"distance too, so the maximum on-screen pan reach stays the same at every zoom level.")]
|
||||
public float panMaxDistance = 1f;
|
||||
|
||||
[Tooltip("How snappily the pan glides back to center when paint mode is left (1/second; 0 = snaps home " +
|
||||
"instantly). The pan stays put on middle-button release - it only returns on leaving paint mode.")]
|
||||
public float panReturnSpeed = 14f;
|
||||
|
||||
[Header("Cursor")]
|
||||
[Tooltip("Lock and hide the cursor while the camera is actively rotating.")]
|
||||
public bool manageCursor = true;
|
||||
@@ -76,6 +95,8 @@ public class ThirdPersonOrbitInput : MonoBehaviour
|
||||
Vector2 _smoothedLook; // low-pass-filtered look delta (deg/frame) for eased rotation
|
||||
float _targetRadius; // zoom radius the actual radius eases toward
|
||||
bool _zoomInit; // _targetRadius seeded from the live radius yet?
|
||||
Vector2 _pan; // accumulated pan in camera-space units (x=right, y=up); stays put on release, eases to 0 on leaving paint mode
|
||||
CinemachineCameraOffset _camOffset; // post-aim extension that slides the whole framing (true pan, not an orbit arc)
|
||||
|
||||
/// <summary>Current orbit mode. Setting it also refreshes the cursor state.</summary>
|
||||
public OrbitLookMode LookMode
|
||||
@@ -91,16 +112,40 @@ public class ThirdPersonOrbitInput : MonoBehaviour
|
||||
/// </summary>
|
||||
public bool LookSuppressed { get; set; }
|
||||
|
||||
/// <summary>While true, holding the middle mouse button and dragging pans the camera framing - a temporary
|
||||
/// look-point offset that stays put on release. Set by PaintMode so the pan is only available while painting;
|
||||
/// clearing it (on leaving paint mode) is what glides the framing back home.</summary>
|
||||
public bool PanEnabled { get; set; }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
_orbit = GetComponent<CinemachineOrbitalFollow>();
|
||||
if (!_orbit)
|
||||
Debug.LogError("[ThirdPersonOrbitInput] No CinemachineOrbitalFollow found on this GameObject.", this);
|
||||
|
||||
// The pan rides a CinemachineCameraOffset extension applied AFTER aim with PreserveComposition off: it
|
||||
// shifts the camera position AND the look-at point by the same world vector, so the framing translates
|
||||
// (the model slides across the screen) instead of re-aiming back to center like a body TargetOffset
|
||||
// would. Offset is camera-local (x=right, y=up). Added at runtime so no scene/prefab authoring is needed.
|
||||
if (enablePan)
|
||||
{
|
||||
_camOffset = GetComponent<CinemachineCameraOffset>();
|
||||
if (!_camOffset) _camOffset = gameObject.AddComponent<CinemachineCameraOffset>();
|
||||
_camOffset.ApplyAfter = CinemachineCore.Stage.Aim;
|
||||
_camOffset.PreserveComposition = false;
|
||||
_camOffset.Offset = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable() => ApplyCursorState();
|
||||
|
||||
void OnDisable() => SetCursorLocked(false);
|
||||
void OnDisable()
|
||||
{
|
||||
SetCursorLocked(false);
|
||||
// Drop any live pan so a disabled rig doesn't leave a stale framing offset behind.
|
||||
_pan = Vector2.zero;
|
||||
if (_camOffset) _camOffset.Offset = Vector3.zero;
|
||||
}
|
||||
|
||||
/// <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
|
||||
@@ -112,6 +157,15 @@ public class ThirdPersonOrbitInput : MonoBehaviour
|
||||
return t ? Mathf.Max(0.0001f, t.lossyScale.x) : 1f;
|
||||
}
|
||||
|
||||
/// <summary>How much the pan scales with the live zoom distance. Dividing the orbit radius by the default
|
||||
/// radius makes this exactly 1 at the default zoom (so the authored panSensitivity/panMaxDistance keep their
|
||||
/// feel there), smaller when zoomed in (finer pan, less reach) and larger when zoomed out. The body scale
|
||||
/// cancels out because the radius already embeds it - a bigger player orbits proportionally farther - so the
|
||||
/// pan stays body-relative across player sizes at every zoom, just like CurrentScale() did at the default
|
||||
/// radius. The result: the model slides the same amount ON SCREEN per pixel, and the same maximum reach
|
||||
/// across the screen, regardless of how far the wheel has zoomed.</summary>
|
||||
float PanZoomScale() => _orbit ? _orbit.Radius / Mathf.Max(0.0001f, defaultRadius) : 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>
|
||||
@@ -211,6 +265,8 @@ public class ThirdPersonOrbitInput : MonoBehaviour
|
||||
? Mathf.Lerp(_orbit.Radius, _targetRadius, 1f - Mathf.Exp(-zoomSmoothing * Time.deltaTime))
|
||||
: _targetRadius;
|
||||
}
|
||||
|
||||
UpdatePan(Time.deltaTime);
|
||||
}
|
||||
|
||||
bool ShouldRotate()
|
||||
@@ -267,6 +323,61 @@ public class ThirdPersonOrbitInput : MonoBehaviour
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsMiddleMouseHeld()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
return Mouse.current != null && Mouse.current.middleButton.isPressed;
|
||||
#else
|
||||
return Input.GetMouseButton(2);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Middle-mouse pan: while PaintMode enables it, holding the middle button and dragging slides the
|
||||
/// whole framing along the screen right/up axes (via the camera-offset extension), then eases the offset
|
||||
/// back to center on release. A temporary framing offset to line up an awkward spot of the model for
|
||||
/// painting - the player body and its paint collider are untouched. Disabled (eases home) outside paint mode
|
||||
/// and while the orbit is parked in <see cref="OrbitLookMode.Disabled"/> (pause / no-player).</summary>
|
||||
void UpdatePan(float dt)
|
||||
{
|
||||
if (!enablePan || !_camOffset)
|
||||
return;
|
||||
|
||||
bool active = PanEnabled && lookMode != OrbitLookMode.Disabled;
|
||||
float zoom = PanZoomScale();
|
||||
|
||||
if (active && IsMiddleMouseHeld())
|
||||
{
|
||||
Vector2 d = ReadMouseDelta() * MouseSensitivity.Multiplier;
|
||||
// Subtracting the drag gives a hand-tool feel (the model follows the cursor: drag right -> model
|
||||
// slides right). Both the step and the clamp scale with the orbit distance so the pan feels the same
|
||||
// on screen at every zoom - finer when zoomed in, and the same maximum reach across the screen.
|
||||
_pan -= d * (panSensitivity * zoom);
|
||||
_pan = Vector2.ClampMagnitude(_pan, panMaxDistance * zoom);
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
return; // still painting, middle button released - hold the pan where it was left
|
||||
}
|
||||
else if (_pan != Vector2.zero)
|
||||
{
|
||||
// Left paint mode (or look disabled) - glide the framing back to center.
|
||||
if (panReturnSpeed > 0f)
|
||||
_pan = Vector2.Lerp(_pan, Vector2.zero, 1f - Mathf.Exp(-panReturnSpeed * dt));
|
||||
else
|
||||
_pan = Vector2.zero;
|
||||
// Snap the last sliver so the offset settles exactly on center and this branch stops running.
|
||||
if (_pan.sqrMagnitude < 1e-8f)
|
||||
_pan = Vector2.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
return; // at rest and not panning - nothing to write
|
||||
}
|
||||
|
||||
// Offset is camera-local (x=right, y=up); the extension translates eye + look-at together -> true pan.
|
||||
_camOffset.Offset = new Vector3(_pan.x, _pan.y, 0f);
|
||||
}
|
||||
|
||||
static bool ShiftHeld()
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
|
||||
Reference in new issue
Block a user