153 lines
5.4 KiB
C#
153 lines
5.4 KiB
C#
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.EnhancedTouch;
|
|
using UnityEngine.InputSystem.OnScreen;
|
|
using UnityEngine.InputSystem.Layouts;
|
|
using EnhancedTouchSupport = UnityEngine.InputSystem.EnhancedTouch.EnhancedTouchSupport;
|
|
|
|
namespace DeviantMobile.Input
|
|
{
|
|
// Drag-based on-screen stick that writes a Vector2 to the Input System.
|
|
// Bind your Move action to the same control path (e.g. <Gamepad>/leftStick).
|
|
[AddComponentMenu("Input/Mobile On-Screen Stick")]
|
|
public class MobileOnScreenStick : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler
|
|
{
|
|
[Header("Platform / Visibility")]
|
|
[Tooltip("If true, on-screen stick will be visible when running in the Editor (useful for testing). If false, controls are hidden in editor/desktop builds and only shown on mobile platforms.")]
|
|
[SerializeField] private bool allowShowInEditor = false;
|
|
|
|
// Helper to determine whether on-screen controls should be active for the current platform.
|
|
private bool ShouldShowOnScreenControls()
|
|
{
|
|
if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer)
|
|
return true;
|
|
if (Application.isEditor && allowShowInEditor)
|
|
return true;
|
|
return false;
|
|
}
|
|
[Header("Control Path")]
|
|
[Tooltip("Input System control path to write the Vector2 to. Commonly '<Gamepad>/leftStick'.")]
|
|
[SerializeField, InputControl(layout = "Vector2")] private string m_ControlPath = "<Gamepad>/leftStick";
|
|
|
|
protected override string controlPathInternal
|
|
{
|
|
get => m_ControlPath;
|
|
set => m_ControlPath = value;
|
|
}
|
|
|
|
[Header("UI References")]
|
|
[SerializeField] private RectTransform background; // Joystick background area
|
|
[SerializeField] private RectTransform handle; // Joystick handle/knob
|
|
|
|
[Header("Behavior")]
|
|
[Tooltip("Max radius in pixels the handle can travel from center.")]
|
|
[SerializeField] private float maxRadius = 100f;
|
|
[Tooltip("Deadzone as a fraction of radius (0..1).")]
|
|
[Range(0f, 0.5f)] [SerializeField] private float deadZone = 0.1f;
|
|
[Tooltip("Return handle to center on pointer up.")]
|
|
[SerializeField] private bool snapBackOnRelease = true;
|
|
|
|
private bool _pressed;
|
|
private Vector2 _centerLocal;
|
|
|
|
void Awake()
|
|
{
|
|
if (background == null)
|
|
background = GetComponent<RectTransform>();
|
|
if (handle == null && background != null && background.childCount > 0)
|
|
handle = background.GetChild(0) as RectTransform;
|
|
|
|
// Disable the UI for non-mobile platforms by default.
|
|
gameObject.SetActive(ShouldShowOnScreenControls());
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
// Ensure the Enhanced Touch pipeline is active so pointer/touch events map correctly to OnScreenControl.
|
|
EnhancedTouchSupport.Enable();
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
EnhancedTouchSupport.Disable();
|
|
}
|
|
|
|
public void OnPointerDown(PointerEventData eventData)
|
|
{
|
|
_pressed = true;
|
|
CacheCenter();
|
|
OnDrag(eventData); // update immediately
|
|
}
|
|
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
if (background == null)
|
|
return;
|
|
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(background, eventData.position, eventData.pressEventCamera, out var localPoint);
|
|
var delta = localPoint - _centerLocal;
|
|
|
|
// Clamp to radius
|
|
if (delta.magnitude > maxRadius)
|
|
delta = delta.normalized * maxRadius;
|
|
|
|
// Normalize to [-1,1]
|
|
var norm = (maxRadius > 0f) ? (delta / maxRadius) : Vector2.zero;
|
|
|
|
// Apply deadzone
|
|
var mag = norm.magnitude;
|
|
if (mag < deadZone)
|
|
norm = Vector2.zero;
|
|
else if (mag > 1f)
|
|
norm = norm.normalized;
|
|
|
|
// Move handle visually
|
|
if (handle != null)
|
|
handle.anchoredPosition = delta;
|
|
|
|
// Send to Input System
|
|
SendValueToControl(norm);
|
|
}
|
|
|
|
public void OnPointerUp(PointerEventData eventData)
|
|
{
|
|
_pressed = false;
|
|
if (snapBackOnRelease)
|
|
{
|
|
ResetHandle();
|
|
}
|
|
SendValueToControl(Vector2.zero);
|
|
}
|
|
|
|
private void CacheCenter()
|
|
{
|
|
if (background != null)
|
|
{
|
|
// Center is the pivot point local position (0 if centered pivot). Use rect center for safety.
|
|
_centerLocal = background.rect.center;
|
|
}
|
|
else
|
|
{
|
|
_centerLocal = Vector2.zero;
|
|
}
|
|
}
|
|
|
|
private void ResetHandle()
|
|
{
|
|
if (handle != null)
|
|
handle.anchoredPosition = Vector2.zero;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void OnValidate()
|
|
{
|
|
if (background == null)
|
|
background = GetComponent<RectTransform>();
|
|
if (handle == null && background != null && background.childCount > 0)
|
|
handle = background.GetChild(0) as RectTransform;
|
|
}
|
|
#endif
|
|
}
|
|
}
|