563 lines
19 KiB
C#
563 lines
19 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.5f;
|
|
private const float NoteSpeedMax = 2f;
|
|
private const string NoteSpeedPrefKey = "noteSpeedMultiplier";
|
|
private const float NoteSpeedDefault = 1.0f;
|
|
|
|
// 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;
|
|
EnsureDefaultNoteSpeedPreference();
|
|
float saved = PlayerPrefs.GetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
|
|
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());
|
|
}
|
|
|
|
// 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<delayTapperPrefab>();
|
|
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<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)
|
|
{
|
|
// 将 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 = NoteSpeedDefault; // triggers change and save
|
|
SaveNoteSpeedValue(NoteSpeedDefault);
|
|
UpdateNoteSpeedText(NoteSpeedDefault);
|
|
}
|
|
|
|
private static void EnsureDefaultNoteSpeedPreference()
|
|
{
|
|
if (PlayerPrefs.HasKey(NoteSpeedPrefKey))
|
|
{
|
|
return;
|
|
}
|
|
|
|
PlayerPrefs.SetFloat(NoteSpeedPrefKey, NoteSpeedDefault);
|
|
PlayerPrefs.Save();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|