Files
ichni_Official/Assets/Scripts/Manager/GameInputManager.cs
SoulliesOfficial 70b2a43824 update
2025-08-22 14:54:40 -04:00

493 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// 为节奏游戏设计的输入管理器,处理多点触控并分发三种主要事件。
/// 【重要】此版本内置了编辑器内的鼠标模拟功能,无需手机即可测试。
/// </summary>
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<int, TouchState> _activeTouches = new Dictionary<int, TouchState>();
// 为鼠标模拟专门设置一个固定的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<string>("Rebinds", rebindsFilePath));
Debug.Log("已加载自定义按键绑定");
}
RegisterActionsInputs();
#endif
}
private void Update()
{
if (!GameManager.instance.audioManager.isUpdating)
{
return;
}
// 使用预处理指令区分平台
#if UNITY_EDITOR || UNITY_STANDALONE
ProcessMouseInput();
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
/// <summary>
/// 【仅在编辑器中运行】处理鼠标输入并模拟触摸事件。
/// </summary>
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
/// <summary>
/// 【仅在真机上运行】处理真实的触摸屏输入。
/// </summary>
private void ProcessRealTouchInput()
{
if (Touchscreen.current == null) return;
foreach (Touch touch in Touch.activeTouches)
{
ProcessInputEvent(
touch.touchId,
touch.phase,
touch.screenPosition
);
}
}
/// <summary>
/// 所有输入事件的核心处理函数,无论是真实触摸还是鼠标模拟都会调用它。
/// </summary>
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;
}
}
/// <summary>
/// 检测划动逻辑 (无需修改)
/// </summary>
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>();
RectTransform canvasRect = GameManager.instance.judgeHintCanvas.GetComponent<RectTransform>();
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, pos, null, out Vector2 uiPosition))
{
mark.anchoredPosition = uiPosition;
mark.GetComponentInChildren<TMP_Text>().text = GameManager.instance.noteJudgeManager.checkingTapList.Count.ToString();
}
Sequence ss = DOTween.Sequence();
ss.OnStart(() =>
{
mark.GetComponent<Image>().color = Color.white;
mark.localScale = Vector3.zero;
});
ss.Join(mark.GetComponent<Image>().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>();
RectTransform canvasRect = GameManager.instance.judgeHintCanvas.GetComponent<RectTransform>();
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, pos, null, out Vector2 uiPosition))
{
mark.anchoredPosition = uiPosition;
}
Sequence ss = DOTween.Sequence();
ss.OnStart(() =>
{
mark.GetComponent<Image>().color = Color.white;
});
ss.Join(mark.GetComponent<Image>().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>();
RectTransform canvasRect = GameManager.instance.judgeHintCanvas.GetComponent<RectTransform>();
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<Image>().color = Color.white;
mark.localScale = Vector3.zero;
});
ss.Join(mark.GetComponent<Image>().DOFade(0, 0.25f));
ss.Join(mark.DOScale(5, 0.25f));
ss.OnComplete(() => LeanPool.Despawn(mark.gameObject));
ss.SetUpdate(true);
ss.Play();
}
}