Files
bansonic_beta_main/Assets/scripts/settings/controllerSettings.cs
T

495 lines
17 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class controllerSettings : MonoBehaviour
{
[Header("Inspector")]
public Button[] keyButtons; // assign 5 buttons in Inspector
public Text errorText; // UI text to show error/success messages
[Header("Message Colors")]
public Color messageErrorColor = Color.red;
public Color messageSuccessColor = Color.green;
private Coroutine messageCoroutine = null;
[Header("Inspector")]
public Slider noteSpeedMultipler_slider;
public Text noteSpeedMultipler_valueText;
public Button noteSpeed_DecreaseButton;
public Button noteSpeed_IncreaseButton;
public Button noteSpeed_ResetButton;
// internal colors/order must match KeyBindingManager usage
private readonly string[] colors = new string[] { "red", "green", "yellow", "purple", "blue" };
private bool isRebinding = false;
private int rebindingIndex = -1;
private Coroutine blinkCoroutine = null;
// store previous key so we can restore if user cancels
private KeyCode previousKey = KeyCode.None;
private bool previousKeyValid = false;
// slider range
private const float NoteSpeedMin = 0.8f;
private const float NoteSpeedMax = 1.25f;
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
// continuous change
private Coroutine continuousChangeCoroutine = null;
private const float ContinuousInitialDelay = 0.5f; // increased to avoid accidental long-press
private const float ContinuousRepeatRate = 0.05f;
private const float StepAmount = 0.01f;
void Start()
{
// initialize button labels from KeyBindingManager
RefreshButtonLabels();
// initialize errorText
if (errorText != null)
{
errorText.text = string.Empty;
}
// hook up listeners for key buttons
if (keyButtons != null)
{
for (int i = 0; i < keyButtons.Length && i < colors.Length; i++)
{
int idx = i;
keyButtons[i].onClick.RemoveAllListeners();
keyButtons[i].onClick.AddListener(() => OnKeyButtonClicked(idx));
}
}
// initialize slider
if (noteSpeedMultipler_slider != null)
{
noteSpeedMultipler_slider.minValue = NoteSpeedMin;
noteSpeedMultipler_slider.maxValue = NoteSpeedMax;
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, 1.0f);
saved = Mathf.Clamp(saved, NoteSpeedMin, NoteSpeedMax);
noteSpeedMultipler_slider.value = saved;
UpdateNoteSpeedText(saved);
noteSpeedMultipler_slider.onValueChanged.RemoveAllListeners();
noteSpeedMultipler_slider.onValueChanged.AddListener(OnNoteSpeedSliderChanged);
// Add PointerUp event to save value when sliding ends (kept for compatibility)
EventTrigger trigger = noteSpeedMultipler_slider.gameObject.GetComponent<EventTrigger>();
if (trigger == null) trigger = noteSpeedMultipler_slider.gameObject.AddComponent<EventTrigger>();
trigger.triggers.RemoveAll(e => e.eventID == EventTriggerType.PointerUp);
var entry = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
entry.callback.AddListener((data) => { OnNoteSpeedSliderPointerUp(); });
trigger.triggers.Add(entry);
}
// setup buttons
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());
}
}
void OnDestroy()
{
if (keyButtons != null)
{
foreach (var b in keyButtons)
if (b != null) b.onClick.RemoveAllListeners();
}
if (noteSpeedMultipler_slider != null)
{
noteSpeedMultipler_slider.onValueChanged.RemoveListener(OnNoteSpeedSliderChanged);
// Do not remove EventTrigger to avoid affecting other listeners, but it's okay on destroy
}
if (noteSpeed_DecreaseButton != null) noteSpeed_DecreaseButton.onClick.RemoveAllListeners();
if (noteSpeed_IncreaseButton != null) noteSpeed_IncreaseButton.onClick.RemoveAllListeners();
if (noteSpeed_ResetButton != null) noteSpeed_ResetButton.onClick.RemoveAllListeners();
}
void Update()
{
// If waiting for a new key, also cancel if user clicks outside UI/buttons
if (isRebinding)
{
if (Input.GetMouseButtonDown(0))
{
if (!IsPointerOverAnyKeyButton())
{
// clicked outside the key buttons while rebinding -> cancel and restore
CancelRebind();
return;
}
}
}
if (!isRebinding) return;
// detect any keydown by iterating KeyCode values
foreach (KeyCode kc in Enum.GetValues(typeof(KeyCode)))
{
// ignore mouse buttons and joystick buttons
if (kc >= KeyCode.Mouse0 && kc <= KeyCode.Mouse6) continue;
if (kc >= KeyCode.JoystickButton0 && kc <= KeyCode.Joystick8Button19) continue;
if (Input.GetKeyDown(kc))
{
HandleRebindKey(kc);
break;
}
}
}
private bool IsPointerOverAnyKeyButton()
{
if (EventSystem.current == null) return false;
var pointerData = new PointerEventData(EventSystem.current) { position = Input.mousePosition };
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
if (results.Count == 0) return false;
foreach (var res in results)
{
var go = res.gameObject;
// check if this gameobject is one of the keyButtons or a child of one
for (int i = 0; i < keyButtons.Length; i++)
{
var btn = keyButtons[i];
if (btn == null) continue;
if (go == btn.gameObject || go.transform.IsChildOf(btn.transform))
return true;
}
}
return false;
}
private void OnKeyButtonClicked(int index)
{
if (isRebinding) return; // ignore while another rebind in progress
if (index < 0 || index >= colors.Length) return;
isRebinding = true;
rebindingIndex = index;
// remember previous key so we can restore if user cancels
previousKey = KeyBindingManager.GetKeyForColor(colors[index]);
previousKeyValid = true;
// start blinking underscore on the clicked button
blinkCoroutine = StartCoroutine(BlinkUnderscore(index));
// show persistent prompt
ShowPersistentMessage("按下新按键", messageErrorColor);
}
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);
}
// restore label when finished
RefreshButtonLabel(index);
}
private void HandleRebindKey(KeyCode key)
{
// ignore ESC
if (key == KeyCode.Escape)
{
CancelRebind();
return;
}
// check if key already assigned to another color
for (int i = 0; i < colors.Length; i++)
{
if (i == rebindingIndex) continue;
KeyCode existing = KeyBindingManager.GetKeyForColor(colors[i]);
if (existing == key)
{
// conflict: reject and keep waiting
string msg = $"按键冲突:{KeyBindingManager.GetDisplayName(key)}已被分配给按键{i+1}。";
ShowMessage(msg, messageErrorColor, 2f);
StartCoroutine(FlashButtonInvalid(rebindingIndex));
return;
}
}
// accept binding
KeyBindingManager.ChangeKeyBinding(colors[rebindingIndex], key);
// update UI labels
RefreshButtonLabel(rebindingIndex);
InputManager.Instance?.RefreshKeyLabels();
// clear persistent prompt and show success message
ClearMessage();
ShowMessage("按键设置已保存", messageSuccessColor, 2f);
// finish
previousKeyValid = false;
previousKey = KeyCode.None;
StopRebind();
}
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;
}
// Show a persistent message (until explicitly cleared)
private void ShowPersistentMessage(string message, Color color)
{
if (errorText == null) return;
// stop any timed message
if (messageCoroutine != null)
{
StopCoroutine(messageCoroutine);
messageCoroutine = null;
}
errorText.text = message;
errorText.color = color;
}
// Clear any displayed message immediately
private void ClearMessage()
{
if (messageCoroutine != null)
{
StopCoroutine(messageCoroutine);
messageCoroutine = null;
}
if (errorText != null) errorText.text = string.Empty;
}
private IEnumerator FlashButtonInvalid(int index)
{
Button b = GetButton(index);
if (b == null) yield break;
Color original = b.image.color;
b.image.color = Color.red;
yield return new WaitForSeconds(0.35f);
b.image.color = original;
}
private void CancelRebind()
{
// restore previous key if we have one
if (previousKeyValid && rebindingIndex >= 0 && rebindingIndex < colors.Length)
{
KeyBindingManager.ChangeKeyBinding(colors[rebindingIndex], previousKey);
InputManager.Instance?.RefreshKeyLabels();
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);
}
}
// update RefreshButtonLabel to use display name
private void RefreshButtonLabel(int index)
{
Text txt = GetButtonText(index);
if (txt == null) return;
KeyCode k = KeyBindingManager.GetKeyForColor(colors[index]);
txt.text = KeyBindingManager.GetDisplayName(k);
}
private Button GetButton(int index)
{
if (keyButtons == null) return null;
if (index < 0 || index >= keyButtons.Length) return null;
return keyButtons[index];
}
private Text GetButtonText(int index)
{
Button b = GetButton(index);
if (b == null) return null;
Text t = b.GetComponentInChildren<Text>();
return t;
}
private void OnNoteSpeedSliderChanged(float value)
{
UpdateNoteSpeedText(value);
// save on every change
SaveNoteSpeedValue(value);
}
private void OnNoteSpeedSliderPointerUp()
{
if (noteSpeedMultipler_slider != null)
{
float value = noteSpeedMultipler_slider.value;
PlayerPrefs.SetFloat(NoteSpeedPrefKey, value);
PlayerPrefs.Save();
}
}
private void UpdateNoteSpeedText(float value)
{
if (noteSpeedMultipler_valueText != null)
{
noteSpeedMultipler_valueText.text = value.ToString("F3");
}
}
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 cur = noteSpeedMultipler_slider.value;
float step = StepAmount;
float newVal = cur;
const float eps = 1e-5f;
if (delta > 0f)
{
float ceilStep = Mathf.Ceil(cur / step) * step;
if (Mathf.Abs(cur - ceilStep) < eps)
{
newVal = Mathf.Min(cur + step, NoteSpeedMax);
}
else
{
newVal = Mathf.Min(ceilStep, NoteSpeedMax);
}
}
else if (delta < 0f)
{
float floorStep = Mathf.Floor(cur / step) * step;
if (Mathf.Abs(cur - floorStep) < eps)
{
newVal = Mathf.Max(cur - step, NoteSpeedMin);
}
else
{
newVal = Mathf.Max(floorStep, NoteSpeedMin);
}
}
newVal = Mathf.Clamp(newVal, NoteSpeedMin, NoteSpeedMax);
// round to 3 decimals for display and consistency
newVal = (float)System.Math.Round(newVal, 3);
noteSpeedMultipler_slider.value = newVal; // will trigger OnNoteSpeedSliderChanged and save
}
private void ResetNoteSpeed()
{
if (noteSpeedMultipler_slider == null) return;
noteSpeedMultipler_slider.value = 1.0f; // triggers change and save
SaveNoteSpeedValue(1.0f);
UpdateNoteSpeedText(1.0f);
}
private void AddButtonContinuousEvents(Button btn, float delta)
{
EventTrigger trigger = btn.gameObject.GetComponent<EventTrigger>();
if (trigger == null) trigger = btn.gameObject.AddComponent<EventTrigger>();
// PointerDown -> start continuous change
var downEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
downEntry.callback.AddListener((data) => { StartContinuousChange(delta); });
trigger.triggers.Add(downEntry);
// PointerUp -> stop continuous change
var upEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerUp };
upEntry.callback.AddListener((data) => { StopContinuousChange(); });
trigger.triggers.Add(upEntry);
// Also stop on PointerExit in case cursor leaves button while pressed
var exitEntry = new EventTrigger.Entry { eventID = EventTriggerType.PointerExit };
exitEntry.callback.AddListener((data) => { StopContinuousChange(); });
trigger.triggers.Add(exitEntry);
}
private void StartContinuousChange(float delta)
{
// perform immediate single step
ChangeNoteSpeedBy(delta);
if (continuousChangeCoroutine != null) StopCoroutine(continuousChangeCoroutine);
continuousChangeCoroutine = StartCoroutine(ContinuousChangeRoutine(delta));
}
private void StopContinuousChange()
{
if (continuousChangeCoroutine != null)
{
StopCoroutine(continuousChangeCoroutine);
continuousChangeCoroutine = null;
}
}
private IEnumerator ContinuousChangeRoutine(float delta)
{
yield return new WaitForSeconds(ContinuousInitialDelay);
while (true)
{
ChangeNoteSpeedBy(delta);
yield return new WaitForSeconds(ContinuousRepeatRate);
}
}
}