长音符再修复 加入了关卡连接 优化了一些默认加载

This commit is contained in:
2026-01-13 11:22:39 +08:00
parent 2738089af1
commit 2f9d4aae0c
426 changed files with 299413 additions and 1050 deletions
+319 -73
View File
@@ -2,14 +2,15 @@ using System.IO;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.Networking;
public class GameManager : MonoBehaviour
{
public BeatmapManager beatmapManager; // 负责加载和管理谱面
public NoteSpawner noteSpawner; // 音符生成器
public AudioSource musicSource; // 音乐播放器
public BeatmapManager beatmapManager; // غ͹
public NoteSpawner noteSpawner; //
public AudioSource musicSource; // ֲ
[Header("paths")]
// 变量声明不再从静态变量中读取
// ٴӾ̬жȡ
public string JSONPath;
public string audioPath;
public string bgPicPath;
@@ -21,7 +22,15 @@ public class GameManager : MonoBehaviour
[Header("Debug / Test")]
public Text banflagTextUI;
[Header("UI Overlays")]
public UnityEngine.UI.Image blackMaskImage; // assign in inspector: full-screen black overlay
[Header("Playback")]
[Tooltip("Delay in seconds after unpausing (press Space) before starting audio and spawning. Configure in inspector.")]
public float playbackStartDelay = 3f;
private bool pauseSubscribed = false;
private bool musicWasPlayingBeforePause = false;
private void SubscribeToPauseManager()
{
@@ -60,37 +69,148 @@ public class GameManager : MonoBehaviour
{
if (musicSource != null)
{
if (isPaused)
try
{
musicSource.Pause();
if (isPaused)
{
// remember whether music was playing and pause it to stop timeline advancing
musicWasPlayingBeforePause = musicSource.isPlaying;
if (musicWasPlayingBeforePause)
{
musicSource.Pause();
}
}
else
{
// Do not change mute state here. External startup flow will unmute when appropriate.
// resume if it was playing before pause
if (musicWasPlayingBeforePause)
{
try { musicSource.UnPause(); } catch { }
}
// reset flag
musicWasPlayingBeforePause = false;
}
}
catch { }
}
}
/// <summary>
/// Fade out the black mask image over duration seconds (unscaled), then disable its raycast target so UI passes through.
/// </summary>
public IEnumerator FadeOutBlackMask(float duration)
{
if (blackMaskImage == null) yield break;
// ensure image active
if (!blackMaskImage.gameObject.activeSelf) blackMaskImage.gameObject.SetActive(true);
float t = 0f;
Color c = blackMaskImage.color;
float startA = c.a;
while (t < duration)
{
t += Time.unscaledDeltaTime;
float frac = Mathf.Clamp01(t / Mathf.Max(0.0001f, duration));
c.a = Mathf.Lerp(startA, 0f, frac);
blackMaskImage.color = c;
yield return null;
}
// ensure fully transparent
c.a = 0f;
blackMaskImage.color = c;
// disable raycast so underlying UI can receive input
try { blackMaskImage.raycastTarget = false; } catch { }
// optionally deactivate the overlay to save draw calls
blackMaskImage.gameObject.SetActive(false);
}
// Public helper to reliably unmute and start music playback, respecting delay.
public void PlayMusicWithDelay(float delaySeconds)
{
if (musicSource == null) return;
try
{
// Ensure unmuted
musicSource.mute = false;
}
catch { }
try
{
// If a delay is specified, stop current and schedule playback
if (delaySeconds > 0f)
{
// Stop any currently playing to avoid overlapping schedules
try { musicSource.Stop(); } catch { }
musicSource.PlayDelayed(delaySeconds);
Debug.Log($"GameManager.PlayMusicWithDelay: PlayDelayed({delaySeconds}) called");
}
else
{
musicSource.UnPause();
// If already playing but paused, try UnPause; otherwise Play
if (musicSource.isPlaying)
{
// already playing - nothing to do
Debug.Log("GameManager.PlayMusicWithDelay: musicSource already playing");
}
else
{
// Try UnPause first (in case it was paused), otherwise Play
try { musicSource.UnPause(); Debug.Log("GameManager.PlayMusicWithDelay: UnPause called"); }
catch { musicSource.Play(); Debug.Log("GameManager.PlayMusicWithDelay: Play called"); }
}
}
}
catch (System.Exception ex)
{
Debug.LogWarning($"PlayMusicWithDelay failed: {ex}");
try { musicSource.Play(); } catch { }
}
}
void Start()
{
Debug.Log("GameManager Start() 被调用");
Debug.Log("GameManager Start() ");
// 检查关键组件是否为空
if (beatmapManager == null) Debug.LogError("beatmapManager 未赋值!");
if (noteSpawner == null) Debug.LogError("noteSpawner 未赋值!");
if (musicSource == null) Debug.LogError("musicSource 未赋值!");
// ؼǷΪ
if (beatmapManager == null) Debug.LogError("beatmapManager δֵ");
if (noteSpawner == null) Debug.LogError("noteSpawner δֵ");
if (musicSource == null) Debug.LogError("musicSource δֵ");
// 如果 pressStart 已经读取了 banflag 内容,则把它显示在 UI(如果分配了)
// preload audio fully and start muted playback to warm up audio decoding
if (musicSource != null && musicSource.clip != null)
{
musicSource.mute = true;
musicSource.loop = false;
musicSource.playOnAwake = false;
try
{
musicSource.Play();
Debug.Log("GameManager: warmed up audio playback (muted)");
}
catch (System.Exception ex)
{
Debug.LogWarning("GameManager: failed to warm up audio: " + ex.Message);
}
}
// pressStart Ѿȡ banflag ݣʾ UIˣ
if (banflagTextUI != null && !string.IsNullOrEmpty(pressStart.banflagContent))
{
banflagTextUI.text = pressStart.banflagContent;
Debug.Log("Displayed banflag content from pressStart into banflagTextUI.");
}
// test mode 下使用外部路径预加载资源并暂停系统,等待按空格继续
// test mode ʹⲿ·ԤԴͣϵͳȴո
if (GameConfig.testMode)
{
// 延迟赋值:在 Start() 中获取静态路径,此时 pressStart.cs 已经运行并更新了它们
// ӳٸֵ Start() лȡ̬·ʱ pressStart.cs Ѿв
JSONPath = pressStart.testmode_beatmapJSON_path;
audioPath = pressStart.testmode_audioFile_path;
bgPicPath = pressStart.testmode_bgPicFile_path;
@@ -110,6 +230,22 @@ public class GameManager : MonoBehaviour
{
StartCoroutine(HandleNormalModeStartup());
}
// Ensure black mask blocks input and is fully opaque at scene start (will fade out after loading).
if (blackMaskImage != null)
{
try
{
Color cc = blackMaskImage.color;
cc.a = 1f;
blackMaskImage.color = cc;
blackMaskImage.gameObject.SetActive(true);
blackMaskImage.raycastTarget = true;
// start automatic fade from alpha=1 to 0 over 1 second after Start
StartCoroutine(FadeOutBlackMask(1f));
}
catch { }
}
}
private IEnumerator HandleTestModeStartup()
@@ -123,42 +259,92 @@ public class GameManager : MonoBehaviour
}
else
{
// 注意:这里仍然使用过时的 WWW,建议替换为 UnityWebRequest
// Use UnityWebRequestMultimedia instead of WWW which is deprecated and can be unreliable for file:// audio
string url = "file://" + audioPath;
using (var www = new WWW(url))
// determine audio type from extension for best compatibility
AudioType audioType = AudioType.UNKNOWN;
string ext = Path.GetExtension(audioPath)?.ToLowerInvariant();
if (!string.IsNullOrEmpty(ext))
{
yield return www;
if (!string.IsNullOrEmpty(www.error))
switch (ext)
{
Debug.LogError($"加载测试模式音频失败: {www.error}");
case ".wav": audioType = AudioType.WAV; break;
case ".ogg": audioType = AudioType.OGGVORBIS; break;
case ".mp3": audioType = AudioType.MPEG; break;
case ".aif":
case ".aiff": audioType = AudioType.AIFF; break;
default: audioType = AudioType.UNKNOWN; break;
}
}
using (var uwr = UnityWebRequestMultimedia.GetAudioClip(url, audioType))
{
yield return uwr.SendWebRequest();
#if UNITY_2020_1_OR_NEWER
if (uwr.result == UnityWebRequest.Result.ConnectionError || uwr.result == UnityWebRequest.Result.ProtocolError)
#else
if (uwr.isNetworkError || uwr.isHttpError)
#endif
{
Debug.LogError($"TestMode audio load failed: {uwr.error}");
}
else
{
try
AudioClip clip = DownloadHandlerAudioClip.GetContent(uwr);
if (clip == null)
{
AudioClip clip = www.GetAudioClip(false, false);
if (clip == null)
Debug.LogError("Failed to obtain AudioClip from downloaded data");
}
else
{
// wait for audio data to be loaded if necessary
float waitStart = Time.realtimeSinceStartup;
while (clip.loadState == AudioDataLoadState.Loading && Time.realtimeSinceStartup - waitStart < 5f)
{
Debug.LogError("从文件获取 AudioClip 失败");
yield return null;
}
if (clip.loadState != AudioDataLoadState.Loaded)
{
Debug.LogWarning($"AudioClip loadState is {clip.loadState}. It may still stream or be incomplete.");
}
if (musicSource == null)
{
Debug.LogError("musicSource is null - cannot assign TestMode audio");
}
else
{
clip.name = Path.GetFileName(audioPath);
musicSource.clip = clip;
musicSource.loop = false;
musicSource.playOnAwake = false;
// Warm up decoding by playing briefly muted; avoid try/catch around yield (not allowed).
musicSource.mute = true;
musicSource.Play();
// allow a few frames for audio system to start
yield return null;
musicSource.Stop();
Debug.Log("TestMode audio loaded into AudioSource");
// After loading audio, enable overlay and pause via PauseManager
var pm = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
if (pm != null)
{
pm.Pause(true);
Debug.Log("PauseManager.Pause(true) invoked after TestMode audio load");
}
}
}
catch (System.Exception ex)
{
Debug.LogError($"将音频设置到 AudioSource 时出错: {ex}");
}
}
}
}
}
else
{
Debug.LogWarning("TestMode audioPath 未配置。");
Debug.LogWarning("TestMode audioPath not provided");
}
// Preload background image into sprite renderer
@@ -179,7 +365,7 @@ public class GameManager : MonoBehaviour
}
catch (System.Exception ex)
{
Debug.LogError($"读取背景图片文件出错: {ex}");
Debug.LogError($"ȡͼƬļ: {ex}");
}
if (imgBytes != null)
@@ -190,29 +376,29 @@ public class GameManager : MonoBehaviour
Sprite s = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
backgroundRenderer.sprite = s;
// 【修改点:设置 SpriteRenderer 透明度为 255 (1.0f)
// ޸ĵ㣺 SpriteRenderer ͸Ϊ 255 (1.0f)
Color color = backgroundRenderer.color;
color.a = 1.0f;
backgroundRenderer.color = color;
// 保持比例不变的最大适应由场景布局决定,记录 assignment
// ֱӦɳ־¼ assignment
Debug.Log("Background sprite assigned (ensure your SpriteRenderer size fits scene)");
}
else
{
Debug.LogError($"无法从字节创建 Texture2D: {bgPicPath}");
Debug.LogError($"޷ֽڴ Texture2D: {bgPicPath}");
}
}
}
}
else
{
Debug.LogWarning("TestMode bgPicPath 未配置。");
Debug.LogWarning("TestMode bgPicPath δá");
}
}
else
{
Debug.LogWarning("backgroundRenderer 未配置,跳过背景加载。");
Debug.LogWarning("backgroundRenderer δãء");
}
// Read JSON from JSONPath and parse via BeatmapManager.ParseJsonOnly
@@ -231,14 +417,14 @@ public class GameManager : MonoBehaviour
}
catch (System.Exception ex)
{
Debug.LogError($"读取 TestMode JSON 文件出错: {ex}");
Debug.LogError($"ȡ TestMode JSON ļ: {ex}");
yield break;
}
bool parsed = beatmapManager.ParseJsonOnly(jsonContent);
if (!parsed)
{
Debug.LogError("ParseJsonOnly 失败,停止启动测试模式。");
Debug.LogError("ParseJsonOnly ʧܣֹͣģʽ");
yield break;
}
@@ -247,7 +433,7 @@ public class GameManager : MonoBehaviour
}
else
{
Debug.LogError("TestMode JSONPath 未配置。");
Debug.LogError("TestMode JSONPath δá");
yield break;
}
@@ -265,6 +451,8 @@ public class GameManager : MonoBehaviour
// Resume using PauseManager
PauseManager.Instance?.Pause(false);
// small buffer to avoid hitching immediately after unpause: wait configured realtime delay
yield return new WaitForSecondsRealtime(Mathf.Max(0f, playbackStartDelay));
// Start spawning using the parsed beatmap
if (beatmapManager.beatmap != null)
@@ -273,40 +461,93 @@ public class GameManager : MonoBehaviour
}
else
{
Debug.LogError("启动播放失败:beatmap 未解析或为空");
Debug.LogError("ʧܣbeatmap δΪ");
yield break;
}
// If audio clip was loaded into musicSource.clip, play now with delay from parsed globalDelaySeconds
float delaySeconds = beatmapManager != null ? beatmapManager.globalDelaySeconds : 0f;
if (musicSource.clip != null)
{
if (delaySeconds > 0f)
{
musicSource.PlayDelayed(delaySeconds);
Debug.Log($"Scheduled test-mode music to play after {delaySeconds} seconds.");
}
else
{
musicSource.Play();
}
}
else
{
Debug.LogWarning("TestMode: no audio clip loaded into AudioSource; skipping play.");
}
PlayMusicWithDelay(delaySeconds);
UpdateStatusOnConsole("Playback started");
}
private IEnumerator HandleNormalModeStartup()
{
// 加载谱面但不生成音符
// If a Beatmap was already provided by previous scene (pendingSongData path), skip loading default JSON
if (beatmapManager != null && beatmapManager.beatmap != null)
{
Debug.Log("HandleNormalModeStartup: existing beatmap found on BeatmapManager; skipping default JSON load.");
// Pause the system until Space pressed
SubscribeToPauseManager();
PauseManager.Instance?.Pause(true);
UpdateStatusOnConsole("Paused: press Space to start playback");
// Wait for space
while (!Input.GetKeyDown(KeyCode.Space))
{
yield return null;
}
// Resume
PauseManager.Instance?.Pause(false);
// small buffer to avoid hitching immediately after unpause: wait configured realtime delay
yield return new WaitForSecondsRealtime(Mathf.Max(0f, playbackStartDelay));
// Start spawning using the existing parsed beatmap
if (beatmapManager.beatmap != null)
{
noteSpawner.LoadBeatmap(beatmapManager.beatmap);
}
else
{
Debug.LogError("HandleNormalModeStartup: beatmap was expected but is null");
yield break;
}
// Play background music if assigned
if (musicSource != null && musicSource.clip != null)
{
float delay = beatmapManager != null ? beatmapManager.globalDelaySeconds : 0f;
PlayMusicWithDelay(delay);
}
else
{
// try load via parsedMusicFile if available
if (beatmapManager != null && !string.IsNullOrEmpty(beatmapManager.parsedMusicFile))
{
AudioClip musicClip = Resources.Load<AudioClip>(beatmapManager.parsedMusicFile);
if (musicClip != null)
{
musicSource.clip = musicClip;
// ensure we don't accidentally autoplay when assigning clips
musicSource.playOnAwake = false;
musicSource.Stop();
float delay = beatmapManager.globalDelaySeconds;
PlayMusicWithDelay(delay);
// ensure overlay shows and pause logic runs after loading the clip
var pm2 = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
if (pm2 != null)
{
pm2.Pause(true);
Debug.Log("PauseManager.Pause(true) invoked after assigning musicClip in normal startup");
}
}
}
}
UpdateStatusOnConsole("Playback started");
yield break;
}
// ԭ̣
string beatmapFilePath = Path.Combine(Application.streamingAssetsPath, "Emilia_demo.json");
if (!File.Exists(beatmapFilePath))
{
Debug.LogError($"谱面文件不存在: {beatmapFilePath}");
Debug.LogError($"ļ: {beatmapFilePath}");
yield break;
}
@@ -314,61 +555,66 @@ public class GameManager : MonoBehaviour
bool parsed = beatmapManager.ParseJsonOnly(json);
if (!parsed)
{
Debug.LogError("ParseJsonOnly 失败");
Debug.LogError("ParseJsonOnly ʧ");
yield break;
}
// 暂停系统
// ͣϵͳ
SubscribeToPauseManager();
PauseManager.Instance?.Pause(true);
UpdateStatusOnConsole("Paused: press Space to start playback");
// 等待空格
// ȴո
while (!Input.GetKeyDown(KeyCode.Space))
{
yield return null;
}
// 恢复
// ָ
PauseManager.Instance?.Pause(false);
// small buffer to avoid hitching immediately after unpause: wait configured realtime delay
yield return new WaitForSecondsRealtime(Mathf.Max(0f, playbackStartDelay));
// 生成音符
//
if (beatmapManager.beatmap != null)
{
noteSpawner.LoadBeatmap(beatmapManager.beatmap);
}
else
{
Debug.LogError("beatmap 未解析");
Debug.LogError("beatmap δ");
yield break;
}
// 播放背景音乐(延迟播放由 beatmapManager.globalDelaySeconds 决定)
// ű֣ӳٲ beatmapManager.globalDelaySeconds
if (!string.IsNullOrEmpty(beatmapManager.parsedMusicFile))
{
AudioClip musicClip = Resources.Load<AudioClip>(beatmapManager.parsedMusicFile);
if (musicClip == null)
{
Debug.LogError($"音乐文件加载失败: {beatmapManager.parsedMusicFile}");
Debug.LogError($"ļʧ: {beatmapManager.parsedMusicFile}");
}
else
{
musicSource.clip = musicClip;
// ensure we don't accidentally autoplay when assigning clips
musicSource.playOnAwake = false;
musicSource.Stop();
float delay = beatmapManager.globalDelaySeconds;
if (delay > 0f)
PlayMusicWithDelay(delay);
// After assigning clip, enable overlay and pause
var pm3 = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
if (pm3 != null)
{
musicSource.PlayDelayed(delay);
Debug.Log($"Scheduled music to play after {delay} seconds.");
}
else
{
musicSource.Play();
pm3.Pause(true);
Debug.Log("PauseManager.Pause(true) invoked after assigning parsedMusicFile clip");
}
}
}
else
{
Debug.LogError("谱面中 musicFile 为空!");
Debug.LogError(" musicFile Ϊգ");
}
UpdateStatusOnConsole("Playback started");