using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using System.IO; using System.Text; using System.Text.RegularExpressions; using UnityEngine.UI; using TMPro; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using Bansonic; public class pressStart : MonoBehaviour { public string sceneName_toNextScene = "selectYourSongFirst"; public string testModeSceneName = "gamePlay_gamePlay"; [SerializeField] private string banflag_filePath = "C:/ProgramData/Bansonic/Caches/Bansonic_TestOutput"; private bool banflag_exists; public static string testmode_audioFile_path; public static string testmode_bgPicFile_path; public static string testmode_beatmapJSON_path; // new: store banflag content for other scenes to read public static string banflagContent = string.Empty; // UI text to show status public Text statusText; public Text warningText; [Header("New UI Buttons")] public Button startButton; public Button helpButton; public Button closeButton; public Button exitButton; public GameObject helpContentObject; [Header("Fade Settings")] public CanvasGroup mainCanvasGroup; public float fadeDelay = 0f; public float fadeDuration = 1f; [Header("TMP Flashing Settings")] public TextMeshProUGUI flashingText; public float flashInterval = 0.5f; [Header("Start Sequence Settings")] public Volume globalVolume; public Image fadeOutImage; public float startButtonDelay = 2f; // UI starts showing after 2 seconds; allow clicking then. [Header("UI_UI Jump Blocker")] public bool blockUIUISceneJump = false; public string blockUIUISceneJumpMessage = "暂不可进入。"; // polling private Coroutine pollCoroutine; private float pollInterval = 3f; private UI_PanelPopupTween helpPopupTween; // Start is called before the first frame update void Start() { // sceneName_toNextScene = "Songs_Select"; banflag_exists = false; testmode_audioFile_path = string.Empty; testmode_bgPicFile_path = string.Empty; testmode_beatmapJSON_path = string.Empty; banflagContent = string.Empty; // Ensure status text shows idle UpdateStatus(LocalizationService.Get("press_start.idle", "Press Enter to continue."), Color.green); // Bind buttons if (startButton != null) { startButton.onClick.AddListener(OnStartButtonClick); } if (helpButton != null) { helpButton.onClick.AddListener(OnHelpButtonClick); } if (closeButton != null) { closeButton.onClick.AddListener(OnCloseButtonClick); } if (exitButton != null) { exitButton.onClick.AddListener(OnExitButtonClick); } if (helpContentObject != null) { helpPopupTween = helpContentObject.GetComponent(); if (helpPopupTween == null) { helpPopupTween = helpContentObject.AddComponent(); } helpContentObject.SetActive(false); } // Initialize fadeOutImage if (fadeOutImage != null) { Color c = fadeOutImage.color; c.a = 0f; fadeOutImage.color = c; fadeOutImage.gameObject.SetActive(false); } // Initialize Global Volume Bloom intensity to 0 if (globalVolume != null && globalVolume.profile.TryGet(out var bloom)) { bloom.intensity.Override(0f); } // Initialize CanvasGroup and start fade-in if (mainCanvasGroup != null) { mainCanvasGroup.alpha = 0f; StartCoroutine(FadeInMainCanvasGroup()); } // Start flashing logic if (flashingText != null) { StartCoroutine(FlashTMPText()); } // Start button delay logic if (startButton != null) { StartCoroutine(StartButtonActivationDelay()); } } private IEnumerator StartButtonActivationDelay() { if (startButton == null) yield break; // 初始禁用按钮 startButton.interactable = false; // Keep the click lock aligned with when the main UI starts appearing. float activationDelay = Mathf.Max(0f, startButtonDelay); if (mainCanvasGroup != null) { activationDelay = Mathf.Min(activationDelay, Mathf.Max(0f, fadeDelay)); } if (activationDelay > 0f) { yield return new WaitForSecondsRealtime(activationDelay); } // 启用按钮 startButton.interactable = true; Debug.Log("[pressStart] Start button is now active."); } private IEnumerator FlashTMPText() { if (flashingText == null) yield break; float elapsed = 0f; while (true) { elapsed += Time.deltaTime; // 使用 Mathf.PingPong 在 0 到 1 之间往复 // 除以 flashInterval 可以控制循环的速度 float alpha = Mathf.PingPong(elapsed / flashInterval, 1f); Color color = flashingText.color; color.a = alpha; flashingText.color = color; yield return null; } } private IEnumerator FadeInMainCanvasGroup() { if (fadeDelay > 0) yield return new WaitForSeconds(fadeDelay); float elapsed = 0f; while (elapsed < fadeDuration) { elapsed += Time.deltaTime; mainCanvasGroup.alpha = Mathf.Lerp(0f, 1f, elapsed / fadeDuration); yield return null; } mainCanvasGroup.alpha = 1f; } private void OnStartButtonClick() { Debug.Log("[pressStart] Start button clicked."); HandleGameStartLogic(); } private void OnHelpButtonClick() { if (helpContentObject != null) { bool isActive = helpContentObject.activeSelf; if (isActive) { HideHelpContentWithExitAnim(); } else { if (helpPopupTween == null) { helpPopupTween = helpContentObject.GetComponent(); if (helpPopupTween == null) { helpPopupTween = helpContentObject.AddComponent(); } } helpContentObject.SetActive(true); } Debug.Log("[pressStart] Help button clicked. Help content active: " + !isActive); } } private void OnCloseButtonClick() { if (helpContentObject != null) { HideHelpContentWithExitAnim(); Debug.Log("[pressStart] Close button clicked. Help content hidden."); } } private void HideHelpContentWithExitAnim() { if (helpContentObject == null || !helpContentObject.activeInHierarchy) { return; } UI_PanelExitUtility.PlayExitThen(helpContentObject, () => { if (helpContentObject != null) { helpContentObject.SetActive(false); } }); } private void OnExitButtonClick() { Debug.Log("[pressStart] Exit button clicked. Application quitting."); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #else Application.Quit(); #endif } // Update is called once per frame private bool check_enterNextScene = false; private static readonly string WARNING_TEXT_CONTENT = "当前处于测试阶段未授权版本版本号:v1.0.0,请注意风险。"; void Update() { if (!(Input.GetKeyDown(KeyCode.Return) || Input.GetKeyDown(KeyCode.KeypadEnter))) return; // 如果按钮未激活 (还在倒计时),不允许通过键盘进入 if (startButton != null && !startButton.interactable) { Debug.Log("[pressStart] Start request ignored. Button is not active yet."); return; } HandleGameStartLogic(); } private void HandleGameStartLogic() { bool requestTestMode = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); if (requestTestMode) { Debug.Log("[pressStart] Enter+Shift or button start requested, starting test mode polling."); GameConfig.testMode = true; if (pollCoroutine != null) StopCoroutine(pollCoroutine); pollCoroutine = StartCoroutine(PollForBanflagAndProceed()); return; } // 修改为点击一次即可开始进入游戏序列 Debug.Log("[pressStart] Start requested, initiating transition sequence."); GameConfig.testMode = false; if (blockUIUISceneJump) { string message = string.IsNullOrWhiteSpace(blockUIUISceneJumpMessage) ? "暂不可进入。" : blockUIUISceneJumpMessage; gNotice.error.display(message); Debug.LogWarning("[pressStart] UI_UI scene jump blocked: " + message); return; } if (warningText != null) { warningText.gameObject.SetActive(true); warningText.text = LocalizationService.Get("press_start.warning_content", WARNING_TEXT_CONTENT); } // 启动进入游戏的异步序列 StartCoroutine(StartGameTransitionSequence()); } private IEnumerator StartGameTransitionSequence() { // 1. 0.5秒内将 Global Volume 的 Bloom Intensity 从 0 变到 3 if (globalVolume != null && globalVolume.profile.TryGet(out var bloom)) { float elapsed = 0f; float duration = 0.5f; float startVal = bloom.intensity.value; float targetVal = 3f; while (elapsed < duration) { elapsed += Time.deltaTime; bloom.intensity.Override(Mathf.Lerp(startVal, targetVal, elapsed / duration)); yield return null; } bloom.intensity.Override(targetVal); } // 2. 结束变 3 后,将 Image 的不透明度在 0.5 秒内从 0 变到 255 (alpha = 1) if (fadeOutImage != null) { fadeOutImage.gameObject.SetActive(true); float elapsed = 0f; float duration = 0.5f; Color color = fadeOutImage.color; while (elapsed < duration) { elapsed += Time.deltaTime; color.a = Mathf.Lerp(0f, 1f, elapsed / duration); fadeOutImage.color = color; yield return null; } color.a = 1f; fadeOutImage.color = color; } // 3. 切换场景 Debug.Log("[pressStart] Transition sequence complete. Loading UI_UI scene."); Time.timeScale = 1f; if (!gTransition.LoadScene("UI_UI", LoadSceneMode.Single)) { SceneManager.LoadScene("UI_UI"); } } private IEnumerator PollForBanflagAndProceed() { UpdateStatus(LocalizationService.Get("press_start.waiting_banflag", "Waiting for banflag..."), new Color(1f, 0.5f, 0f)); // orange while (true) { bool found = check_banflag_testBanflag(); if (found) { UpdateStatus(LocalizationService.Get("press_start.loading_test_mode", "Banflag detected. Loading test mode..."), Color.green); // small delay to show status yield return new WaitForSeconds(0.5f); if (gTransition.LoadScene(testModeSceneName, LoadSceneMode.Single)) { while (gTransition.IsBusy) { yield return null; } yield break; } AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(testModeSceneName); while (asyncLoad != null && !asyncLoad.isDone) { yield return null; } yield break; } else { UpdateStatus(LocalizationService.Get("press_start.waiting_banflag", "Waiting for banflag..."), new Color(1f, 0.5f, 0f)); // orange } yield return new WaitForSeconds(pollInterval); } } private IEnumerator LoadSceneAsync(string sceneName) { if (gTransition.LoadScene(sceneName, LoadSceneMode.Single)) { while (gTransition.IsBusy) { yield return null; } yield break; } AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (asyncLoad != null && !asyncLoad.isDone) { yield return null; } } private void UpdateStatus(string message, Color color) { if (statusText != null) { statusText.text = message; statusText.color = color; } Debug.Log(message); } private string NormalizeToForwardSlashes(string path) { if (string.IsNullOrEmpty(path)) return string.Empty; try { // Get full absolute path to remove any relative segments string full = Path.GetFullPath(path); // Replace backslashes with forward slashes string fwd = full.Replace('\\', '/'); // Collapse multiple slashes into single string collapsed = Regex.Replace(fwd, "/{2,}", "/"); // Remove trailing slash except when path is root like "C:/" if (collapsed.Length > 3 && collapsed.EndsWith("/")) collapsed = collapsed.TrimEnd('/'); return collapsed; } catch { // Fallback: replace backslashes and collapse string f = path.Replace('\\', '/'); string c = Regex.Replace(f, "/{2,}", "/"); if (c.Length > 3 && c.EndsWith("/")) c = c.TrimEnd('/'); return c; } } // returns true if banflag detected private bool check_banflag_testBanflag() { try { // reset previous results banflag_exists = false; testmode_audioFile_path = string.Empty; testmode_bgPicFile_path = string.Empty; testmode_beatmapJSON_path = string.Empty; banflagContent = string.Empty; if (string.IsNullOrEmpty(banflag_filePath)) { Debug.LogWarning("banflag_filePath 未设置"); UpdateStatus(LocalizationService.Get("press_start.error_path_not_configured", "Error: banflag path not configured"), Color.red); return false; } string filePath = Path.Combine(banflag_filePath, "test.banflag"); if (!File.Exists(filePath)) { Debug.LogWarning($"未找到对应 banflag 文件: {filePath}"); return false; } // mark exists banflag_exists = true; byte[] data = File.ReadAllBytes(filePath); if (data == null || data.Length == 0) { Debug.LogWarning($"banflag 文件为空: {filePath}"); } else { // Documentation text normalized. string text = null; try { text = Encoding.UTF8.GetString(data); } catch { text = null; } bool looksLikeText = false; if (!string.IsNullOrEmpty(text)) { // Documentation text normalized. int nonPrintable = 0; int checkLen = Mathf.Min(256, text.Length); for (int i = 0; i < checkLen; i++) { char c = text[i]; if (char.IsControl(c) && c != '\r' && c != '\n' && c != '\t') nonPrintable++; } looksLikeText = (nonPrintable == 0); } if (looksLikeText) { Debug.Log($"已读取 banflag 文件: {filePath}\n内容:\n{text}"); // store the readable content for other scenes banflagContent = text; } else { // Documentation text normalized. string base64 = System.Convert.ToBase64String(data); Debug.Log($"已读取 banflag 文件(二进制): {filePath},长度={data.Length} bytes,Base64 前200字符预览:\n{(base64.Length > 200 ? base64.Substring(0,200) + "..." : base64)}"); banflagContent = $"[Binary Data] Length: {data.Length} bytes"; } } // Documentation text normalized. string dir = Path.GetDirectoryName(filePath); if (string.IsNullOrEmpty(dir)) dir = banflag_filePath; // find JSON string foundJson = string.Empty; try { string[] jsons = Directory.GetFiles(dir, "*.json", SearchOption.TopDirectoryOnly); if (jsons != null && jsons.Length > 0) { foundJson = jsons[0]; } } catch { } // find audio: mp3, wav, ogg (in that priority) string foundAudio = string.Empty; try { string[] exts = new string[] { "*.mp3", "*.wav", "*.ogg" }; foreach (var pat in exts) { string[] matches = Directory.GetFiles(dir, pat, SearchOption.TopDirectoryOnly); if (matches != null && matches.Length > 0) { foundAudio = matches[0]; break; } } } catch { } // find image: jpg/jpeg, png (priority jpg then png) string foundImage = string.Empty; try { string[] imgExts = new string[] { "*.jpg", "*.jpeg", "*.png" }; foreach (var pat in imgExts) { string[] matches = Directory.GetFiles(dir, pat, SearchOption.TopDirectoryOnly); if (matches != null && matches.Length > 0) { foundImage = matches[0]; break; } } } catch { } // normalize and assign testmode_beatmapJSON_path = string.IsNullOrEmpty(foundJson) ? string.Empty : NormalizeToForwardSlashes(foundJson); testmode_audioFile_path = string.IsNullOrEmpty(foundAudio) ? string.Empty : NormalizeToForwardSlashes(foundAudio); testmode_bgPicFile_path = string.IsNullOrEmpty(foundImage) ? string.Empty : NormalizeToForwardSlashes(foundImage); // Debug the normalized paths Debug.Log($"banflag_exists={banflag_exists}"); Debug.Log($"testmode_beatmapJSON_path = {testmode_beatmapJSON_path}"); Debug.Log($"testmode_audioFile_path = {testmode_audioFile_path}"); Debug.Log($"testmode_bgPicFile_path = {testmode_bgPicFile_path}"); UpdateStatus(LocalizationService.Get("press_start.banflag_found", "Banflag found"), Color.green); return banflag_exists; } catch (System.Exception ex) { Debug.LogError($"读取 banflag 文件时发生错误: {ex}"); UpdateStatus(LocalizationService.Get("press_start.error_reading_banflag", "Error reading banflag"), Color.red); // ensure flags cleared on error banflag_exists = false; testmode_audioFile_path = string.Empty; testmode_bgPicFile_path = string.Empty; testmode_beatmapJSON_path = string.Empty; banflagContent = string.Empty; return false; } } }