428 lines
14 KiB
C#
428 lines
14 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.Controls;
|
|
using UnityEngine.InputSystem.EnhancedTouch;
|
|
using UnityEngine.InputSystem.Utilities;
|
|
using EnhancedTouchSupport = UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport;
|
|
using EnhancedTouchTouch = UnityEngine.InputSystem.EnhancedTouch.Touch;
|
|
using EnhancedTouchPhase = UnityEngine.InputSystem.TouchPhase;
|
|
using UnityTouch = UnityEngine.Touch;
|
|
using TouchPhaseLegacy = UnityEngine.TouchPhase;
|
|
|
|
/// <summary>
|
|
/// Aggregates look input from mouse, gamepad sticks, and mobile touch into a single delta value.
|
|
/// Works with either the legacy Input Manager or the newer Input System.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public sealed class LookInputRouter : MonoBehaviour
|
|
{
|
|
private const float TinyThreshold = 0.0001f;
|
|
|
|
[Header("General")]
|
|
[SerializeField] private bool autoDetectInputSystem = true;
|
|
[SerializeField] private bool preferNewInputSystem = true;
|
|
|
|
[Header("Input System (New)")]
|
|
[Tooltip("Optional PlayerInput component containing the action map with a Look action.")]
|
|
[SerializeField] private PlayerInput playerInput;
|
|
|
|
[Tooltip("Action name used for look deltas when using the new Input System.")]
|
|
[SerializeField] private string lookActionName = "Look";
|
|
|
|
[Header("Legacy Input Manager")]
|
|
[SerializeField] private string mouseXAxis = "Mouse X";
|
|
[SerializeField] private string mouseYAxis = "Mouse Y";
|
|
[SerializeField] private string rightStickXAxis = "Joystick X";
|
|
[SerializeField] private string rightStickYAxis = "Joystick Y";
|
|
|
|
[Header("Sensitivities")]
|
|
[SerializeField] private float mouseSensitivity = 1.0f;
|
|
[SerializeField] private float stickSensitivity = 120.0f;
|
|
[SerializeField] private float touchSensitivity = 0.15f;
|
|
|
|
[Header("Filtering")]
|
|
[Tooltip("Inputs below this magnitude will be ignored and reset to zero.")]
|
|
[SerializeField] private float deadzone = 0.05f;
|
|
|
|
[Tooltip("Optional smoothing duration in seconds. Set to zero for raw input.")]
|
|
[SerializeField] private float smoothingTime = 0.08f;
|
|
|
|
[Tooltip("Maximum magnitude allowed for a single look delta.")]
|
|
[SerializeField] private float maxMagnitude = 5.0f;
|
|
|
|
private InputAction cachedLookAction;
|
|
private bool usingNewInputSystem;
|
|
|
|
private Vector2 currentDelta;
|
|
private Vector2 smoothVelocity;
|
|
|
|
// Touch state
|
|
private int activeTouchId = -1;
|
|
private Vector2 lastTouchPosition;
|
|
|
|
private void Awake()
|
|
{
|
|
DetectInputSystem();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
// Enable Enhanced Touch so we can use the high-level Touch struct API.
|
|
EnhancedTouchSupport.Enable();
|
|
|
|
if (usingNewInputSystem)
|
|
{
|
|
EnableLookAction();
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (cachedLookAction != null)
|
|
{
|
|
cachedLookAction.Disable();
|
|
}
|
|
cachedLookAction = null;
|
|
|
|
// Pair with Enable() to keep the global reference count balanced.
|
|
EnhancedTouchSupport.Disable();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
Vector2 rawDelta = usingNewInputSystem ? ReadNewSystemDelta() : ReadLegacyDelta();
|
|
|
|
if (!usingNewInputSystem && preferNewInputSystem)
|
|
{
|
|
// If no PlayerInput was supplied but the Input System package is present,
|
|
// fall back to gathering from Gamepad/Touchscreen directly.
|
|
rawDelta += ReadDirectDeviceDelta();
|
|
}
|
|
|
|
rawDelta = Vector2.ClampMagnitude(rawDelta, maxMagnitude);
|
|
|
|
if (smoothingTime > TinyThreshold)
|
|
{
|
|
currentDelta = Vector2.SmoothDamp(currentDelta, rawDelta, ref smoothVelocity, smoothingTime, Mathf.Infinity, Time.unscaledDeltaTime);
|
|
}
|
|
else
|
|
{
|
|
currentDelta = rawDelta;
|
|
}
|
|
|
|
if (currentDelta.magnitude < deadzone)
|
|
{
|
|
currentDelta = Vector2.zero;
|
|
}
|
|
}
|
|
|
|
public bool TryGetLookDelta(out Vector2 delta)
|
|
{
|
|
delta = currentDelta;
|
|
return delta.sqrMagnitude > TinyThreshold;
|
|
}
|
|
|
|
private void DetectInputSystem()
|
|
{
|
|
if (!autoDetectInputSystem)
|
|
{
|
|
usingNewInputSystem = preferNewInputSystem && playerInput != null;
|
|
CacheLookAction();
|
|
return;
|
|
}
|
|
|
|
// If PlayerInput is explicitly assigned, use it.
|
|
if (playerInput == null)
|
|
{
|
|
playerInput = GetComponent<PlayerInput>();
|
|
}
|
|
|
|
if (playerInput != null && preferNewInputSystem)
|
|
{
|
|
usingNewInputSystem = true;
|
|
CacheLookAction();
|
|
}
|
|
else
|
|
{
|
|
usingNewInputSystem = false;
|
|
}
|
|
}
|
|
|
|
private void CacheLookAction()
|
|
{
|
|
if (playerInput == null)
|
|
return;
|
|
|
|
var actions = playerInput.actions;
|
|
if (actions == null)
|
|
return;
|
|
|
|
cachedLookAction = actions.FindAction(lookActionName, false);
|
|
if (cachedLookAction != null && !cachedLookAction.enabled && isActiveAndEnabled)
|
|
{
|
|
cachedLookAction.SafeEnable();
|
|
}
|
|
}
|
|
|
|
private void EnableLookAction()
|
|
{
|
|
if (cachedLookAction == null && playerInput != null)
|
|
{
|
|
CacheLookAction();
|
|
}
|
|
|
|
cachedLookAction?.SafeEnable();
|
|
}
|
|
|
|
private Vector2 ReadNewSystemDelta()
|
|
{
|
|
if (cachedLookAction == null)
|
|
return ReadDirectDeviceDelta();
|
|
|
|
Vector2 value = cachedLookAction.ReadValue<Vector2>();
|
|
return value * Time.unscaledDeltaTime;
|
|
}
|
|
|
|
private Vector2 ReadLegacyDelta()
|
|
{
|
|
// Replace legacy Input.GetAxis usage with InputSystem device reads where possible.
|
|
float x = 0f, y = 0f;
|
|
|
|
if (UnityEngine.InputSystem.Mouse.current != null)
|
|
{
|
|
Vector2 mouseDelta = UnityEngine.InputSystem.Mouse.current.delta.ReadValue();
|
|
x = mouseDelta.x * mouseSensitivity;
|
|
y = mouseDelta.y * mouseSensitivity;
|
|
}
|
|
|
|
float stickX = 0f, stickY = 0f;
|
|
if (UnityEngine.InputSystem.Gamepad.current != null)
|
|
{
|
|
Vector2 right = UnityEngine.InputSystem.Gamepad.current.rightStick.ReadValue();
|
|
stickX = right.x * stickSensitivity * Time.unscaledDeltaTime;
|
|
stickY = right.y * stickSensitivity * Time.unscaledDeltaTime;
|
|
}
|
|
|
|
return new Vector2(x + stickX, y + stickY);
|
|
}
|
|
|
|
private Vector2 ReadDirectDeviceDelta()
|
|
{
|
|
Vector2 aggregate = Vector2.zero;
|
|
|
|
if (Mouse.current != null)
|
|
{
|
|
Vector2 mouseDelta = Mouse.current.delta.ReadValue();
|
|
aggregate += mouseDelta * mouseSensitivity * Time.unscaledDeltaTime;
|
|
}
|
|
|
|
if (Gamepad.current != null)
|
|
{
|
|
Vector2 stick = Gamepad.current.rightStick.ReadValue();
|
|
aggregate += stick * stickSensitivity * Time.unscaledDeltaTime;
|
|
}
|
|
|
|
aggregate += ReadTouchDelta();
|
|
|
|
return aggregate;
|
|
}
|
|
|
|
private Vector2 ReadTouchDelta()
|
|
{
|
|
if (EnhancedTouchSupport.enabled)
|
|
{
|
|
var enhancedDelta = ReadEnhancedTouchDelta();
|
|
if (enhancedDelta != Vector2.zero || EnhancedTouchTouch.activeTouches.Count > 0)
|
|
return enhancedDelta;
|
|
}
|
|
|
|
if (Touchscreen.current != null)
|
|
{
|
|
var touchscreenDelta = ReadTouchscreenDelta();
|
|
if (touchscreenDelta != Vector2.zero)
|
|
return touchscreenDelta;
|
|
}
|
|
|
|
return ReadLegacyTouch();
|
|
}
|
|
|
|
private Vector2 ReadEnhancedTouchDelta()
|
|
{
|
|
var touches = EnhancedTouchTouch.activeTouches;
|
|
if (touches.Count <= 0)
|
|
{
|
|
ResetTouchTracking();
|
|
return Vector2.zero;
|
|
}
|
|
|
|
for (int i = 0; i < touches.Count; ++i)
|
|
{
|
|
var touch = touches[i];
|
|
if (!touch.valid)
|
|
continue;
|
|
|
|
if (activeTouchId == -1)
|
|
{
|
|
activeTouchId = touch.touchId;
|
|
lastTouchPosition = touch.screenPosition;
|
|
}
|
|
|
|
if (touch.touchId != activeTouchId)
|
|
continue;
|
|
|
|
if (touch.phase == EnhancedTouchPhase.Ended || touch.phase == EnhancedTouchPhase.Canceled)
|
|
{
|
|
ResetTouchTracking();
|
|
return Vector2.zero;
|
|
}
|
|
|
|
if (touch.phase == EnhancedTouchPhase.Began)
|
|
{
|
|
lastTouchPosition = touch.screenPosition;
|
|
return Vector2.zero;
|
|
}
|
|
|
|
Vector2 delta = (touch.screenPosition - lastTouchPosition) * touchSensitivity * Time.unscaledDeltaTime;
|
|
lastTouchPosition = touch.screenPosition;
|
|
return delta;
|
|
}
|
|
|
|
return Vector2.zero;
|
|
}
|
|
|
|
private Vector2 ReadTouchscreenDelta()
|
|
{
|
|
if (Touchscreen.current == null)
|
|
{
|
|
return Vector2.zero;
|
|
}
|
|
|
|
if (!AnyTouchPressed(Touchscreen.current.touches))
|
|
{
|
|
ResetTouchTracking();
|
|
return Vector2.zero;
|
|
}
|
|
|
|
foreach (var touchControl in Touchscreen.current.touches)
|
|
{
|
|
if (!touchControl.press.isPressed)
|
|
continue;
|
|
|
|
int id = touchControl.touchId.ReadValue();
|
|
Vector2 position = touchControl.position.ReadValue();
|
|
|
|
if (activeTouchId == -1)
|
|
{
|
|
activeTouchId = id;
|
|
lastTouchPosition = position;
|
|
continue;
|
|
}
|
|
|
|
if (activeTouchId != id)
|
|
continue;
|
|
|
|
Vector2 delta = (position - lastTouchPosition) * touchSensitivity;
|
|
lastTouchPosition = position;
|
|
return delta * Time.unscaledDeltaTime;
|
|
}
|
|
|
|
return Vector2.zero;
|
|
}
|
|
|
|
private Vector2 ReadLegacyTouch()
|
|
{
|
|
// Prefer the Input System touchscreen if available. Fall back to legacy Input.touches only when necessary.
|
|
if (UnityEngine.InputSystem.Touchscreen.current != null)
|
|
{
|
|
var touches = UnityEngine.InputSystem.Touchscreen.current.touches;
|
|
if (touches.Count <= 0)
|
|
{
|
|
ResetTouchTracking();
|
|
return Vector2.zero;
|
|
}
|
|
|
|
for (int i = 0; i < touches.Count; ++i)
|
|
{
|
|
var tc = touches[i];
|
|
if (!tc.press.isPressed)
|
|
continue;
|
|
|
|
int id = tc.touchId.ReadValue();
|
|
Vector2 pos = tc.position.ReadValue();
|
|
|
|
// We already check tc.press.isPressed above. No additional phase check is required
|
|
// here for the Input System touch path. This avoids ambiguous TouchPhase enum
|
|
// comparisons between the legacy and new Input System types.
|
|
|
|
if (activeTouchId == -1)
|
|
{
|
|
activeTouchId = id;
|
|
lastTouchPosition = pos;
|
|
continue;
|
|
}
|
|
|
|
if (activeTouchId != id)
|
|
continue;
|
|
|
|
Vector2 delta = (pos - lastTouchPosition) * touchSensitivity * Time.unscaledDeltaTime;
|
|
lastTouchPosition = pos;
|
|
return delta;
|
|
}
|
|
|
|
return Vector2.zero;
|
|
}
|
|
|
|
// Legacy fallback
|
|
if (Input.touchCount <= 0)
|
|
{
|
|
ResetTouchTracking();
|
|
return Vector2.zero;
|
|
}
|
|
|
|
for (int i = 0; i < Input.touchCount; i++)
|
|
{
|
|
UnityTouch touch = Input.GetTouch(i);
|
|
if (touch.phase == TouchPhaseLegacy.Ended || touch.phase == TouchPhaseLegacy.Canceled)
|
|
continue;
|
|
|
|
if (activeTouchId == -1)
|
|
{
|
|
activeTouchId = touch.fingerId;
|
|
lastTouchPosition = touch.position;
|
|
continue;
|
|
}
|
|
|
|
if (activeTouchId != touch.fingerId)
|
|
continue;
|
|
|
|
Vector2 delta = touch.deltaPosition * touchSensitivity * Time.unscaledDeltaTime;
|
|
lastTouchPosition = touch.position;
|
|
return delta;
|
|
}
|
|
|
|
return Vector2.zero;
|
|
}
|
|
|
|
private void ResetTouchTracking()
|
|
{
|
|
activeTouchId = -1;
|
|
lastTouchPosition = Vector2.zero;
|
|
}
|
|
|
|
public void ResetLookDelta()
|
|
{
|
|
currentDelta = Vector2.zero;
|
|
smoothVelocity = Vector2.zero;
|
|
}
|
|
private static bool AnyTouchPressed(ReadOnlyArray<TouchControl> touches)
|
|
{
|
|
for (int i = 0; i < touches.Count; ++i)
|
|
{
|
|
if (touches[i].press.isPressed)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|