using System.Collections.Generic; using UnityEngine; #if ENABLE_INPUT_SYSTEM using UnityEngine.InputSystem; using UnityEngine.InputSystem.EnhancedTouch; using ETouch = UnityEngine.InputSystem.EnhancedTouch.Touch; #endif /// /// Mobile touch dispatcher for the five gameplay tracks. /// PC builds disable this component immediately and use keyboard input only. /// 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 activeTouches = new Dictionary(); 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(); if (zone == null) zone = hit.GetComponentInParent(); if (zone != null) return zone.trackIndex; } return ResolveNearestZone(worldPoint); } private int ResolveNearestZone(Vector2 worldPoint) { IReadOnlyList 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 touch in activeTouches) { inputManager.ReleaseTrack(touch.Value); } if (mouseHeldTrack >= 0) inputManager.ReleaseTrack(mouseHeldTrack); } activeTouches.Clear(); mouseHeldTrack = -1; #if ENABLE_INPUT_SYSTEM EnhancedTouchSupport.Disable(); #endif } }