修了很多bug和增加功能,优化不少问题

This commit is contained in:
FloatGaming
2026-02-28 06:59:22 +08:00
parent 508a40bba0
commit e11cc1da7a
345 changed files with 173803 additions and 69332 deletions
@@ -0,0 +1,669 @@
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Bansonic;
using UnityEngine.EventSystems;
public class delayTapperPrefab : MonoBehaviour
{
[Header("data")]
[SerializeField] private float delayAmountSeconds;
[Header("delay tapper")]
public TMP_InputField delayAmountNow;
public Slider runSlider;
public TMP_InputField bpmInputField;
public Button startButton;
public Button resetButton;
public Button saveButton;
public Button quitButton;
public TMP_Dropdown modeDropdown;
public Image[] modeSpecificImages; // 4个特定的Image
public Button endiasble_hori;
public GameObject hori_object;
[HideInInspector] public controllerSettings mainSettings;
public Button toggle_extra_ui;
public GameObject extra_ui_object;
public enum TapperMode { EightBeats, FourBeats }
private TapperMode currentMode = TapperMode.FourBeats;
public GameObject tapperHandlePrefab;
public GameObject tapperHandlePrefab_toput;
[Header("Audio Playback")]
public AudioSource audioSource;
public AudioSource hit_sfx_as;
public float musicStartTime = 0f;
public float musicEndTime = 0f;
[Header("Tapper Stats")]
[SerializeField] public List<string> tapValueOffsets = new List<string>();
private float totalOffsetSum = 0f; // 用于计算平均值的累计偏移
private int tapCount = 0; // 累计点击次数
[SerializeField] private int BPM = 120;
private Coroutine sliderCoroutine;
private Coroutine audioCoroutine;
private bool isInternalSetting = false;
private bool isAudioReady = false;
private bool isPaused = false;
private bool isRunning = false;
private float currentElapsed = 0f;
private const string DELAY_PREFS_KEY = "UserGlobalDelaySeconds";
private void Awake()
{
// 强制确保 List 已初始化
if (tapValueOffsets == null) tapValueOffsets = new List<string>();
}
private void Start()
{
if (bpmInputField != null)
{
// 锁定 BPM 为 120 且不可编辑
isInternalSetting = true;
bpmInputField.text = "120";
bpmInputField.interactable = false; // 禁用交互
isInternalSetting = false;
// 监听点击或编辑事件
bpmInputField.onSelect.AddListener(delegate { OnInputFieldSelected(); });
bpmInputField.onValueChanged.AddListener(OnInputFieldChanged);
}
if (startButton != null)
{
// 在代码中显式绑定按钮,防止 Inspector 遗漏
startButton.onClick.RemoveListener(OnStartButtonClicked);
startButton.onClick.AddListener(OnStartButtonClicked);
// 允许点击按钮
startButton.interactable = true;
UpdateStartButtonText();
}
if (saveButton != null)
{
saveButton.onClick.RemoveListener(OnSaveButtonClicked);
saveButton.onClick.AddListener(OnSaveButtonClicked);
}
if (quitButton != null)
{
quitButton.onClick.RemoveListener(OnQuitButtonClicked);
quitButton.onClick.AddListener(OnQuitButtonClicked);
}
// 读取保存的延迟值
float savedDelay = PlayerPrefs.GetFloat(DELAY_PREFS_KEY, 0f);
if (delayAmountNow != null)
{
isInternalSetting = true;
delayAmountNow.text = Mathf.RoundToInt(savedDelay * 1000f).ToString();
isInternalSetting = false;
}
if (modeDropdown != null)
{
modeDropdown.onValueChanged.RemoveListener(OnModeChanged);
modeDropdown.onValueChanged.AddListener(OnModeChanged);
// 默认设置为四拍模式 (index 1)
modeDropdown.value = 1;
// 初始化显示状态
OnModeChanged(modeDropdown.value);
}
if (delayAmountNow != null)
{
delayAmountNow.onEndEdit.RemoveListener(OnDelayAmountEndEdit);
delayAmountNow.onEndEdit.AddListener(OnDelayAmountEndEdit);
}
if (resetButton != null)
{
resetButton.onClick.RemoveListener(OnResetButtonClicked);
resetButton.onClick.AddListener(OnResetButtonClicked);
}
if (endiasble_hori != null)
{
endiasble_hori.onClick.RemoveListener(OnEndisableHoriClicked);
endiasble_hori.onClick.AddListener(OnEndisableHoriClicked);
}
if (toggle_extra_ui != null)
{
toggle_extra_ui.onClick.RemoveListener(OnToggleExtraUIClicked);
toggle_extra_ui.onClick.AddListener(OnToggleExtraUIClicked);
}
// 开始加载音频
StartCoroutine(PrepareAudioAndEnableButton());
}
public void OnResetButtonClicked()
{
Debug.Log("[delayTapperPrefab] Reset 按钮被点击");
// 1. 停止滚动和音频
StopAndResetTapper();
isRunning = false;
isPaused = false;
currentElapsed = 0f;
UpdateStartButtonText();
// 2. 清空 Prefab 和采样数据
ClearTapperHandles();
// 3. 重置 UI 显示
if (delayAmountNow != null)
{
isInternalSetting = true;
delayAmountNow.text = "0";
isInternalSetting = false;
}
// 4. 取消按钮焦点
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(null);
}
}
private void OnEndisableHoriClicked()
{
if (hori_object != null)
{
bool isActive = hori_object.activeSelf;
hori_object.SetActive(!isActive);
Debug.Log($"[delayTapperPrefab] hori_object state toggled to: {!isActive}");
}
// 取消按钮焦点,防止空格触发重复点击
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(null);
}
}
private void OnToggleExtraUIClicked()
{
if (extra_ui_object != null)
{
bool isActive = extra_ui_object.activeSelf;
extra_ui_object.SetActive(!isActive);
Debug.Log($"[delayTapperPrefab] extra_ui_object state toggled to: {!isActive}");
}
// 取消按钮焦点,防止空格触发重复点击
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(null);
}
}
private void OnSaveButtonClicked()
{
if (delayAmountNow != null && float.TryParse(delayAmountNow.text, out float msValue))
{
float seconds = msValue / 1000f;
PlayerPrefs.SetFloat(DELAY_PREFS_KEY, seconds);
PlayerPrefs.Save();
gNotice.warning.display($"已保存全局延迟: {msValue}ms ({seconds:F3}s)");
Debug.Log($"[delayTapperPrefab] Saved delay to PlayerPrefs: {seconds}s");
// 刷新父界面的显示
if (mainSettings != null)
{
mainSettings.RefreshDelayText();
}
}
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(null);
}
// 保存后销毁物体
OnQuitButtonClicked();
}
private void OnQuitButtonClicked()
{
Debug.Log("[delayTapperPrefab] Quit 按钮被点击,销毁物体");
Destroy(this.gameObject);
}
private void UpdateStartButtonText()
{
if (startButton == null) return;
Text btnText = startButton.GetComponentInChildren<Text>();
if (btnText == null)
{
TMP_Text tmpText = startButton.GetComponentInChildren<TMP_Text>();
if (tmpText != null)
{
if (!isRunning) tmpText.text = "开始测试";
else if (isPaused) tmpText.text = "继续";
else tmpText.text = "暂停";
}
return;
}
if (!isRunning) btnText.text = "开始测试";
else if (isPaused) btnText.text = "继续";
else btnText.text = "暂停";
}
private void OnStartButtonClicked()
{
if (!isAudioReady)
{
gNotice.warning.display("音频未就绪");
return;
}
if (!isRunning)
{
// 第一次开始
isRunning = true;
isPaused = false;
currentElapsed = 0f;
StartTapperFromInput();
}
else
{
// 切换暂停/继续
isPaused = !isPaused;
if (isPaused)
{
// 暂停
if (audioSource != null && audioSource.isPlaying) audioSource.Pause();
Debug.Log("[delayTapperPrefab] 暂停测试");
}
else
{
// 继续
if (audioSource != null) audioSource.UnPause();
Debug.Log("[delayTapperPrefab] 继续测试");
}
}
UpdateStartButtonText();
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(null);
}
}
private void OnDelayAmountEndEdit(string input)
{
if (isInternalSetting) return;
if (int.TryParse(input, out int value))
{
if (value < -1000 || value > 1000)
{
int clampedValue = Mathf.Clamp(value, -1000, 1000);
gNotice.warning.display($"延迟值超出范围 (-1000~1000),已重置为 {clampedValue}");
isInternalSetting = true;
delayAmountNow.text = clampedValue.ToString();
isInternalSetting = false;
}
}
else
{
gNotice.warning.display("非法的延迟值输入,已重置为 0");
isInternalSetting = true;
delayAmountNow.text = "0";
isInternalSetting = false;
}
}
private void OnModeChanged(int index)
{
// 0 = 8拍模式, 1 = 4拍模式 (由 Dropdown 设置顺序决定)
currentMode = (index == 1) ? TapperMode.FourBeats : TapperMode.EightBeats;
// 如果是 4 拍模式,将 4 张图 alpha 设置为 0
if (modeSpecificImages != null)
{
foreach (var img in modeSpecificImages)
{
if (img != null)
{
Color c = img.color;
c.a = (currentMode == TapperMode.FourBeats) ? 0f : 1f;
img.color = c;
}
}
}
Debug.Log($"[delayTapperPrefab] 模式切换:{currentMode}");
// 切换模式时重置数据
StopAndResetTapper();
ClearTapperHandles();
}
private IEnumerator PrepareAudioAndEnableButton()
{
isAudioReady = false;
if (audioSource == null || audioSource.clip == null)
{
Debug.LogWarning("[delayTapperPrefab] 无音频源或片段");
isAudioReady = true;
yield break;
}
// 如果音频还未加载,则手动触发加载
if (audioSource.clip.loadState != AudioDataLoadState.Loaded)
{
Debug.Log($"[delayTapperPrefab] 正在加载音频: {audioSource.clip.name}...");
audioSource.clip.LoadAudioData();
// 等待加载完成
while (audioSource.clip.loadState == AudioDataLoadState.Loading)
{
yield return null;
}
if (audioSource.clip.loadState != AudioDataLoadState.Loaded)
{
Debug.LogError($"[delayTapperPrefab] 音频加载失败! 状态: {audioSource.clip.loadState}");
}
else
{
Debug.Log("[delayTapperPrefab] 音频加载成功");
isAudioReady = true;
}
}
else
{
Debug.Log("[delayTapperPrefab] 音频已在内存中");
isAudioReady = true;
}
}
private void OnInputFieldSelected()
{
if (isInternalSetting) return;
Debug.Log("[delayTapperPrefab] InputField 被选中,停止播放并重置");
StopAndResetTapper();
}
private void OnInputFieldChanged(string val)
{
if (isInternalSetting) return;
Debug.Log("[delayTapperPrefab] InputField 内容改变,停止播放并重置");
StopAndResetTapper();
}
public void StartTapperFromInput()
{
Debug.Log("[delayTapperPrefab] StartTapperFromInput 按钮被点击");
// 0. 点击后立刻取消按钮焦点,防止空格触发重复点击
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(null);
}
if (!isAudioReady)
{
gNotice.warning.display("音频未就绪");
return;
}
// 强制锁定 BPM 为 120
BPM = 120;
if (bpmInputField != null)
{
isInternalSetting = true;
bpmInputField.text = "120";
isInternalSetting = false;
}
// 点击启动按钮时,手动清理一次旧数据
ClearTapperHandles();
RunTapperProcess();
StartAudioPlayback();
}
private void StartAudioPlayback()
{
if (audioSource == null || audioSource.clip == null)
{
Debug.LogWarning("[delayTapperPrefab] AudioSource 或 AudioClip 未分配,跳过播放");
return;
}
// 停止当前可能正在运行的播放协程
if (audioCoroutine != null) StopCoroutine(audioCoroutine);
// 强制停止当前音频并直接跳转到指定时间,确保没有重音或残留播放
audioSource.Stop();
float start = Mathf.Clamp(musicStartTime, 0f, audioSource.clip.length);
audioSource.time = start;
audioCoroutine = StartCoroutine(AudioPlaybackRoutine());
}
private void Update()
{
// 如果暂停,不执行任何 Update 逻辑
if (isPaused) return;
// 改进监听逻辑:只要 Slider 正在滚动,就允许记录空格
if (sliderCoroutine != null && Input.GetKeyDown(KeyCode.Space))
{
SpawnTapperHandle();
}
}
private void SpawnTapperHandle()
{
Debug.Log("[delayTapperPrefab] SpawnTapperHandle 被调用");
if (runSlider == null) { Debug.LogError("runSlider 为空"); return; }
// 1. 直接基于当前时间(currentElapsed)计算偏移,不再依赖 Slider 的百分比,提高精度
float beatDuration = 60f / Mathf.Max(BPM, 1); // 每拍时长 (120BPM 为 0.5s)
float nearestBeatTime = Mathf.Round(currentElapsed / beatDuration) * beatDuration;
float offsetSeconds = currentElapsed - nearestBeatTime;
// 2. 记录差值到 List (Inspector 显示用,保留 ms 字符串格式)
string offsetStr = (offsetSeconds * 1000f).ToString("F2") + "ms";
tapValueOffsets.Add(offsetStr);
// 3. 计算平均偏移逻辑
tapCount++;
totalOffsetSum += offsetSeconds;
float averageOffsetSeconds = totalOffsetSum / tapCount;
int finalDelayMs = Mathf.RoundToInt(averageOffsetSeconds * 1000f);
// 4. 输出到 delayAmountNow
if (delayAmountNow != null)
{
isInternalSetting = true;
delayAmountNow.text = finalDelayMs.ToString();
isInternalSetting = false;
}
Debug.Log($"[delayTapperPrefab] 模式={currentMode}, 点击#{tapCount}: Time={currentElapsed:F3}s, NearestBeat={nearestBeatTime:F3}s, Offset={offsetSeconds*1000f:F2}ms, AvgDelay={finalDelayMs}ms");
if (tapperHandlePrefab == null || tapperHandlePrefab_toput == null) return;
// 获取 Slider 的 Handle
RectTransform handle = runSlider.handleRect;
if (handle == null) return;
// 实例化到指定父物体下
GameObject newHandle = Instantiate(tapperHandlePrefab, tapperHandlePrefab_toput.transform);
// 同步 Handle 的位置
newHandle.transform.position = handle.position;
newHandle.transform.rotation = handle.rotation;
// 播放点击音效
if (hit_sfx_as != null)
{
hit_sfx_as.Play();
}
}
private void ClearTapperHandles()
{
Debug.Log("[delayTapperPrefab] ClearTapperHandles 被显式调用,即将清空 List 和累计数据");
if (tapperHandlePrefab_toput == null) return;
int childCount = tapperHandlePrefab_toput.transform.childCount;
for (int i = childCount - 1; i >= 0; i--)
{
Destroy(tapperHandlePrefab_toput.transform.GetChild(i).gameObject);
}
// 重置所有累计数据
tapValueOffsets.Clear();
totalOffsetSum = 0f;
tapCount = 0;
Debug.Log("[delayTapperPrefab] 已手动清理 TapperHandles 和所有采样数据");
}
private IEnumerator AudioPlaybackRoutine()
{
// 再次确保开始时间不超过结束时间且在音频长度范围内
float start = Mathf.Clamp(musicStartTime, 0f, audioSource.clip.length);
float end = Mathf.Clamp(musicEndTime, start, audioSource.clip.length);
if (end <= start)
{
Debug.LogWarning("[delayTapperPrefab] 音乐结束时间小于或等于开始时间,无法播放");
yield break;
}
// 此时直接开始播放即可,因为在 StartAudioPlayback 中已经跳转过时间
audioSource.Play();
Debug.Log($"[delayTapperPrefab] 音频开始播放(跳转):{start}s -> {end}s");
while (audioSource.isPlaying && audioSource.time < end)
{
yield return null;
}
audioSource.Stop();
Debug.Log("[delayTapperPrefab] 音频播放结束,停止 Slider 并清理数据");
// 重置运行状态和按钮文本
isRunning = false;
isPaused = false;
UpdateStartButtonText();
// 音频停止时,同时停止并重置 Slider
StopAndResetTapper();
// 清理所有数据和 Prefab
ClearTapperHandles();
}
public void StopAndResetTapper()
{
if (sliderCoroutine != null)
{
Debug.Log("[delayTapperPrefab] 停止当前滚动协程");
StopCoroutine(sliderCoroutine);
sliderCoroutine = null;
}
if (audioCoroutine != null)
{
StopCoroutine(audioCoroutine);
audioCoroutine = null;
}
if (audioSource != null && audioSource.isPlaying)
{
audioSource.Stop();
}
if (runSlider != null)
{
runSlider.value = 0f;
}
}
public void RunTapperProcess()
{
Debug.Log("[delayTapperPrefab] RunTapperProcess 准备启动协程");
if (sliderCoroutine != null) StopCoroutine(sliderCoroutine);
sliderCoroutine = StartCoroutine(RunTapperProcessCoroutine());
}
private IEnumerator RunTapperProcessCoroutine()
{
if (runSlider == null)
{
Debug.LogError("[delayTapperPrefab] runSlider 为空,无法开始滚动!");
yield break;
}
// 强制确保 Slider 的范围是 0 到 1
runSlider.minValue = 0f;
runSlider.maxValue = 1f;
while (true)
{
// 每次循环开始时清理视觉 Prefab (如果不是第一次启动)
// 注意:这里我们只清理生成的 Handle,不重置数据,因为数据是跨循环累计的
ClearVisualHandlesOnly();
// 如果不是暂停后继续,则重置 currentElapsed
if (!isPaused) currentElapsed = 0f;
// 8拍模式走完 8 拍,4拍模式走完 4 拍
int beatsInCycle = (currentMode == TapperMode.FourBeats) ? 4 : 8;
float cycleDuration = (60f / Mathf.Max(BPM, 1)) * beatsInCycle;
Debug.Log($"[delayTapperPrefab] 开始 {beatsInCycle} 拍滚动,BPM: {BPM}, 单次循环时长: {cycleDuration}s");
while (currentElapsed < cycleDuration)
{
if (!isPaused)
{
currentElapsed += Time.deltaTime;
runSlider.value = Mathf.Clamp01(currentElapsed / cycleDuration);
}
yield return null;
}
currentElapsed = 0f;
runSlider.value = 0f;
}
}
private void ClearVisualHandlesOnly()
{
if (tapperHandlePrefab_toput == null) return;
int childCount = tapperHandlePrefab_toput.transform.childCount;
for (int i = childCount - 1; i >= 0; i--)
{
Destroy(tapperHandlePrefab_toput.transform.GetChild(i).gameObject);
}
}
public void on_close_img()
{
gNotice.recommendation.display("打开了!但是没有完全打开!", 2f, 2f);
}
}