Files
2026-07-25 08:31:10 +08:00

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
}
}