Files
bansonic_beta_main/Assets/scripts/settings/controllerSettings.cs
T
2026-07-30 23:15:58 +08:00

655 lines
22 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
public class controllerSettings : MonoBehaviour
{
[Header("Inspector")]
public Button[] keyButtons;
public Button secondary_track3Button;
public Text errorText;
[Header("Message Colors")]
public Color messageErrorColor = Color.red;
public Color messageSuccessColor = Color.green;
[Header("Inspector")]
public Slider noteSpeedMultipler_slider;
public Text noteSpeedMultipler_valueText;
public Button noteSpeed_DecreaseButton;
public Button noteSpeed_IncreaseButton;
public Button noteSpeed_ResetButton;
[Header("call dt prefab")]
public GameObject delayTapper_prefab;
public GameObject dt_to_put;
public Button launch_delayTapper;
public Text currentDelayText;
private readonly string[] colors = { "red", "green", "yellow", "purple", "blue" };
private readonly string[] bindingDisplayNames = { "轨道1键位", "轨道2键位", "轨道3键位", "轨道4键位", "轨道5键位", "轨道3键位2" };
private const int SecondaryTrack3Slot = 5;
private const float NoteSpeedMin = 1f;
private const float NoteSpeedMax = 3.25f;
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
private const string NoteSpeedDefaultVersionKey = "noteSpeedMultiplierDefaultVersion";
private const int NoteSpeedDefaultVersion = 2;
private const float NoteSpeedDefault = 2.0f;
private const float ContinuousInitialDelay = 0.5f;
private const float ContinuousRepeatRate = 0.05f;
private const float StepAmount = 0.01f;
private bool isRebinding;
private int rebindingIndex = -1;
private KeyCode previousKey = KeyCode.None;
private bool previousKeyValid;
private Coroutine blinkCoroutine;
private Coroutine messageCoroutine;
private Coroutine continuousChangeCoroutine;
// Input System interactive rebind: a throwaway InputAction listens for the next keyboard
// press and reports the bound control's path, which we convert back to KeyCode so the
// existing KeyCode-based storage and conflict-swap logic stay unchanged.
private InputAction rebindAction;
private InputActionRebindingExtensions.RebindingOperation rebindOperation;
private void Start()
{
RefreshButtonLabels();
ClearMessage();
SetupKeyButtonListeners();
SetupNoteSpeedControls();
SetupDelayTapper();
RefreshDelayText();
}
private void OnDestroy()
{
if (keyButtons != null)
{
foreach (Button button in keyButtons)
{
if (button != null) button.onClick.RemoveAllListeners();
}
}
if (secondary_track3Button != null) secondary_track3Button.onClick.RemoveAllListeners();
if (noteSpeedMultipler_slider != null) noteSpeedMultipler_slider.onValueChanged.RemoveListener(OnNoteSpeedSliderChanged);
if (noteSpeed_DecreaseButton != null) noteSpeed_DecreaseButton.onClick.RemoveAllListeners();
if (noteSpeed_IncreaseButton != null) noteSpeed_IncreaseButton.onClick.RemoveAllListeners();
if (noteSpeed_ResetButton != null) noteSpeed_ResetButton.onClick.RemoveAllListeners();
if (launch_delayTapper != null) launch_delayTapper.onClick.RemoveAllListeners();
CleanupRebindOperation();
}
private void Update()
{
// Clicking away from the key buttons cancels an in-progress rebind. Key capture itself
// is handled by the Input System RebindingOperation started in OnKeyButtonClicked.
if (isRebinding && Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame && !IsPointerOverAnyKeyButton())
{
CancelRebind();
}
}
public void RefreshDelayText()
{
if (currentDelayText == null) return;
const string delayPrefsKey = "UserGlobalDelaySeconds";
if (PlayerPrefs.HasKey(delayPrefsKey))
{
float savedDelaySeconds = PlayerPrefs.GetFloat(delayPrefsKey, 0f);
int ms = Mathf.RoundToInt(savedDelaySeconds * 1000f);
currentDelayText.text = $"当前偏移:{(ms >= 0 ? "+" : "")}{ms}ms";
}
else
{
currentDelayText.text = "未设定偏移数值";
}
}
public void OnLaunchDelayTapperClicked()
{
if (delayTapper_prefab == null || dt_to_put == null)
{
Debug.LogWarning("[controllerSettings] Prefab or target container is missing!");
return;
}
GameObject instantiated = Instantiate(delayTapper_prefab, dt_to_put.transform);
Debug.Log($"[controllerSettings] Instantiated delay tapper prefab under {dt_to_put.name}");
delayTapperPrefab dtp = instantiated.GetComponent<delayTapperPrefab>();
if (dtp != null)
{
dtp.mainSettings = this;
}
}
private void SetupKeyButtonListeners()
{
if (keyButtons != null)
{
for (int i = 0; i < keyButtons.Length && i < colors.Length; i++)
{
int idx = i;
if (keyButtons[i] == null) continue;
keyButtons[i].onClick.RemoveAllListeners();
keyButtons[i].onClick.AddListener(() => OnKeyButtonClicked(idx));
}
}
if (secondary_track3Button != null)
{
secondary_track3Button.onClick.RemoveAllListeners();
secondary_track3Button.onClick.AddListener(() => OnKeyButtonClicked(SecondaryTrack3Slot));
}
}
private void SetupNoteSpeedControls()
{
if (noteSpeedMultipler_slider != null)
{
noteSpeedMultipler_slider.minValue = NoteSpeedMin;
noteSpeedMultipler_slider.maxValue = NoteSpeedMax;
EnsureDefaultNoteSpeedPreference();
float saved = Mathf.Clamp(PlayerPrefs.GetFloat(NoteSpeedPrefKey, NoteSpeedDefault), NoteSpeedMin, NoteSpeedMax);
noteSpeedMultipler_slider.value = saved;
UpdateNoteSpeedText(saved);
noteSpeedMultipler_slider.onValueChanged.RemoveAllListeners();
noteSpeedMultipler_slider.onValueChanged.AddListener(OnNoteSpeedSliderChanged);
EventTrigger trigger = noteSpeedMultipler_slider.gameObject.GetComponent<EventTrigger>();
if (trigger == null) trigger = noteSpeedMultipler_slider.gameObject.AddComponent<EventTrigger>();
trigger.triggers.RemoveAll(e => e.eventID == EventTriggerType.PointerUp);
EventTrigger.Entry entry = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
entry.callback.AddListener(_ => OnNoteSpeedSliderPointerUp());
trigger.triggers.Add(entry);
}
if (noteSpeed_DecreaseButton != null)
{
noteSpeed_DecreaseButton.onClick.RemoveAllListeners();
noteSpeed_DecreaseButton.onClick.AddListener(() => ChangeNoteSpeedBy(-StepAmount));
AddButtonContinuousEvents(noteSpeed_DecreaseButton, -StepAmount);
}
if (noteSpeed_IncreaseButton != null)
{
noteSpeed_IncreaseButton.onClick.RemoveAllListeners();
noteSpeed_IncreaseButton.onClick.AddListener(() => ChangeNoteSpeedBy(StepAmount));
AddButtonContinuousEvents(noteSpeed_IncreaseButton, StepAmount);
}
if (noteSpeed_ResetButton != null)
{
noteSpeed_ResetButton.onClick.RemoveAllListeners();
noteSpeed_ResetButton.onClick.AddListener(ResetNoteSpeed);
}
}
private void SetupDelayTapper()
{
if (launch_delayTapper == null) return;
launch_delayTapper.onClick.RemoveAllListeners();
launch_delayTapper.onClick.AddListener(OnLaunchDelayTapperClicked);
}
private bool IsPointerOverAnyKeyButton()
{
if (EventSystem.current == null) return false;
Vector2 mousePos = Mouse.current != null ? Mouse.current.position.ReadValue() : Vector2.zero;
PointerEventData pointerData = new PointerEventData(EventSystem.current) { position = mousePos };
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
if (results.Count == 0) return false;
foreach (RaycastResult result in results)
{
GameObject go = result.gameObject;
if (IsPointerOverButton(go, secondary_track3Button)) return true;
if (keyButtons == null) continue;
foreach (Button button in keyButtons)
{
if (IsPointerOverButton(go, button)) return true;
}
}
return false;
}
private static bool IsPointerOverButton(GameObject go, Button button)
{
return go != null && button != null && (go == button.gameObject || go.transform.IsChildOf(button.transform));
}
private void OnKeyButtonClicked(int index)
{
if (isRebinding || !IsValidBindingSlot(index)) return;
isRebinding = true;
rebindingIndex = index;
previousKey = GetKeyForSlot(index);
previousKeyValid = true;
blinkCoroutine = StartCoroutine(BlinkUnderscore(index));
ShowPersistentMessage("按下新按键", messageErrorColor);
StartInteractiveRebind();
}
/// <summary>
/// Start an Input System interactive rebind that waits for the next keyboard press.
/// The captured control path is mapped back to KeyCode and fed into the existing
/// HandleRebindKey conflict-swap logic, keeping storage and behavior equivalent.
/// </summary>
private void StartInteractiveRebind()
{
CleanupRebindOperation();
rebindAction = new InputAction("Rebind", InputActionType.Button, expectedControlType: "Button");
// PerformInteractiveRebinding needs an existing binding (index 0) to override.
rebindAction.AddBinding("<Keyboard>/space");
rebindOperation = rebindAction.PerformInteractiveRebinding(0)
.WithControlsExcluding("<Mouse>/leftButton")
.WithControlsExcluding("<Mouse>/rightButton")
.WithControlsExcluding("<Mouse>/middleButton")
.WithControlsExcluding("<Mouse>/position")
.WithControlsExcluding("<Mouse>/delta")
.WithCancelingThrough("<Keyboard>/escape")
.OnCancel(_ => { CleanupRebindOperation(); CancelRebind(); })
.OnComplete(op =>
{
// selectedControl.name is the control name (e.g. "d", "space"); map to KeyCode.
KeyCode captured = ResolveKeyCodeFromControl(op.selectedControl);
CleanupRebindOperation();
if (captured == KeyCode.None)
{
// Unmappable key: treat like a cancel so we don't clobber the binding.
CancelRebind();
return;
}
HandleRebindKey(captured);
})
.Start();
}
private static KeyCode ResolveKeyCodeFromControl(InputControl control)
{
if (control == null) return KeyCode.None;
// control.path -> "/Keyboard/d"; the last segment is the control name.
string name = control.name; // e.g. "d", "space", "leftShift"
return InputSystemKeyMap.PathToKeyCode("<Keyboard>/" + name);
}
private void CleanupRebindOperation()
{
if (rebindOperation != null)
{
rebindOperation.Dispose();
rebindOperation = null;
}
if (rebindAction != null)
{
rebindAction.Dispose();
rebindAction = null;
}
}
private IEnumerator BlinkUnderscore(int index)
{
Text txt = GetButtonText(index);
if (txt == null) yield break;
while (isRebinding && rebindingIndex == index)
{
txt.text = "_";
yield return new WaitForSeconds(0.5f);
txt.text = "";
yield return new WaitForSeconds(0.25f);
}
RefreshButtonLabel(index);
}
private void HandleRebindKey(KeyCode key)
{
if (key == KeyCode.Escape)
{
CancelRebind();
return;
}
if (!IsValidBindingSlot(rebindingIndex)) return;
int conflictSlot = FindSlotByKey(key, rebindingIndex);
KeyCode oldKey = GetKeyForSlot(rebindingIndex);
if (conflictSlot >= 0)
{
SetKeyForSlot(rebindingIndex, key);
SetKeyForSlot(conflictSlot, oldKey);
string msg = $"按键冲突:{KeyBindingManager.GetDisplayName(key)}已与{GetSlotDisplayName(conflictSlot)}互换。";
ShowMessage(msg, messageErrorColor, 2f);
StartCoroutine(FlashButtonInvalid(rebindingIndex));
}
else
{
SetKeyForSlot(rebindingIndex, key);
ClearMessage();
ShowMessage("按键设置已保存", messageSuccessColor, 2f);
}
previousKeyValid = false;
previousKey = KeyCode.None;
RefreshButtonLabels();
InputManager.Instance?.RefreshKeyLabels();
GameplayInputActions.Instance?.RefreshBindings();
StopRebind();
}
private int FindSlotByKey(KeyCode key, int ignoredSlot)
{
for (int i = 0; i <= SecondaryTrack3Slot; i++)
{
if (i == ignoredSlot || !IsValidBindingSlot(i)) continue;
if (GetKeyForSlot(i) == key) return i;
}
return -1;
}
private bool IsValidBindingSlot(int index)
{
return (index >= 0 && index < colors.Length) || index == SecondaryTrack3Slot;
}
private KeyCode GetKeyForSlot(int index)
{
if (index == SecondaryTrack3Slot) return KeyBindingManager.GetSecondaryKeyForTrack3();
if (index >= 0 && index < colors.Length) return KeyBindingManager.GetKeyForColor(colors[index]);
return KeyCode.None;
}
private void SetKeyForSlot(int index, KeyCode key)
{
if (index == SecondaryTrack3Slot)
{
KeyBindingManager.ChangeSecondaryTrack3KeyBinding(key);
return;
}
if (index >= 0 && index < colors.Length)
{
KeyBindingManager.ChangeKeyBinding(colors[index], key);
}
}
private string GetSlotDisplayName(int index)
{
if (index >= 0 && index < bindingDisplayNames.Length) return bindingDisplayNames[index];
return $"键位{index + 1}";
}
private void ShowMessage(string message, Color color, float duration = 2f)
{
if (errorText == null) return;
if (messageCoroutine != null) StopCoroutine(messageCoroutine);
messageCoroutine = StartCoroutine(ShowMessageCoroutine(message, color, duration));
}
private IEnumerator ShowMessageCoroutine(string message, Color color, float duration)
{
errorText.text = message;
errorText.color = color;
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
yield return null;
}
errorText.text = string.Empty;
messageCoroutine = null;
}
private void ShowPersistentMessage(string message, Color color)
{
if (errorText == null) return;
if (messageCoroutine != null)
{
StopCoroutine(messageCoroutine);
messageCoroutine = null;
}
errorText.text = message;
errorText.color = color;
}
private void ClearMessage()
{
if (messageCoroutine != null)
{
StopCoroutine(messageCoroutine);
messageCoroutine = null;
}
if (errorText != null) errorText.text = string.Empty;
}
private IEnumerator FlashButtonInvalid(int index)
{
Button button = GetButton(index);
if (button == null || button.image == null) yield break;
Color original = button.image.color;
button.image.color = Color.red;
yield return new WaitForSeconds(0.35f);
button.image.color = original;
}
private void CancelRebind()
{
CleanupRebindOperation();
if (previousKeyValid && IsValidBindingSlot(rebindingIndex))
{
SetKeyForSlot(rebindingIndex, previousKey);
InputManager.Instance?.RefreshKeyLabels();
GameplayInputActions.Instance?.RefreshBindings();
ClearMessage();
ShowMessage("按键设置已保存", messageSuccessColor, 2f);
}
previousKeyValid = false;
previousKey = KeyCode.None;
StopRebind();
}
private void StopRebind()
{
isRebinding = false;
rebindingIndex = -1;
if (blinkCoroutine != null)
{
StopCoroutine(blinkCoroutine);
blinkCoroutine = null;
}
RefreshButtonLabels();
}
private void RefreshButtonLabels()
{
for (int i = 0; i < colors.Length; i++)
{
RefreshButtonLabel(i);
}
RefreshButtonLabel(SecondaryTrack3Slot);
}
private void RefreshButtonLabel(int index)
{
Text txt = GetButtonText(index);
if (txt == null) return;
txt.text = KeyBindingManager.GetDisplayName(GetKeyForSlot(index));
}
private Button GetButton(int index)
{
if (index == SecondaryTrack3Slot) return secondary_track3Button;
if (keyButtons == null || index < 0 || index >= keyButtons.Length) return null;
return keyButtons[index];
}
private Text GetButtonText(int index)
{
Button button = GetButton(index);
return button != null ? button.GetComponentInChildren<Text>() : null;
}
private void OnNoteSpeedSliderChanged(float value)
{
UpdateNoteSpeedText(value);
SaveNoteSpeedValue(value);
}
private void OnNoteSpeedSliderPointerUp()
{
if (noteSpeedMultipler_slider == null) return;
PlayerPrefs.SetFloat(NoteSpeedPrefKey, noteSpeedMultipler_slider.value);
PlayerPrefs.Save();
}
private void UpdateNoteSpeedText(float value)
{
if (noteSpeedMultipler_valueText == null) return;
int percentage = Mathf.RoundToInt(value * 100f);
noteSpeedMultipler_valueText.text = $"{percentage}%";
}
private void SaveNoteSpeedValue(float value)
{
float v = Mathf.Clamp(value, NoteSpeedMin, NoteSpeedMax);
PlayerPrefs.SetFloat(NoteSpeedPrefKey, v);
PlayerPrefs.Save();
}
private void ChangeNoteSpeedBy(float delta)
{
if (noteSpeedMultipler_slider == null) return;
float current = noteSpeedMultipler_slider.value;
float newValue = current;
const float eps = 1e-5f;
if (delta > 0f)
{
float ceilStep = Mathf.Ceil(current / StepAmount) * StepAmount;
newValue = Mathf.Abs(current - ceilStep) < eps
? Mathf.Min(current + StepAmount, NoteSpeedMax)
: Mathf.Min(ceilStep, NoteSpeedMax);
}
else if (delta < 0f)
{
float floorStep = Mathf.Floor(current / StepAmount) * StepAmount;
newValue = Mathf.Abs(current - floorStep) < eps
? Mathf.Max(current - StepAmount, NoteSpeedMin)
: Mathf.Max(floorStep, NoteSpeedMin);
}
newValue = Mathf.Clamp(newValue, NoteSpeedMin, NoteSpeedMax);
newValue = (float)Math.Round(newValue, 3);
noteSpeedMultipler_slider.value = newValue;
}
private void ResetNoteSpeed()
{
if (noteSpeedMultipler_slider == null) return;
noteSpeedMultipler_slider.value = NoteSpeedDefault;
SaveNoteSpeedValue(NoteSpeedDefault);
UpdateNoteSpeedText(NoteSpeedDefault);
}
private static void EnsureDefaultNoteSpeedPreference()
{
if (PlayerPrefs.HasKey(NoteSpeedPrefKey))
{
int version = PlayerPrefs.GetInt(NoteSpeedDefaultVersionKey, 0);
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
if (version < NoteSpeedDefaultVersion && Mathf.Approximately(saved, 3f))
{
saved = NoteSpeedDefault;
}
saved = Mathf.Clamp(saved, NoteSpeedMin, NoteSpeedMax);
PlayerPrefs.SetFloat(NoteSpeedPrefKey, saved);
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
PlayerPrefs.Save();
return;
}
PlayerPrefs.SetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
PlayerPrefs.SetInt(NoteSpeedDefaultVersionKey, NoteSpeedDefaultVersion);
PlayerPrefs.Save();
}
private void AddButtonContinuousEvents(Button button, float delta)
{
EventTrigger trigger = button.gameObject.GetComponent<EventTrigger>();
if (trigger == null) trigger = button.gameObject.AddComponent<EventTrigger>();
EventTrigger.Entry downEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
downEntry.callback.AddListener(_ => StartContinuousChange(delta));
trigger.triggers.Add(downEntry);
EventTrigger.Entry upEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
upEntry.callback.AddListener(_ => StopContinuousChange());
trigger.triggers.Add(upEntry);
EventTrigger.Entry exitEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerExit };
exitEntry.callback.AddListener(_ => StopContinuousChange());
trigger.triggers.Add(exitEntry);
}
private void StartContinuousChange(float delta)
{
ChangeNoteSpeedBy(delta);
if (continuousChangeCoroutine != null) StopCoroutine(continuousChangeCoroutine);
continuousChangeCoroutine = StartCoroutine(ContinuousChangeRoutine(delta));
}
private void StopContinuousChange()
{
if (continuousChangeCoroutine == null) return;
StopCoroutine(continuousChangeCoroutine);
continuousChangeCoroutine = null;
}
private IEnumerator ContinuousChangeRoutine(float delta)
{
yield return new WaitForSeconds(ContinuousInitialDelay);
while (true)
{
ChangeNoteSpeedBy(delta);
yield return new WaitForSeconds(ContinuousRepeatRate);
}
}
}