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 TMPro; using UnityEngine.InputSystem.Controls; using UnityEngine.UI; using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch; using TouchPhase = UnityEngine.InputSystem.TouchPhase; /// /// 为节奏游戏设计的输入管理器,处理多点触控并分发三种主要事件。 /// 【重要】此版本内置了编辑器内的鼠标模拟功能,无需手机即可测试。 /// public class GameInputManager : SerializedMonoBehaviour { // ===================================================================== // 可配置参数 (Configurable Parameters) // ===================================================================== [Header("划动设置 (Swipe Settings)")] [Tooltip("识别为划动的最小移动距离(像素)")] [SerializeField] private float minSwipeDistance = 100f; [SerializeField] private float swipeAngleThreshold = 1f; public GameInput gameInput; public PlayerInput playerInput; private string rebindsFilePath => Application.persistentDataPath + "/GameData/Rebindings.json"; // ===================================================================== // 内部状态 (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() { #if UNITY_EDITOR || UNITY_STANDALONE DOTween.SetTweensCapacity(200, 200); gameInput = new GameInput(); gameInput.Game.Enable(); if (ES3.FileExists(rebindsFilePath) && ES3.KeyExists("Rebinds", rebindsFilePath)) { gameInput.LoadBindingOverridesFromJson(ES3.Load("Rebinds", rebindsFilePath)); Debug.Log("已加载自定义按键绑定"); } RegisterActionsInputs(); #endif } private void Update() { if (!GameManager.instance.audioManager.isUpdating) { return; } // 使用预处理指令区分平台 #if UNITY_EDITOR || UNITY_STANDALONE HandleHolding(); #else ProcessRealTouchInput(); #endif } private void OnTap(int id, Vector2 position) { if (SettingsManager.instance.gameSettings.debugMode) { GenerateTapMark(id, position); } GameManager.instance.noteJudgeManager.SetNewInputUnitTap(id, position); } private void OnTouch(int id, Vector2 position) { if (SettingsManager.instance.gameSettings.debugMode) { GenerateTouchMark(id, position); } GameManager.instance.noteJudgeManager.SetNewInputUnitTouch(id, position); } private void OnSwipe(int id, Vector2 position, bool isGeneric, Vector2 direction) { if (SettingsManager.instance.gameSettings.debugMode) { GenerateSwipeMark(id, position, isGeneric, direction); } GameManager.instance.noteJudgeManager.SetNewInputUnitSwipe(id, position, isGeneric, direction); } // ===================================================================== // 核心处理逻辑 (Core Processing Logic) // ===================================================================== #if UNITY_EDITOR || UNITY_STANDALONE /// /// 【仅在编辑器中运行】处理鼠标输入并模拟触摸事件。 /// 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 bool holdingTouch0; public bool holdingTouch1; public bool holdingTouch2; public bool holdingTouch3; public bool holdingSwipe0; public bool holdingSwipe1; private void RegisterActionsInputs() { gameInput.Game.Tap0.performed += ctx => { if (ctx.performed) { Vector2 inputPosition = new Vector2(-600 + Screen.width * 0.5f, 200f); OnTap(0, inputPosition); holdingTouch0 = true; } }; gameInput.Game.Tap1.performed += ctx => { if (ctx.performed) { Vector2 inputPosition = new Vector2(-200 + Screen.width * 0.5f, 200f); OnTap(1, inputPosition); holdingTouch1 = true; } }; gameInput.Game.Tap2.performed += ctx => { if (ctx.performed) { Vector2 inputPosition = new Vector2(200 + Screen.width * 0.5f, 200f); OnTap(2, inputPosition); holdingTouch2 = true; } }; gameInput.Game.Tap3.performed += ctx => { if (ctx.performed) { Vector2 inputPosition = new Vector2(600 + Screen.width * 0.5f, 200f); OnTap(3, inputPosition); holdingTouch3 = true; } }; gameInput.Game.Tap0.canceled += ctx => { if (ctx.canceled) { holdingTouch0 = false; } }; gameInput.Game.Tap1.canceled += ctx => { if (ctx.canceled) { holdingTouch1 = false; } }; gameInput.Game.Tap2.canceled += ctx => { if (ctx.canceled) { holdingTouch2 = false; } }; gameInput.Game.Tap3.canceled += ctx => { if (ctx.canceled) { holdingTouch3 = false; } }; gameInput.Game.Swipe0.performed += ctx => { if (ctx.performed) { holdingSwipe0 = true; } }; gameInput.Game.Swipe0.canceled += ctx => { if (ctx.canceled) { holdingSwipe0 = false; } }; /*gameInput.Game.Swipe1.performed += ctx => { if (ctx.performed) { holdingSwipe1 = true; } }; gameInput.Game.Swipe1.canceled += ctx => { if (ctx.canceled) { holdingSwipe1 = false; } };*/ } private void HandleHolding() { if (holdingTouch0) { Vector2 inputPosition = new Vector2(-600 + Screen.width * 0.5f, 200f); OnTouch(0, inputPosition); } if (holdingTouch1) { Vector2 inputPosition = new Vector2(-200 + Screen.width * 0.5f, 200f); OnTouch(1, inputPosition); } if (holdingTouch2) { Vector2 inputPosition = new Vector2(200 + Screen.width * 0.5f, 200f); OnTouch(2, inputPosition); } if (holdingTouch3) { Vector2 inputPosition = new Vector2(600 + Screen.width * 0.5f, 200f); OnTouch(3, inputPosition); } if (holdingSwipe0) { Vector2 inputPosition = new Vector2(Screen.width * 0.5f, 200f); OnSwipe(0, inputPosition, true, Vector2.zero); } /*if (holdingSwipe1) { Vector2 inputPosition = new Vector2(600 + Screen.width * 0.5f, 200f); OnSwipe(3, 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(touchId, position); OnTouch(touchId, position); break; case TouchPhase.Moved: if (_activeTouches.TryGetValue(touchId, out TouchState movedState)) { OnTouch(touchId, position); DetectSwipe(movedState, position); } break; case TouchPhase.Stationary: if (_activeTouches.TryGetValue(touchId, out TouchState stationaryState)) { OnTouch(touchId, position); } break; case TouchPhase.Ended: if (_activeTouches.ContainsKey(touchId)) { _activeTouches.Remove(touchId); } 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(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; mark.GetComponentInChildren().text = GameManager.instance.noteJudgeManager.checkingTapList.Count.ToString(); } Sequence ss = DOTween.Sequence(); ss.OnStart(() => { mark.GetComponent().color = Color.white; mark.localScale = Vector3.zero; }); ss.Join(mark.GetComponent().DOFade(0, 0.25f)); ss.Join(mark.DOScale(5, 0.25f)); 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.25f)); ss.Join(mark.DOScale(5, 0.25f)); ss.OnComplete(() => LeanPool.Despawn(mark.gameObject)); ss.SetUpdate(true); ss.Play(); } }