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(); if (pm != null) { pm.OnPauseStateChanged += HandlePauseStateChanged; pauseSubscribed = true; } } private void UnsubscribeFromPauseManager() { if (!pauseSubscribed) return; var pm = PauseManager.Instance ?? FindObjectOfType(); 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 { } } } /// /// Fade out the black mask image over duration seconds (unscaled), then disable its raycast target so UI passes through. /// 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(); 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(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(); 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(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(); 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); } }