720 lines
23 KiB
C#
720 lines
23 KiB
C#
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 Dictionary<AudioSource, bool> originalMuteStates = new Dictionary<AudioSource, bool>();
|
|
|
|
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());
|
|
|
|
// 查找场景中所有的 AudioSource 并将非指定的 AudioSource 静音
|
|
MuteOtherAudioSources();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
// 销毁时恢复音频源状态
|
|
RestoreAudioSources();
|
|
}
|
|
|
|
private void MuteOtherAudioSources()
|
|
{
|
|
originalMuteStates.Clear();
|
|
// 查找场景中所有的 AudioSource (包括非激活的)
|
|
AudioSource[] allAudioSources = FindObjectsOfType<AudioSource>(true);
|
|
foreach (var asrc in allAudioSources)
|
|
{
|
|
// 如果是指定的两个音频源之一,不静音
|
|
if (asrc == audioSource || asrc == hit_sfx_as)
|
|
{
|
|
// 确保它们是取消静音的,以便能听到声音
|
|
asrc.mute = false;
|
|
continue;
|
|
}
|
|
|
|
// 记录原始静音状态
|
|
if (!originalMuteStates.ContainsKey(asrc))
|
|
{
|
|
originalMuteStates[asrc] = asrc.mute;
|
|
asrc.mute = true; // 静音
|
|
}
|
|
}
|
|
Debug.Log($"[delayTapperPrefab] 已静音 {originalMuteStates.Count} 个音频源");
|
|
}
|
|
|
|
private void RestoreAudioSources()
|
|
{
|
|
int restoredCount = 0;
|
|
foreach (var kvp in originalMuteStates)
|
|
{
|
|
if (kvp.Key != null)
|
|
{
|
|
kvp.Key.mute = kvp.Value;
|
|
restoredCount++;
|
|
}
|
|
}
|
|
originalMuteStates.Clear();
|
|
Debug.Log($"[delayTapperPrefab] 已恢复 {restoredCount} 个音频源的静音状态");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|