using UnityEngine; using UnityEngine.InputSystem; using System; using System.Collections.Generic; using DG.Tweening; using Ichni; using Lean.Common; using Lean.Pool; using Sirenix.OdinInspector; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch; using TouchPhase = UnityEngine.InputSystem.TouchPhase; /// /// 为节奏游戏设计的输入管理器,处理多点触控并分发三种主要事件。 /// 【重要】此版本内置了编辑器内的鼠标模拟功能,无需手机即可测试。 /// public class RhythmInputManager : SerializedMonoBehaviour { // ===================================================================== // 公共事件 (Public Events) // ===================================================================== public static event Action OnTap; public static event Action OnTouch; public static event Action OnSwipe; // ===================================================================== // 可配置参数 (Configurable Parameters) // ===================================================================== [Header("划动设置 (Swipe Settings)")] [Tooltip("识别为划动的最小移动距离(像素)")] [SerializeField] private float minSwipeDistance = 200.0f; [SerializeField] private float swipeAngleThreshold = 1f; // ===================================================================== // 内部状态 (Internal State) // ===================================================================== private class TouchState { public int TouchId; public Vector2 StartPosition; public float StartTime; public Vector2 LastSwipeDirection = Vector2.zero; public bool IsTapCandidate = true; } private readonly Dictionary _activeTouches = new Dictionary(); // 为鼠标模拟专门设置一个固定的Touch ID private const int MOUSE_TOUCH_ID = 999; // ===================================================================== // MonoBehaviour 生命周期方法 (Lifecycle Methods) // ===================================================================== private void Awake() { //OnTap += (id, pos)=> Debug.Log($"Tap Detected: Touch ID {id}, Position {pos}"); //OnTouch += (id, pos) => Debug.Log($"Touch Detected: Touch ID {id}, Position {pos}"); //OnSwipe += (id, pos, dir) => Debug.Log($"Swipe Detected: Touch ID {id}, Position {pos}, Direction {dir}"); OnTap += GenerateTapMark; OnTouch += GenerateTouchMark; OnSwipe += GenerateSwipeMark; } private void Update() { // 使用预处理指令区分平台 #if UNITY_EDITOR || UNITY_STANDALONE // --- 在Unity编辑器中,使用鼠标模拟触摸 --- //ProcessMouseInput(); ProcessKeyboardInput(); #else // --- 在真机设备上,使用真实的触摸输入 --- ProcessRealTouchInput(); #endif } // ===================================================================== // 核心处理逻辑 (Core Processing Logic) // ===================================================================== #if UNITY_EDITOR /// /// 【仅在编辑器中运行】处理鼠标输入并模拟触摸事件。 /// private void ProcessMouseInput() { if (Mouse.current == null) return; Vector2 position = Mouse.current.position.ReadValue(); if (Mouse.current.leftButton.wasPressedThisFrame) { ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Began, position); } else if (Mouse.current.leftButton.isPressed) { // 如果鼠标位置有变化,则为Moved,否则为Stationary if (Mouse.current.delta.ReadValue().sqrMagnitude > 0.1f) { ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Moved, position); } else { ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Stationary, position); } } else if (Mouse.current.leftButton.wasReleasedThisFrame) { ProcessInputEvent(MOUSE_TOUCH_ID, TouchPhase.Ended, position); } if (Mouse.current.rightButton.wasPressedThisFrame) { ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Began, position); } else if (Mouse.current.rightButton.isPressed) { // 如果鼠标位置有变化,则为Moved,否则为Stationary if (Mouse.current.delta.ReadValue().sqrMagnitude > 0.1f) { ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Moved, position); } else { ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Stationary, position); } } else if (Mouse.current.rightButton.wasReleasedThisFrame) { ProcessInputEvent(MOUSE_TOUCH_ID + 1, TouchPhase.Ended, position); } } #endif #if UNITY_EDITOR || UNITY_STANDALONE public static List availableKeys = new List() { Keyboard.current.dKey, Keyboard.current.fKey, Keyboard.current.jKey, Keyboard.current.kKey, }; public static List flickKeys = new List() { Keyboard.current.sKey, Keyboard.current.lKey, }; private void ProcessKeyboardInput() { for (int index = 0; index < availableKeys.Count; index++) { Vector2 inputPosition = new Vector2(index * 400 - 600 + Screen.width * 0.5f, 200f); // 假设每个按键的虚拟位置 if (availableKeys[index].wasPressedThisFrame) { OnTap?.Invoke(index, inputPosition); } if (availableKeys[index].isPressed) { OnTouch?.Invoke(index, inputPosition); } } for (int index = 0; index < flickKeys.Count; index++) { Vector2 inputPosition = new Vector2(index * 1600 - 800 + Screen.width * 0.5f, 200f); // 假设每个划动按键的虚拟位置 if (flickKeys[index].wasPressedThisFrame) { OnSwipe?.Invoke(index, inputPosition, true, Vector2.zero); } } } #endif /// /// 【仅在真机上运行】处理真实的触摸屏输入。 /// private void ProcessRealTouchInput() { if (Touchscreen.current == null) return; foreach (Touch touch in Touch.activeTouches) { ProcessInputEvent( touch.touchId, touch.phase, touch.screenPosition ); } } /// /// 所有输入事件的核心处理函数,无论是真实触摸还是鼠标模拟都会调用它。 /// private void ProcessInputEvent(int touchId, TouchPhase phase, Vector2 position) { switch (phase) { case TouchPhase.Began: var newState = new TouchState { TouchId = touchId, StartPosition = position, StartTime = Time.time, }; _activeTouches[touchId] = newState; OnTap?.Invoke(touchId, position); OnTouch?.Invoke(touchId, position); break; case TouchPhase.Moved: if (_activeTouches.TryGetValue(touchId, out TouchState movedState)) { OnTouch?.Invoke(touchId, position); DetectSwipe(movedState, position); } break; case TouchPhase.Stationary: if (_activeTouches.TryGetValue(touchId, out TouchState stationaryState)) { OnTouch?.Invoke(touchId, position); } break; case TouchPhase.Canceled: if (_activeTouches.ContainsKey(touchId)) { _activeTouches.Remove(touchId); } break; } } /// /// 检测划动逻辑 (无需修改) /// private void DetectSwipe(TouchState state, Vector2 currentPosition) { Vector2 swipeVector = currentPosition - state.StartPosition; if (swipeVector.magnitude < minSwipeDistance) return; Vector2 direction = swipeVector.normalized; // 检查是否是新的划动方向 if (Vector2.Dot(direction, state.LastSwipeDirection) <= swipeAngleThreshold) { OnSwipe?.Invoke(state.TouchId, state.StartPosition, false, direction); state.LastSwipeDirection = direction; state.StartPosition = currentPosition; state.StartTime = Time.time; } } private void GenerateTapMark(int id, Vector2 pos) { RectTransform mark = LeanPool.Spawn(GameManager.instance.basePrefabs.tapInputMark, GameManager.instance.judgeHintCanvas.transform). GetComponent(); RectTransform canvasRect = GameManager.instance.judgeHintCanvas.GetComponent(); if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, pos, null, out Vector2 uiPosition)) { mark.anchoredPosition = uiPosition; } Sequence ss = DOTween.Sequence(); ss.OnStart(() => { mark.GetComponent().color = Color.white; mark.localScale = Vector3.zero; }); ss.Join(mark.GetComponent().DOFade(0, 0.5f)); ss.Join(mark.DOScale(5, 0.5f)); ss.OnComplete(() => LeanPool.Despawn(mark.gameObject)); ss.SetUpdate(true); ss.Play(); } private void GenerateTouchMark(int id, Vector2 pos) { RectTransform mark = LeanPool.Spawn(GameManager.instance.basePrefabs.touchInputMark, GameManager.instance.judgeHintCanvas.transform). GetComponent(); RectTransform canvasRect = GameManager.instance.judgeHintCanvas.GetComponent(); if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, pos, null, out Vector2 uiPosition)) { mark.anchoredPosition = uiPosition; } Sequence ss = DOTween.Sequence(); ss.OnStart(() => { mark.GetComponent().color = Color.white; }); ss.Join(mark.GetComponent().DOFade(0, 0.1f)); ss.OnComplete(() => LeanPool.Despawn(mark.gameObject)); ss.SetUpdate(true); ss.Play(); } private void GenerateSwipeMark(int id, Vector2 pos, bool isGeneric, Vector2 direction) { GameObject markPrefab = isGeneric ? GameManager.instance.basePrefabs.genericSwipeInputMark : GameManager.instance.basePrefabs.directionalSwipeInputMark; RectTransform mark = LeanPool.Spawn(markPrefab, GameManager.instance.judgeHintCanvas.transform). GetComponent(); RectTransform canvasRect = GameManager.instance.judgeHintCanvas.GetComponent(); if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, pos, null, out Vector2 uiPosition)) { mark.anchoredPosition = uiPosition; } mark.localEulerAngles = new Vector3(0, 0, Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg - 90f); Sequence ss = DOTween.Sequence(); ss.OnStart(() => { mark.GetComponent().color = Color.white; mark.localScale = Vector3.zero; }); ss.Join(mark.GetComponent().DOFade(0, 0.5f)); ss.Join(mark.DOScale(5, 0.5f)); ss.OnComplete(() => LeanPool.Despawn(mark.gameObject)); ss.SetUpdate(true); ss.Play(); } }