286 lines
8.2 KiB
C#
286 lines
8.2 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.EnhancedTouch;
|
|
using ETouch = UnityEngine.InputSystem.EnhancedTouch.Touch;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Mobile touch dispatcher for the five gameplay tracks.
|
|
/// PC builds disable this component immediately and use keyboard input only.
|
|
/// </summary>
|
|
public class TrackTouchInput : MonoBehaviour
|
|
{
|
|
[SerializeField] private Camera gameplayCamera;
|
|
[SerializeField] private bool enableMouseFallback = false;
|
|
[Tooltip("推荐:给 5 个 TrackTouchZone 物体单独设一个 layer(比如 layer 10 TouchZone),这里只勾该 layer,避免音符/背景碰撞体挤爆缓冲导致间歇漏点")]
|
|
[SerializeField] private LayerMask hitLayers = ~0;
|
|
[SerializeField] private float trackPlaneWorldZ = 0f;
|
|
[SerializeField] private bool forceOrthographicMode = false;
|
|
|
|
private readonly Dictionary<int, int> activeTouches = new Dictionary<int, int>();
|
|
private int mouseHeldTrack = -1;
|
|
|
|
// 缓冲从 16 扩到 64:密集音符流量下防止 TrackTouchZone 被挤出 OverlapPoint 结果
|
|
private static readonly Collider2D[] overlapResults = new Collider2D[64];
|
|
private ContactFilter2D overlapFilter;
|
|
private bool filterReady;
|
|
|
|
private Camera Cam
|
|
{
|
|
get
|
|
{
|
|
if (gameplayCamera == null)
|
|
gameplayCamera = Camera.main;
|
|
return gameplayCamera;
|
|
}
|
|
}
|
|
|
|
private static bool ShouldProcessTouchInput()
|
|
{
|
|
#if UNITY_ANDROID || UNITY_IOS
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (!ShouldProcessTouchInput())
|
|
enabled = false;
|
|
}
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
private void OnEnable()
|
|
{
|
|
if (!ShouldProcessTouchInput())
|
|
return;
|
|
|
|
EnhancedTouchSupport.Enable();
|
|
}
|
|
#endif
|
|
|
|
private void Update()
|
|
{
|
|
if (!ShouldProcessTouchInput())
|
|
return;
|
|
|
|
InputManager inputManager = InputManager.Instance;
|
|
Camera cam = Cam;
|
|
if (inputManager == null || cam == null)
|
|
return;
|
|
|
|
int touchCount = ProcessTouches(inputManager, cam);
|
|
if (enableMouseFallback && touchCount == 0)
|
|
{
|
|
ProcessMouse(inputManager, cam);
|
|
}
|
|
}
|
|
|
|
private int ProcessTouches(InputManager inputManager, Camera cam)
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
var touches = ETouch.activeTouches;
|
|
int count = touches.Count;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ETouch touch = touches[i];
|
|
switch (touch.phase)
|
|
{
|
|
case UnityEngine.InputSystem.TouchPhase.Began:
|
|
HandleTouchBegan(inputManager, cam, touch.touchId, touch.screenPosition);
|
|
break;
|
|
case UnityEngine.InputSystem.TouchPhase.Ended:
|
|
case UnityEngine.InputSystem.TouchPhase.Canceled:
|
|
HandleTouchEnded(inputManager, touch.touchId);
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
#else
|
|
int count = Input.touchCount;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Touch touch = Input.GetTouch(i);
|
|
switch (touch.phase)
|
|
{
|
|
case TouchPhase.Began:
|
|
HandleTouchBegan(inputManager, cam, touch.fingerId, touch.position);
|
|
break;
|
|
case TouchPhase.Ended:
|
|
case TouchPhase.Canceled:
|
|
HandleTouchEnded(inputManager, touch.fingerId);
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
#endif
|
|
}
|
|
|
|
private void HandleTouchBegan(InputManager inputManager, Camera cam, int touchId, Vector2 screenPosition)
|
|
{
|
|
int track = ResolveTrack(cam, screenPosition);
|
|
if (track < 0)
|
|
return;
|
|
|
|
activeTouches[touchId] = track;
|
|
inputManager.PressTrack(track);
|
|
}
|
|
|
|
private void HandleTouchEnded(InputManager inputManager, int touchId)
|
|
{
|
|
if (!activeTouches.TryGetValue(touchId, out int track))
|
|
return;
|
|
|
|
activeTouches.Remove(touchId);
|
|
inputManager.ReleaseTrack(track);
|
|
}
|
|
|
|
private void ProcessMouse(InputManager inputManager, Camera cam)
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
Mouse mouse = Mouse.current;
|
|
if (mouse == null)
|
|
return;
|
|
|
|
if (mouse.leftButton.wasPressedThisFrame)
|
|
{
|
|
int track = ResolveTrack(cam, mouse.position.ReadValue());
|
|
if (track >= 0)
|
|
{
|
|
mouseHeldTrack = track;
|
|
inputManager.PressTrack(track);
|
|
}
|
|
}
|
|
else if (mouse.leftButton.wasReleasedThisFrame && mouseHeldTrack >= 0)
|
|
{
|
|
inputManager.ReleaseTrack(mouseHeldTrack);
|
|
mouseHeldTrack = -1;
|
|
}
|
|
#else
|
|
if (Input.GetMouseButtonDown(0))
|
|
{
|
|
int track = ResolveTrack(cam, Input.mousePosition);
|
|
if (track >= 0)
|
|
{
|
|
mouseHeldTrack = track;
|
|
inputManager.PressTrack(track);
|
|
}
|
|
}
|
|
else if (Input.GetMouseButtonUp(0) && mouseHeldTrack >= 0)
|
|
{
|
|
inputManager.ReleaseTrack(mouseHeldTrack);
|
|
mouseHeldTrack = -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
private int ResolveTrack(Camera cam, Vector2 screenPosition)
|
|
{
|
|
EnsureOverlapFilter();
|
|
|
|
Vector2 worldPoint;
|
|
if (forceOrthographicMode || cam.orthographic)
|
|
{
|
|
Vector3 world = cam.ScreenToWorldPoint(screenPosition);
|
|
worldPoint = new Vector2(world.x, world.y);
|
|
}
|
|
else
|
|
{
|
|
Ray ray = cam.ScreenPointToRay(screenPosition);
|
|
float denom = ray.direction.z;
|
|
if (Mathf.Abs(denom) < 1e-6f)
|
|
return -1;
|
|
|
|
float t = (trackPlaneWorldZ - ray.origin.z) / denom;
|
|
if (t < 0f)
|
|
return -1;
|
|
|
|
Vector3 world = ray.origin + ray.direction * t;
|
|
worldPoint = new Vector2(world.x, world.y);
|
|
}
|
|
|
|
int count = Physics2D.OverlapPoint(worldPoint, overlapFilter, overlapResults);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Collider2D hit = overlapResults[i];
|
|
if (hit == null)
|
|
continue;
|
|
|
|
TrackTouchZone zone = hit.GetComponent<TrackTouchZone>();
|
|
if (zone == null)
|
|
zone = hit.GetComponentInParent<TrackTouchZone>();
|
|
if (zone != null)
|
|
return zone.trackIndex;
|
|
}
|
|
|
|
return ResolveNearestZone(worldPoint);
|
|
}
|
|
|
|
private int ResolveNearestZone(Vector2 worldPoint)
|
|
{
|
|
IReadOnlyList<TrackTouchZone> zones = TrackTouchZone.All;
|
|
int bestTrack = -1;
|
|
float bestDistance = float.MaxValue;
|
|
|
|
for (int i = 0; i < zones.Count; i++)
|
|
{
|
|
TrackTouchZone zone = zones[i];
|
|
if (zone == null || zone.Collider == null)
|
|
continue;
|
|
|
|
Bounds bounds = zone.Collider.bounds;
|
|
if (worldPoint.y < bounds.min.y - 0.1f || worldPoint.y > bounds.max.y + 0.1f)
|
|
continue;
|
|
|
|
float distance = Mathf.Abs(worldPoint.x - bounds.center.x);
|
|
if (distance < bestDistance)
|
|
{
|
|
bestDistance = distance;
|
|
bestTrack = zone.trackIndex;
|
|
}
|
|
}
|
|
|
|
return bestTrack;
|
|
}
|
|
|
|
private void EnsureOverlapFilter()
|
|
{
|
|
if (filterReady)
|
|
return;
|
|
|
|
overlapFilter = new ContactFilter2D();
|
|
overlapFilter.useLayerMask = true;
|
|
overlapFilter.SetLayerMask(hitLayers);
|
|
overlapFilter.useTriggers = true;
|
|
filterReady = true;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (!ShouldProcessTouchInput())
|
|
return;
|
|
|
|
InputManager inputManager = InputManager.Instance;
|
|
if (inputManager != null)
|
|
{
|
|
foreach (KeyValuePair<int, int> touch in activeTouches)
|
|
{
|
|
inputManager.ReleaseTrack(touch.Value);
|
|
}
|
|
|
|
if (mouseHeldTrack >= 0)
|
|
inputManager.ReleaseTrack(mouseHeldTrack);
|
|
}
|
|
|
|
activeTouches.Clear();
|
|
mouseHeldTrack = -1;
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
EnhancedTouchSupport.Disable();
|
|
#endif
|
|
}
|
|
}
|