627 lines
24 KiB
C#
627 lines
24 KiB
C#
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; // ���ֲ�����
|
||
[Header("paths")]
|
||
// �����������ٴӾ�̬�����ж�ȡ
|
||
public string JSONPath;
|
||
public string audioPath;
|
||
public string bgPicPath;
|
||
|
||
// Optional: sprite renderer to show background image
|
||
public SpriteRenderer backgroundRenderer;
|
||
|
||
// NEW: UI Text to display banflag content from pressStart
|
||
[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()
|
||
{
|
||
if (pauseSubscribed) return;
|
||
// prefer the singleton, but try to find in scene if null
|
||
var pm = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
|
||
if (pm != null)
|
||
{
|
||
pm.OnPauseStateChanged += HandlePauseStateChanged;
|
||
pauseSubscribed = true;
|
||
}
|
||
}
|
||
|
||
private void UnsubscribeFromPauseManager()
|
||
{
|
||
if (!pauseSubscribed) return;
|
||
var pm = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
|
||
if (pm != null)
|
||
{
|
||
pm.OnPauseStateChanged -= HandlePauseStateChanged;
|
||
}
|
||
pauseSubscribed = false;
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
SubscribeToPauseManager();
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
UnsubscribeFromPauseManager();
|
||
}
|
||
|
||
private void HandlePauseStateChanged(bool isPaused)
|
||
{
|
||
if (musicSource != null)
|
||
{
|
||
try
|
||
{
|
||
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
|
||
{
|
||
// 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() ������");
|
||
|
||
// ���ؼ�����Ƿ�Ϊ��
|
||
if (beatmapManager == null) Debug.LogError("beatmapManager δ��ֵ��");
|
||
if (noteSpawner == null) Debug.LogError("noteSpawner δ��ֵ��");
|
||
if (musicSource == null) Debug.LogError("musicSource δ��ֵ��");
|
||
|
||
// 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 ��ʹ���ⲿ·��Ԥ������Դ����ͣϵͳ���ȴ����ո����
|
||
if (GameConfig.testMode)
|
||
{
|
||
// �ӳٸ�ֵ���� Start() �л�ȡ��̬·������ʱ pressStart.cs �Ѿ����в�����������
|
||
JSONPath = pressStart.testmode_beatmapJSON_path;
|
||
audioPath = pressStart.testmode_audioFile_path;
|
||
bgPicPath = pressStart.testmode_bgPicFile_path;
|
||
|
||
Debug.Log("Test mode active: using external paths to preload assets");
|
||
|
||
// also update banflag UI if content exists
|
||
if (banflagTextUI != null && !string.IsNullOrEmpty(pressStart.banflagContent))
|
||
{
|
||
banflagTextUI.text = pressStart.banflagContent;
|
||
}
|
||
|
||
StartCoroutine(HandleTestModeStartup());
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
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()
|
||
{
|
||
// Preload audio from audioPath (absolute)
|
||
if (!string.IsNullOrEmpty(audioPath))
|
||
{
|
||
if (!File.Exists(audioPath))
|
||
{
|
||
Debug.LogError($"TestMode audio file not found: {audioPath}");
|
||
}
|
||
else
|
||
{
|
||
// Use UnityWebRequestMultimedia instead of WWW which is deprecated and can be unreliable for file:// audio
|
||
string url = "file://" + audioPath;
|
||
|
||
// determine audio type from extension for best compatibility
|
||
AudioType audioType = AudioType.UNKNOWN;
|
||
string ext = Path.GetExtension(audioPath)?.ToLowerInvariant();
|
||
if (!string.IsNullOrEmpty(ext))
|
||
{
|
||
switch (ext)
|
||
{
|
||
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
|
||
{
|
||
AudioClip clip = DownloadHandlerAudioClip.GetContent(uwr);
|
||
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)
|
||
{
|
||
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");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("TestMode audioPath not provided");
|
||
}
|
||
|
||
// Preload background image into sprite renderer
|
||
if (backgroundRenderer != null)
|
||
{
|
||
if (!string.IsNullOrEmpty(bgPicPath))
|
||
{
|
||
if (!File.Exists(bgPicPath))
|
||
{
|
||
Debug.LogError($"TestMode background image not found: {bgPicPath}");
|
||
}
|
||
else
|
||
{
|
||
byte[] imgBytes = null;
|
||
try
|
||
{
|
||
imgBytes = File.ReadAllBytes(bgPicPath);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"��ȡ����ͼƬ�ļ�����: {ex}");
|
||
}
|
||
|
||
if (imgBytes != null)
|
||
{
|
||
Texture2D tex = new Texture2D(2, 2);
|
||
if (tex.LoadImage(imgBytes))
|
||
{
|
||
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)��
|
||
Color color = backgroundRenderer.color;
|
||
color.a = 1.0f;
|
||
backgroundRenderer.color = color;
|
||
|
||
// ���ֱ�������������Ӧ�ɳ������־�������¼ assignment
|
||
Debug.Log("Background sprite assigned (ensure your SpriteRenderer size fits scene)");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"�����ֽڴ��� Texture2D: {bgPicPath}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("TestMode bgPicPath δ���á�");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("backgroundRenderer δ���ã������������ء�");
|
||
}
|
||
|
||
// Read JSON from JSONPath and parse via BeatmapManager.ParseJsonOnly
|
||
if (!string.IsNullOrEmpty(JSONPath))
|
||
{
|
||
if (!File.Exists(JSONPath))
|
||
{
|
||
Debug.LogError($"TestMode beatmap JSON not found: {JSONPath}");
|
||
yield break; // abort
|
||
}
|
||
|
||
string jsonContent = null;
|
||
try
|
||
{
|
||
jsonContent = File.ReadAllText(JSONPath);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"��ȡ TestMode JSON �ļ�����: {ex}");
|
||
yield break;
|
||
}
|
||
|
||
bool parsed = beatmapManager.ParseJsonOnly(jsonContent);
|
||
if (!parsed)
|
||
{
|
||
Debug.LogError("ParseJsonOnly ʧ�ܣ�ֹͣ��������ģʽ��");
|
||
yield break;
|
||
}
|
||
|
||
// Do not start spawning yet. We will start after unpausing.
|
||
Debug.Log("Beatmap parsed (deferred start). Ready to unpause.");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("TestMode JSONPath δ���á�");
|
||
yield break;
|
||
}
|
||
|
||
// Pause the system until Space pressed
|
||
// Ensure we are subscribed before invoking pause so we track audio state
|
||
SubscribeToPauseManager();
|
||
PauseManager.Instance?.Pause(true);
|
||
UpdateStatusOnConsole("Paused: press Space to start playback");
|
||
|
||
// Wait for space key down (use unscaled input loop)
|
||
while (!Input.GetKeyDown(KeyCode.Space))
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
beatmapManager.LoadBeatmap(beatmapManager.beatmap);
|
||
}
|
||
else
|
||
{
|
||
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;
|
||
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}");
|
||
yield break;
|
||
}
|
||
|
||
string json = File.ReadAllText(beatmapFilePath);
|
||
bool parsed = beatmapManager.ParseJsonOnly(json);
|
||
if (!parsed)
|
||
{
|
||
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 ���");
|
||
yield break;
|
||
}
|
||
|
||
// ���ű������֣��ӳٲ����� beatmapManager.globalDelaySeconds ������
|
||
if (!string.IsNullOrEmpty(beatmapManager.parsedMusicFile))
|
||
{
|
||
AudioClip musicClip = Resources.Load<AudioClip>(beatmapManager.parsedMusicFile);
|
||
if (musicClip == null)
|
||
{
|
||
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;
|
||
PlayMusicWithDelay(delay);
|
||
|
||
// After assigning clip, enable overlay and pause
|
||
var pm3 = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
|
||
if (pm3 != null)
|
||
{
|
||
pm3.Pause(true);
|
||
Debug.Log("PauseManager.Pause(true) invoked after assigning parsedMusicFile clip");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("������ musicFile Ϊ�գ�");
|
||
}
|
||
|
||
UpdateStatusOnConsole("Playback started");
|
||
}
|
||
|
||
private void UpdateStatusOnConsole(string message)
|
||
{
|
||
Debug.Log(message);
|
||
}
|
||
} |