274 lines
7.9 KiB
C#
274 lines
7.9 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace XGame.VirtualInput
|
|
{
|
|
public sealed class VirtualJoystick : MonoBehaviour
|
|
{
|
|
[SerializeField] private RectTransform joystickRoot;
|
|
[SerializeField] private RectTransform knob;
|
|
[SerializeField] private float deadZone = 0.18f;
|
|
[SerializeField] private float acquireRadiusMultiplier = 1.6f;
|
|
|
|
private Vector2 value;
|
|
private bool isActive;
|
|
private bool isCaptured;
|
|
private bool sentStarted;
|
|
private int touchFingerId = -1;
|
|
|
|
private Vector2? screenCenterOverride;
|
|
private float? screenRadiusOverride;
|
|
|
|
public Vector2 Value => value;
|
|
public bool IsActive => isActive;
|
|
|
|
public event Action Started;
|
|
public event Action<Vector2, bool> Changed;
|
|
public event Action Ended;
|
|
|
|
private void Awake()
|
|
{
|
|
BindNodes();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
ReleasePointer();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!isActiveAndEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (UnityEngine.Input.touchCount > 0)
|
|
{
|
|
UpdateTouchInput();
|
|
return;
|
|
}
|
|
|
|
touchFingerId = -1;
|
|
UpdateMouseInput();
|
|
}
|
|
|
|
public bool IsScreenPositionInAcquireArea(Vector2 screenPosition)
|
|
{
|
|
return Vector2.Distance(screenPosition, GetScreenCenter()) <= GetScreenRadius() * acquireRadiusMultiplier;
|
|
}
|
|
|
|
private void BindNodes()
|
|
{
|
|
if (joystickRoot == null)
|
|
{
|
|
Transform widget = transform.Find("Joystick");
|
|
joystickRoot = widget as RectTransform;
|
|
}
|
|
if (joystickRoot == null)
|
|
{
|
|
joystickRoot = transform as RectTransform;
|
|
}
|
|
|
|
if (knob == null)
|
|
{
|
|
Transform knobTransform = transform.FindTransform("Knob");
|
|
knob = knobTransform as RectTransform;
|
|
}
|
|
|
|
if (knob == null)
|
|
{
|
|
Debug.LogError("[VirtualJoystick] missing Knob node: " + name, this);
|
|
enabled = false;
|
|
}
|
|
}
|
|
|
|
private void UpdateMouseInput()
|
|
{
|
|
if (UnityEngine.Input.GetMouseButtonDown(0))
|
|
{
|
|
isCaptured = IsScreenPositionInAcquireArea(UnityEngine.Input.mousePosition);
|
|
}
|
|
|
|
if (UnityEngine.Input.GetMouseButtonUp(0))
|
|
{
|
|
isCaptured = false;
|
|
ReleasePointer();
|
|
return;
|
|
}
|
|
|
|
if (isCaptured && UnityEngine.Input.GetMouseButton(0))
|
|
{
|
|
ApplyPointer(UnityEngine.Input.mousePosition);
|
|
}
|
|
else
|
|
{
|
|
ReleasePointer();
|
|
}
|
|
}
|
|
|
|
private void UpdateTouchInput()
|
|
{
|
|
if (touchFingerId >= 0)
|
|
{
|
|
for (int i = 0; i < UnityEngine.Input.touchCount; i++)
|
|
{
|
|
Touch touch = UnityEngine.Input.GetTouch(i);
|
|
if (touch.fingerId != touchFingerId)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
|
|
{
|
|
touchFingerId = -1;
|
|
ReleasePointer();
|
|
return;
|
|
}
|
|
|
|
ApplyPointer(touch.position);
|
|
return;
|
|
}
|
|
|
|
touchFingerId = -1;
|
|
ReleasePointer();
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < UnityEngine.Input.touchCount; i++)
|
|
{
|
|
Touch touch = UnityEngine.Input.GetTouch(i);
|
|
if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (IsScreenPositionInAcquireArea(touch.position))
|
|
{
|
|
touchFingerId = touch.fingerId;
|
|
ApplyPointer(touch.position);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ReleasePointer();
|
|
}
|
|
|
|
private void ApplyPointer(Vector2 screenPosition)
|
|
{
|
|
if (!sentStarted)
|
|
{
|
|
sentStarted = true;
|
|
Started?.Invoke();
|
|
}
|
|
|
|
Vector2 raw = (screenPosition - GetScreenCenter()) / GetScreenRadius();
|
|
Vector2 nextValue = Vector2.ClampMagnitude(raw, 1f);
|
|
bool nextActive = nextValue.magnitude >= deadZone;
|
|
if (!nextActive)
|
|
{
|
|
nextValue = Vector2.zero;
|
|
}
|
|
|
|
bool changed = (nextValue - value).sqrMagnitude > 0.000001f || nextActive != isActive;
|
|
value = nextValue;
|
|
isActive = nextActive;
|
|
UpdateKnob();
|
|
|
|
if (changed)
|
|
{
|
|
Changed?.Invoke(value, isActive);
|
|
}
|
|
}
|
|
|
|
private void ReleasePointer()
|
|
{
|
|
bool hadState = sentStarted || isActive || value.sqrMagnitude > 0.000001f;
|
|
sentStarted = false;
|
|
isCaptured = false;
|
|
touchFingerId = -1;
|
|
value = Vector2.zero;
|
|
isActive = false;
|
|
UpdateKnob();
|
|
|
|
if (hadState)
|
|
{
|
|
Changed?.Invoke(value, isActive);
|
|
Ended?.Invoke();
|
|
}
|
|
}
|
|
|
|
private void UpdateKnob()
|
|
{
|
|
if (knob != null)
|
|
{
|
|
knob.anchoredPosition = value * GetKnobTravelRadius();
|
|
}
|
|
}
|
|
|
|
private float GetKnobTravelRadius()
|
|
{
|
|
RectTransform root = joystickRoot != null ? joystickRoot : transform as RectTransform;
|
|
if (root == null)
|
|
{
|
|
return GetScreenRadius();
|
|
}
|
|
|
|
float rootHalf = Mathf.Min(root.rect.width, root.rect.height) * 0.5f;
|
|
float knobHalf = knob != null ? Mathf.Min(knob.rect.width, knob.rect.height) * 0.5f : 0f;
|
|
return Mathf.Max(1f, rootHalf - knobHalf);
|
|
}
|
|
|
|
private Vector2 GetScreenCenter()
|
|
{
|
|
if (screenCenterOverride.HasValue)
|
|
{
|
|
return screenCenterOverride.Value;
|
|
}
|
|
|
|
RectTransform root = joystickRoot != null ? joystickRoot : transform as RectTransform;
|
|
if (root == null)
|
|
{
|
|
return Vector2.zero;
|
|
}
|
|
|
|
return RectTransformUtility.WorldToScreenPoint(GetUICamera(), root.position);
|
|
}
|
|
|
|
private float GetScreenRadius()
|
|
{
|
|
if (screenRadiusOverride.HasValue)
|
|
{
|
|
return Mathf.Max(1f, screenRadiusOverride.Value);
|
|
}
|
|
|
|
RectTransform root = joystickRoot != null ? joystickRoot : transform as RectTransform;
|
|
if (root == null)
|
|
{
|
|
return 1f;
|
|
}
|
|
|
|
Camera cam = GetUICamera();
|
|
Vector3[] corners = new Vector3[4];
|
|
root.GetWorldCorners(corners);
|
|
Vector2 s0 = RectTransformUtility.WorldToScreenPoint(cam, corners[0]);
|
|
Vector2 s2 = RectTransformUtility.WorldToScreenPoint(cam, corners[2]);
|
|
return Mathf.Max(1f, Mathf.Min(Mathf.Abs(s2.x - s0.x), Mathf.Abs(s2.y - s0.y)) * 0.5f);
|
|
}
|
|
|
|
private Camera GetUICamera()
|
|
{
|
|
RectTransform root = joystickRoot != null ? joystickRoot : transform as RectTransform;
|
|
Canvas canvas = root != null ? root.GetComponentInParent<Canvas>() : null;
|
|
if (canvas == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
canvas = canvas.rootCanvas;
|
|
return canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
|
|
}
|
|
}
|
|
}
|