198 lines
7.3 KiB
C#
198 lines
7.3 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
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
|
|
{
|
|
// Variant that targets actions directly instead of raw control paths.
|
|
[RequireComponent(typeof(RectTransform))]
|
|
public class MobileControlManagerAction : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
|
{
|
|
[Header("Platform / Visibility")]
|
|
[Tooltip("If true, on-screen controls 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;
|
|
}
|
|
[System.Serializable]
|
|
public class ActionBinding
|
|
{
|
|
[Tooltip("InputActionReference to trigger for this tap count. Leave empty to ignore.")]
|
|
public InputActionReference action;
|
|
|
|
[Tooltip("Optional label for clarity only (not used at runtime)")]
|
|
public string label;
|
|
}
|
|
|
|
[Header("Tap to Action Bindings (index 0=single, 1=double, 2=triple, 3=quad)")]
|
|
public ActionBinding[] actions = new ActionBinding[4];
|
|
|
|
[Header("Tap Settings")]
|
|
[Tooltip("Max delay between taps to register as a multi-tap (seconds)")]
|
|
[Range(0.1f, 0.6f)] public float multiTapWindow = 0.3f;
|
|
[Tooltip("How long to hold the virtual press (seconds)")]
|
|
[Range(0.01f, 0.2f)] public float pressDuration = 0.05f;
|
|
|
|
private int tapCount = 0;
|
|
private float lastTapTime = -1f;
|
|
private Coroutine finalizeCoroutine;
|
|
|
|
// Internally we build OnScreenPulseControl(s) that point at the bound actions' control paths.
|
|
private readonly List<OnScreenPulseControl> _pulsers = new List<OnScreenPulseControl>(4);
|
|
|
|
void Awake()
|
|
{
|
|
BuildOrRefreshPulsers();
|
|
// Ensure visibility matches platform (do this early so UI is correct in hierarchy)
|
|
gameObject.SetActive(ShouldShowOnScreenControls());
|
|
}
|
|
|
|
void OnEnable()
|
|
{
|
|
// Ensure EnhancedTouch is available so Unity's UI and On-Screen controls receive multi-touch data.
|
|
EnhancedTouchSupport.Enable();
|
|
BuildOrRefreshPulsers();
|
|
gameObject.SetActive(ShouldShowOnScreenControls());
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
// Balance the Enable call; safe to invoke even if already disabled elsewhere.
|
|
EnhancedTouchSupport.Disable();
|
|
}
|
|
|
|
public void OnPointerDown(PointerEventData eventData) { }
|
|
|
|
public void OnPointerUp(PointerEventData eventData)
|
|
{
|
|
var now = Time.unscaledTime;
|
|
if (lastTapTime > 0 && (now - lastTapTime) <= multiTapWindow)
|
|
tapCount++;
|
|
else
|
|
tapCount = 1;
|
|
|
|
lastTapTime = now;
|
|
if (finalizeCoroutine != null)
|
|
StopCoroutine(finalizeCoroutine);
|
|
finalizeCoroutine = StartCoroutine(FinalizeTapAfterWindow());
|
|
}
|
|
|
|
private IEnumerator FinalizeTapAfterWindow()
|
|
{
|
|
yield return new WaitForSecondsRealtime(multiTapWindow);
|
|
TriggerForTapCount(tapCount);
|
|
tapCount = 0;
|
|
lastTapTime = -1f;
|
|
finalizeCoroutine = null;
|
|
}
|
|
|
|
private void TriggerForTapCount(int taps)
|
|
{
|
|
int index = Mathf.Clamp(taps, 1, 4) - 1;
|
|
if (index >= _pulsers.Count) return;
|
|
var pulser = _pulsers[index];
|
|
if (pulser == null) return;
|
|
pulser.Pulse(1f, pressDuration);
|
|
}
|
|
|
|
private void BuildOrRefreshPulsers()
|
|
{
|
|
_pulsers.Clear();
|
|
|
|
foreach (var existing in GetComponents<OnScreenPulseControl>())
|
|
{
|
|
if (Application.isPlaying)
|
|
Destroy(existing);
|
|
else
|
|
DestroyImmediate(existing);
|
|
}
|
|
|
|
// If actions not set, nothing to do.
|
|
if (actions == null) return;
|
|
|
|
// We resolve each InputActionReference to a suitable control path.
|
|
for (int i = 0; i < Mathf.Min(4, actions.Length); i++)
|
|
{
|
|
var binding = actions[i];
|
|
if (binding == null || binding.action == null || binding.action.action == null)
|
|
{
|
|
_pulsers.Add(null);
|
|
continue;
|
|
}
|
|
|
|
// Determine a control path. Prefer the first non-empty binding path.
|
|
string path = ResolvePreferredControlPath(binding.action.action);
|
|
if (string.IsNullOrWhiteSpace(path))
|
|
{
|
|
_pulsers.Add(null);
|
|
continue;
|
|
}
|
|
|
|
var pulser = gameObject.AddComponent<OnScreenPulseControl>();
|
|
// Set the control path resolved from the InputAction.
|
|
// In the InputAction asset, ensure the action is bound to a control that matches this path
|
|
// (for example a Button control on a touch/gamepad). The OnScreenPulseControl will send
|
|
// press/release values to that control path, which the InputSystem action can receive.
|
|
pulser.controlPath = path;
|
|
_pulsers.Add(pulser);
|
|
}
|
|
}
|
|
|
|
private static string ResolvePreferredControlPath(InputAction action)
|
|
{
|
|
// Try active controls if the action is enabled
|
|
if (action.enabled && action.controls.Count > 0)
|
|
{
|
|
// Use the first control's path
|
|
return action.controls[0].path;
|
|
}
|
|
|
|
// Fallback to the first binding that has a path
|
|
foreach (var b in action.bindings)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(b.effectivePath))
|
|
return b.effectivePath;
|
|
if (!string.IsNullOrWhiteSpace(b.path))
|
|
return b.path;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void OnValidate()
|
|
{
|
|
if (actions == null || actions.Length != 4)
|
|
{
|
|
var old = actions;
|
|
actions = new ActionBinding[4];
|
|
if (old != null)
|
|
{
|
|
for (int i = 0; i < Mathf.Min(4, old.Length); i++)
|
|
actions[i] = old[i];
|
|
}
|
|
}
|
|
|
|
if (Application.isPlaying == false)
|
|
UnityEditor.EditorApplication.delayCall += () => {
|
|
if (this != null)
|
|
BuildOrRefreshPulsers();
|
|
};
|
|
}
|
|
#endif
|
|
}
|
|
}
|