超大量的更新 修复很多问题,gameplay特效初步

This commit is contained in:
FloatGaming
2026-02-14 23:46:20 +08:00
parent ef8eb67259
commit 3a7a0b4669
360 changed files with 85670 additions and 4144 deletions
+615 -25
View File
@@ -1,43 +1,88 @@
using UnityEngine;
using UnityEngine;
using System;
using System.Collections;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// Simple pause manager. Call Pause(true) to pause or Pause(false) to resume.
/// - overlayRoot: GameObject that will be enabled when paused and disabled when resumed.
/// - Pressing Escape toggles pause while the manager exists.
/// Central gameplay pause manager.
/// - ESC / top-right Pause button can pause only in gameplay scene.
/// - Continue: hide pause panel, replay 3-2-1-Go, then resume chart + music.
/// - Replay: reload current gameplay scene (with current selected song/difficulty if available).
/// - Exit: return to selectYourSongFirst.
/// </summary>
public class PauseManager : MonoBehaviour
{
public static PauseManager Instance { get; private set; }
[Header("Overlay")]
public GameObject overlayRoot; // object to enable/disable on pause
public GameObject overlayRoot;
[SerializeField] private float overlayFadeDuration = 0.2f;
[SerializeField] private bool preferPausePrefabOverWhiteScreen = true;
[SerializeField] private string legacyWhiteScreenRootName = "pauseWhiteScreen";
[Header("Gameplay Scope")]
[SerializeField] private bool onlyInGameplayScene = true;
[SerializeField] private string gameplaySceneName = "gamePlay_gamePlay";
[SerializeField] private bool requirePlaybackStarted = true;
[Header("Buttons")]
[SerializeField] private string pauseButtonScenePath = "artworks/NEW UI/zhuangshi (3)/Pause";
[SerializeField] private Button pauseTriggerButton;
[SerializeField] private Button continueButton;
[SerializeField] private Button replayButton;
[SerializeField] private Button exitButton;
[SerializeField] private Button settingsButton;
[Header("Pause Entry Animation")]
[SerializeField] private bool playPauseEntryAnimation = true;
[SerializeField] private float pauseItemEnterDuration = 0.18f;
[SerializeField] private float pauseItemEnterStagger = 0.016f;
public bool IsPaused { get; private set; } = false;
// ͣ״̬ı¼
public event Action<bool> OnPauseStateChanged;
private CanvasGroup overlayCanvasGroup;
private RectTransform overlayPanelRoot;
private CanvasGroup overlayPanelCanvasGroup;
private Vector3 panelShownScale = Vector3.zero;
private static readonly Vector3 PanelHiddenScale = Vector3.zero;
private Coroutine overlayFadeCoroutine;
private Coroutine continueCoroutine;
private bool buttonsBound = false;
private float nextBindRetryTime = 0f;
private const string ExitSceneName = "selectYourSongFirst";
void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
else
{
Destroy(gameObject);
return;
}
}
void Start()
{
if (overlayRoot != null)
overlayRoot.SetActive(false);
ResolveOverlayRootForPausePrefab();
EnsureOverlayCanvasGroup();
EnsureOverlayPanelRoot();
SetOverlayVisibleImmediate(false);
TryBindButtons(force: true);
}
void Update()
{
// Delete = immediate quit (stop play in editor or quit app)
if (!buttonsBound && Time.unscaledTime >= nextBindRetryTime)
{
TryBindButtons(force: false);
}
if (Input.GetKeyDown(KeyCode.Delete))
{
#if UNITY_EDITOR
@@ -50,54 +95,599 @@ public class PauseManager : MonoBehaviour
if (Input.GetKeyDown(KeyCode.Escape))
{
TogglePause();
// User requested ESC as pause trigger in gameplay only.
if (CanPauseFromInput() && !IsPaused)
{
Pause(true);
}
}
// ͣ״̬Ұ Backspace ʱ
// Keep existing Backspace quick-exit while paused.
if (IsPaused && Input.GetKeyDown(KeyCode.Backspace))
{
// ָʱ¼Ȼ
Pause(false);
// ȷĿеƥ
StartCoroutine(LoadSceneAsync("Main_main"));
}
}
/// <summary>
/// Pause or resume the game. Call Pause(true) to pause.
/// This implementation enables/disables overlayRoot and sets Time.timeScale.
/// </summary>
public void Pause(bool pause)
{
if (pause)
{
if (IsPaused) return;
if (overlayRoot != null) overlayRoot.SetActive(true);
Time.timeScale = 0f;
IsPaused = true;
Time.timeScale = 0f;
OnPauseStateChanged?.Invoke(true);
// Startup flows call Pause(true) before real gameplay starts.
// In that phase we keep time paused but do not show pause UI.
bool allowOverlay = CanPauseFromInput();
if (allowOverlay)
ShowOverlayAnimated(true);
else
SetOverlayVisibleImmediate(false);
}
else
{
if (!IsPaused) return;
if (overlayRoot != null) overlayRoot.SetActive(false);
Time.timeScale = 1f;
IsPaused = false;
Time.timeScale = 1f;
OnPauseStateChanged?.Invoke(false);
ShowOverlayAnimated(false);
}
}
public void TogglePause()
{
Pause(!IsPaused);
if (!CanPauseFromInput()) return;
if (!IsPaused) Pause(true);
}
public void ContinueFromPause()
{
if (!IsPaused) return;
if (continueCoroutine != null) return;
continueCoroutine = StartCoroutine(ContinueFromPauseCoroutine());
}
public void ReplayFromPause()
{
if (!IsPaused) return;
if (continueCoroutine != null)
{
StopCoroutine(continueCoroutine);
continueCoroutine = null;
}
StartCoroutine(ReplayFromPauseCoroutine());
}
public void ExitFromPause()
{
if (!IsPaused) return;
if (continueCoroutine != null)
{
StopCoroutine(continueCoroutine);
continueCoroutine = null;
}
StartCoroutine(ExitFromPauseCoroutine());
}
private bool IsGameplaySceneActive()
{
string sceneName = SceneManager.GetActiveScene().name ?? string.Empty;
if (string.Equals(sceneName, gameplaySceneName, StringComparison.OrdinalIgnoreCase)) return true;
if (sceneName.IndexOf("gameplay", StringComparison.OrdinalIgnoreCase) >= 0) return true;
return false;
}
private bool CanPauseFromInput()
{
if (onlyInGameplayScene && !IsGameplaySceneActive()) return false;
if (!requirePlaybackStarted) return true;
var gm = FindAnyObjectByType<GameManager>();
if (gm == null) return false;
return gm.PlaybackStarted;
}
private void EnsureOverlayCanvasGroup()
{
if (overlayRoot == null) return;
overlayCanvasGroup = overlayRoot.GetComponent<CanvasGroup>();
if (overlayCanvasGroup == null)
overlayCanvasGroup = overlayRoot.AddComponent<CanvasGroup>();
}
private void ResolveOverlayRootForPausePrefab()
{
if (!preferPausePrefabOverWhiteScreen) return;
// Prefer the real pause prefab root (contains Continue/Replay/Exit).
GameObject pauseRoot = FindPauseMenuRootInScene();
if (pauseRoot != null)
{
overlayRoot = pauseRoot;
return;
}
if (overlayRoot == null) return;
// Legacy setup fallback: PauseManager points to pauseWhiteScreen.
if (!string.Equals(overlayRoot.name, legacyWhiteScreenRootName, StringComparison.OrdinalIgnoreCase))
return;
GameObject candidate = FindPauseOverlayCandidate(overlayRoot.transform);
if (candidate == null)
candidate = FindPauseOverlayCandidate(null);
if (candidate != null && candidate != overlayRoot)
overlayRoot = candidate;
}
private GameObject FindPauseOverlayCandidate(Transform searchRoot)
{
Canvas[] canvases = searchRoot != null
? searchRoot.GetComponentsInChildren<Canvas>(true)
: FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
for (int i = 0; i < canvases.Length; i++)
{
Canvas canvas = canvases[i];
if (canvas == null) continue;
GameObject go = canvas.gameObject;
if (go == null || go == overlayRoot) continue;
if (!HasPauseButtons(go)) continue;
return go;
}
return null;
}
private GameObject FindPauseMenuRootInScene()
{
var all = Resources.FindObjectsOfTypeAll<Transform>();
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (!t.gameObject.scene.IsValid()) continue;
if (!string.Equals(t.name, "pause", StringComparison.OrdinalIgnoreCase)) continue;
GameObject go = t.gameObject;
if (HasPauseButtons(go))
return go;
}
// Fallback: any object that contains all three pause action buttons.
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (!t.gameObject.scene.IsValid()) continue;
GameObject go = t.gameObject;
if (HasPauseButtons(go))
return go;
}
return null;
}
private static bool HasPauseButtons(GameObject root)
{
if (root == null) return false;
Button[] buttons = root.GetComponentsInChildren<Button>(true);
bool hasContinue = false;
bool hasReplay = false;
bool hasExit = false;
for (int i = 0; i < buttons.Length; i++)
{
Button b = buttons[i];
if (b == null) continue;
string n = b.gameObject.name;
if (string.Equals(n, "Continue", StringComparison.OrdinalIgnoreCase)) hasContinue = true;
else if (string.Equals(n, "Replay", StringComparison.OrdinalIgnoreCase)) hasReplay = true;
else if (string.Equals(n, "Exit", StringComparison.OrdinalIgnoreCase)) hasExit = true;
}
return hasContinue && hasReplay && hasExit;
}
private void EnsureOverlayPanelRoot()
{
if (overlayRoot == null) return;
if (overlayPanelRoot == null)
{
var canvases = overlayRoot.GetComponentsInChildren<Canvas>(true);
for (int i = 0; i < canvases.Length; i++)
{
var canvas = canvases[i];
if (canvas == null) continue;
if (canvas.gameObject == overlayRoot) continue;
overlayPanelRoot = canvas.GetComponent<RectTransform>();
if (overlayPanelRoot != null)
break;
}
if (overlayPanelRoot == null)
{
var rts = overlayRoot.GetComponentsInChildren<RectTransform>(true);
for (int i = 0; i < rts.Length; i++)
{
var rt = rts[i];
if (rt == null) continue;
if (rt.gameObject == overlayRoot) continue;
overlayPanelRoot = rt;
break;
}
}
}
if (overlayPanelRoot == null) return;
if (overlayPanelCanvasGroup == null)
{
overlayPanelCanvasGroup = overlayPanelRoot.GetComponent<CanvasGroup>();
if (overlayPanelCanvasGroup == null)
overlayPanelCanvasGroup = overlayPanelRoot.gameObject.AddComponent<CanvasGroup>();
}
if (IsScaleNearlyZero(panelShownScale))
{
Vector3 current = overlayPanelRoot.localScale;
panelShownScale = IsScaleNearlyZero(current) ? Vector3.one : current;
}
}
private static bool IsScaleNearlyZero(Vector3 scale)
{
return Mathf.Abs(scale.x) <= 0.0001f
&& Mathf.Abs(scale.y) <= 0.0001f
&& Mathf.Abs(scale.z) <= 0.0001f;
}
private void SetOverlayVisibleImmediate(bool visible)
{
if (overlayRoot == null) return;
EnsureOverlayCanvasGroup();
EnsureOverlayPanelRoot();
overlayRoot.SetActive(visible);
if (overlayCanvasGroup != null)
{
overlayCanvasGroup.alpha = visible ? 1f : 0f;
overlayCanvasGroup.interactable = visible;
overlayCanvasGroup.blocksRaycasts = visible;
}
if (overlayPanelCanvasGroup != null)
{
overlayPanelCanvasGroup.alpha = visible ? 1f : 0f;
overlayPanelCanvasGroup.interactable = visible;
overlayPanelCanvasGroup.blocksRaycasts = visible;
}
if (overlayPanelRoot != null)
{
overlayPanelRoot.localScale = visible ? panelShownScale : PanelHiddenScale;
}
}
private void ShowOverlayAnimated(bool show)
{
if (overlayRoot == null)
return;
EnsureOverlayCanvasGroup();
if (overlayFadeCoroutine != null)
{
StopCoroutine(overlayFadeCoroutine);
overlayFadeCoroutine = null;
}
overlayFadeCoroutine = StartCoroutine(FadeOverlayCoroutine(show));
}
private IEnumerator FadeOverlayCoroutine(bool show)
{
if (overlayRoot == null)
yield break;
EnsureOverlayCanvasGroup();
EnsureOverlayPanelRoot();
if (show && !overlayRoot.activeSelf)
overlayRoot.SetActive(true);
// Pause entry animation helper may be unavailable in some compile targets.
// Keep pause functionality independent from that optional visual effect.
float startA = overlayCanvasGroup != null ? overlayCanvasGroup.alpha : (show ? 0f : 1f);
float startPanelA = overlayPanelCanvasGroup != null ? overlayPanelCanvasGroup.alpha : startA;
float targetA = show ? 1f : 0f;
Vector3 startScale = overlayPanelRoot != null ? overlayPanelRoot.localScale : panelShownScale;
if (show && IsScaleNearlyZero(startScale))
startScale = PanelHiddenScale;
Vector3 targetScale = show ? panelShownScale : PanelHiddenScale;
float duration = Mathf.Max(0.01f, overlayFadeDuration);
float t = 0f;
if (show)
{
if (overlayCanvasGroup != null)
{
overlayCanvasGroup.interactable = true;
overlayCanvasGroup.blocksRaycasts = true;
}
if (overlayPanelCanvasGroup != null)
{
overlayPanelCanvasGroup.interactable = true;
overlayPanelCanvasGroup.blocksRaycasts = true;
}
}
else
{
if (overlayCanvasGroup != null)
{
overlayCanvasGroup.interactable = false;
overlayCanvasGroup.blocksRaycasts = false;
}
if (overlayPanelCanvasGroup != null)
{
overlayPanelCanvasGroup.interactable = false;
overlayPanelCanvasGroup.blocksRaycasts = false;
}
}
while (t < duration)
{
t += Time.unscaledDeltaTime;
float k = Mathf.Clamp01(t / duration);
if (overlayCanvasGroup != null)
overlayCanvasGroup.alpha = Mathf.Lerp(startA, targetA, k);
if (overlayPanelCanvasGroup != null)
overlayPanelCanvasGroup.alpha = Mathf.Lerp(startPanelA, targetA, k);
if (overlayPanelRoot != null)
overlayPanelRoot.localScale = Vector3.Lerp(startScale, targetScale, k);
yield return null;
}
if (overlayCanvasGroup != null)
overlayCanvasGroup.alpha = targetA;
if (overlayPanelCanvasGroup != null)
overlayPanelCanvasGroup.alpha = targetA;
if (overlayPanelRoot != null)
overlayPanelRoot.localScale = targetScale;
if (!show)
overlayRoot.SetActive(false);
overlayFadeCoroutine = null;
}
private IEnumerator ContinueFromPauseCoroutine()
{
// Start hiding panel, but do not wait before countdown starts.
ShowOverlayAnimated(false);
// Replay 3-2-1-Go immediately on continue click.
var gm = FindAnyObjectByType<GameManager>();
var ready = gm != null ? gm.readyLetsGo : FindAnyObjectByType<readyLetsGo>();
float originalWaitingDuration = 0f;
bool waitingDurationOverridden = false;
if (ready != null)
{
// Remove extra idle delay before countdown visuals in continue flow.
originalWaitingDuration = ready.waitingDuration;
ready.waitingDuration = 0f;
waitingDurationOverridden = true;
try { ready.ResetPlayState(); } catch { }
try { ready.PlaySequence(); } catch { }
int imageCount = ready._321goImages != null ? ready._321goImages.Count : 0;
float sequenceDuration = 0.25f
+ 0f
+ Mathf.Max(0f, imageCount * ready.oneImageDuration)
+ 0.15f;
if (sequenceDuration > 0f)
yield return new WaitForSecondsRealtime(sequenceDuration);
}
if (waitingDurationOverridden && ready != null)
{
ready.waitingDuration = originalWaitingDuration;
}
// Resume chart + music.
Pause(false);
continueCoroutine = null;
}
private IEnumerator ReplayFromPauseCoroutine()
{
SetOverlayVisibleImmediate(false);
IsPaused = false;
Time.timeScale = 1f;
try { OnPauseStateChanged?.Invoke(false); } catch { }
var bmm = FindAnyObjectByType<BeatmapManager>();
if (bmm != null && bmm.assignedSongData != null)
{
int diff = bmm.assignedDifficulty;
if (diff < 0) diff = 0;
BeatmapManager.SetPendingSong(bmm.assignedSongData, diff);
}
string current = SceneManager.GetActiveScene().name;
yield return LoadSceneAsync(current);
}
private IEnumerator ExitFromPauseCoroutine()
{
SetOverlayVisibleImmediate(false);
IsPaused = false;
Time.timeScale = 1f;
try { OnPauseStateChanged?.Invoke(false); } catch { }
yield return LoadSceneAsync(ExitSceneName);
}
private IEnumerator LoadSceneAsync(string sceneName)
{
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
{
yield return null;
}
private void TryBindButtons(bool force)
{
if (buttonsBound && !force) return;
if (onlyInGameplayScene && !IsGameplaySceneActive() && !force)
{
nextBindRetryTime = Time.unscaledTime + 1f;
return;
}
if (pauseTriggerButton == null)
{
var pauseGO = GameObject.Find(pauseButtonScenePath);
if (pauseGO == null)
pauseGO = FindByNameInScene("Pause");
if (pauseGO != null)
pauseTriggerButton = pauseGO.GetComponent<Button>();
}
if (overlayRoot != null)
{
if (continueButton == null) continueButton = FindButtonInOverlay("Continue");
if (replayButton == null) replayButton = FindButtonInOverlay("Replay");
if (exitButton == null) exitButton = FindButtonInOverlay("Exit");
if (settingsButton == null) settingsButton = FindButtonInOverlay("settings");
}
if (pauseTriggerButton != null)
{
pauseTriggerButton.onClick.RemoveListener(OnPauseButtonClicked);
pauseTriggerButton.onClick.AddListener(OnPauseButtonClicked);
}
if (continueButton != null)
{
continueButton.onClick.RemoveListener(ContinueFromPause);
continueButton.onClick.AddListener(ContinueFromPause);
}
if (replayButton != null)
{
replayButton.onClick.RemoveListener(ReplayFromPause);
replayButton.onClick.AddListener(ReplayFromPause);
}
if (exitButton != null)
{
exitButton.onClick.RemoveListener(ExitFromPause);
exitButton.onClick.AddListener(ExitFromPause);
}
if (settingsButton != null)
{
settingsButton.onClick.RemoveListener(OnSettingsButtonClicked);
settingsButton.onClick.AddListener(OnSettingsButtonClicked);
}
buttonsBound = pauseTriggerButton != null && continueButton != null && replayButton != null && exitButton != null;
nextBindRetryTime = Time.unscaledTime + (buttonsBound ? 9999f : 1f);
}
private void OnPauseButtonClicked()
{
if (!CanPauseFromInput()) return;
if (!IsPaused) Pause(true);
}
private void OnSettingsButtonClicked()
{
if (!IsPaused) return;
if (TryOpenExistingSettingsPanel()) return;
var topController = FindAnyObjectByType<btmandtopController>();
if (topController != null)
{
topController.SendMessage("OnSettingsNavClicked", SendMessageOptions.DontRequireReceiver);
return;
}
Debug.LogWarning("[PauseManager] Settings button clicked, but no settings panel source was found.");
}
private bool TryOpenExistingSettingsPanel()
{
var settingsPanels = Resources.FindObjectsOfTypeAll<ui_Panel_Setting>();
for (int i = 0; i < settingsPanels.Length; i++)
{
ui_Panel_Setting panel = settingsPanels[i];
if (panel == null) continue;
GameObject go = panel.gameObject;
if (go == null || !go.scene.IsValid()) continue;
go.SetActive(true);
go.transform.SetAsLastSibling();
CanvasGroup cg = go.GetComponent<CanvasGroup>();
if (cg != null)
{
cg.interactable = true;
cg.blocksRaycasts = true;
}
UI_SettingsPanelEnterAnim enterAnim = go.GetComponent<UI_SettingsPanelEnterAnim>();
if (enterAnim != null) enterAnim.Play();
return true;
}
GameObject byName = FindByNameInScene("ui_Panel_Setting");
if (byName != null)
{
byName.SetActive(true);
byName.transform.SetAsLastSibling();
UI_SettingsPanelEnterAnim enterAnim = byName.GetComponent<UI_SettingsPanelEnterAnim>();
if (enterAnim != null) enterAnim.Play();
return true;
}
return false;
}
private Button FindButtonInOverlay(string buttonName)
{
if (overlayRoot == null || string.IsNullOrEmpty(buttonName)) return null;
var buttons = overlayRoot.GetComponentsInChildren<Button>(true);
for (int i = 0; i < buttons.Length; i++)
{
var b = buttons[i];
if (b == null) continue;
if (string.Equals(b.gameObject.name, buttonName, StringComparison.OrdinalIgnoreCase))
return b;
}
return null;
}
private GameObject FindByNameInScene(string name)
{
if (string.IsNullOrEmpty(name)) return null;
var all = Resources.FindObjectsOfTypeAll<Transform>();
for (int i = 0; i < all.Length; i++)
{
var t = all[i];
if (t == null) continue;
if (!string.Equals(t.name, name, StringComparison.OrdinalIgnoreCase)) continue;
if (t.gameObject.scene.IsValid())
return t.gameObject;
}
return null;
}
}