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.5f; private const float NoteSpeedMax = 2f; 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; [Header("call dt prefab")] public GameObject delayTapper_prefab; public GameObject dt_to_put; public Button launch_delayTapper; public Text currentDelayText; 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(); if (trigger == null) trigger = noteSpeedMultipler_slider.gameObject.AddComponent(); 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()); } // Initialize delay tapper button and display if (launch_delayTapper != null) { launch_delayTapper.onClick.RemoveAllListeners(); launch_delayTapper.onClick.AddListener(OnLaunchDelayTapperClicked); } RefreshDelayText(); } public void RefreshDelayText() { if (currentDelayText != null) { const string DELAY_PREFS_KEY = "UserGlobalDelaySeconds"; if (PlayerPrefs.HasKey(DELAY_PREFS_KEY)) { float savedDelaySeconds = PlayerPrefs.GetFloat(DELAY_PREFS_KEY, 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) { GameObject instantiated = Instantiate(delayTapper_prefab, dt_to_put.transform); Debug.Log($"[controllerSettings] Instantiated delay tapper prefab under {dt_to_put.name}"); // 获取 dtp 脚本并传递当前 settings 引用,以便保存时刷新 UI delayTapperPrefab dtp = instantiated.GetComponent(); if (dtp != null) { dtp.mainSettings = this; } } else { Debug.LogWarning("[controllerSettings] Prefab or target container is missing!"); } } 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(); 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(); 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) { // 将 0.5-2.0 映射为 50%-200% 的百分比显示 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 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(); if (trigger == null) trigger = btn.gameObject.AddComponent(); // 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); } } }