加入了浮动小游戏功能和设置界面
运行时请手动修改运行库目录fdBrowser
This commit is contained in:
@@ -0,0 +1,494 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
public class controllerSettings : MonoBehaviour
|
||||
{
|
||||
[Header("按键绑定")]
|
||||
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("音符流动速度滑动变阻器")]
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user