Files
zeling_v2/Assets/_Game/Scripts/UI/Splash/SplashScreenController.cs
Joywayer c88d2d0549 feat: Addressables rules/sync tools, UI fixes, AddressKeys update
- Add AddressableRules.cs: single source of truth for prefix->group and prefix->label rules
- Add AddressableRuleSyncWindow.cs: scan/fix/export-CSV tool (BaseGames > Addressables > Rule Sync)
- AddressableBatchTool.cs: delegate DeriveGroupName to AddressableRules, remove duplicate PrefixGroupMap
- AddressKeys.cs: add Labels constants (Preload, Poolable, Enemy, BGM, SFX, Charms, Config, Weapon)
- Docs/Standards/AddressablesLabelSpec.md: new label naming & assignment spec
- Docs/Standards/AssetFolderSpec.md: update Addressables group strategy section
- SplashScreenController.cs: fix MainMenu loading flow
- BootFlowSetupWizard.cs / SceneScaffoldTools.cs: scene scaffold fixes
- PlayerInputActions: set UI/Point to Pass-Through type
- Persistent.unity: add BootSequencer to auto-load MainMenu on play
- EditorBuildSettings.asset: register scenes for build

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-20 11:10:31 +08:00

151 lines
6.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
using BaseGames.Core.Events;
namespace BaseGames.UI.Splash
{
/// <summary>
/// 游戏启动 Logo 演出控制器。挂载在 Persistent 场景的 Canvas_Splash 根节点。
///
/// 演出顺序:工作室 Logo 淡入 → 停留 → 淡出 → 游戏标题淡入 → 停留 → 黑幕整体淡出。
/// 任意按键 / 手柄键可跳过整段演出。
///
/// 与 Core 程序集完全解耦:
/// 监听 EVT_SplashStartRequestVoidEventChannelSO→ 开始演出。
/// 完成后发布 EVT_SplashCompleteVoidEventChannelSO→ 通知 BootSequencer 继续。
/// </summary>
public class SplashScreenController : MonoBehaviour
{
[Header("Canvas Groups")]
[Tooltip("整个 Splash 画布的根节点 CanvasGroup用于整体淡出黑幕")]
[SerializeField] private CanvasGroup _splashRoot;
[Tooltip("工作室 Logo CanvasGroup")]
[SerializeField] private CanvasGroup _studioLogoGroup;
[Tooltip("游戏标题 Logo CanvasGroup")]
[SerializeField] private CanvasGroup _gameTitleGroup;
[Header("时序(秒)")]
[SerializeField] private float _fadeInDuration = 0.8f;
[SerializeField] private float _holdDuration = 1.5f;
[SerializeField] private float _fadeOutDuration = 0.6f;
[SerializeField] private float _stageGapDuration = 0.3f;
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onSplashStartRequest; // Listen
[SerializeField] private VoidEventChannelSO _onSplashComplete; // Raise
private bool _skipRequested;
private InputAction _anyButtonAction;
private readonly CompositeDisposable _subs = new();
// ── 生命周期 ─────────────────────────────────────────────────────────
private void Awake()
{
// 初始态:根节点完全不透明(充当开场黑幕),各 Logo 完全透明
SetAlpha(_splashRoot, 1f, blocksRaycasts: true);
SetAlpha(_studioLogoGroup, 0f, blocksRaycasts: false);
SetAlpha(_gameTitleGroup, 0f, blocksRaycasts: false);
// 通配符绑定:捕获任意设备的任意按键/手柄键/触屏按压
_anyButtonAction = new InputAction(binding: "/*/<button>");
_anyButtonAction.performed += _ => _skipRequested = true;
}
private void OnEnable()
{
_onSplashStartRequest?.Subscribe(OnStartRequested).AddTo(_subs);
_anyButtonAction.Enable();
}
private void OnDisable()
{
_subs.Clear();
_anyButtonAction.Disable();
}
private void OnDestroy()
{
_anyButtonAction.Dispose();
}
// ── 入口 ─────────────────────────────────────────────────────────────
private void OnStartRequested() => StartCoroutine(PlayAndNotify());
private IEnumerator PlayAndNotify()
{
yield return StartCoroutine(PlayAsync());
_onSplashComplete?.Raise();
}
// ── 演出主逻辑 ───────────────────────────────────────────────────────
private IEnumerator PlayAsync()
{
_skipRequested = false;
// Stage 1工作室 Logo
if (_studioLogoGroup != null)
{
yield return StartCoroutine(FadeGroup(_studioLogoGroup, 0f, 1f, _fadeInDuration));
yield return StartCoroutine(WaitOrSkip(_holdDuration));
yield return StartCoroutine(FadeGroup(_studioLogoGroup, 1f, 0f, _fadeOutDuration));
}
if (!_skipRequested)
yield return new WaitForSecondsRealtime(_stageGapDuration);
// Stage 2游戏标题
if (_gameTitleGroup != null)
{
yield return StartCoroutine(FadeGroup(_gameTitleGroup, 0f, 1f, _fadeInDuration));
yield return StartCoroutine(WaitOrSkip(_holdDuration));
yield return StartCoroutine(FadeGroup(_gameTitleGroup, 1f, 0f, _fadeOutDuration));
}
// 黑幕整体淡出(显露主菜单场景)
yield return StartCoroutine(FadeGroup(_splashRoot, 1f, 0f, _fadeOutDuration));
if (_splashRoot != null)
_splashRoot.blocksRaycasts = false;
gameObject.SetActive(false);
}
// ── 内部工具 ─────────────────────────────────────────────────────────
private IEnumerator FadeGroup(CanvasGroup group, float from, float to, float duration)
{
if (group == null) yield break;
group.alpha = from;
float elapsed = 0f;
while (elapsed < duration && !_skipRequested)
{
group.alpha = Mathf.Lerp(from, to, elapsed / duration);
elapsed += Time.unscaledDeltaTime;
yield return null;
}
group.alpha = to;
}
private IEnumerator WaitOrSkip(float duration)
{
float elapsed = 0f;
while (elapsed < duration && !_skipRequested)
{
elapsed += Time.unscaledDeltaTime;
yield return null;
}
}
private static void SetAlpha(CanvasGroup group, float alpha, bool blocksRaycasts)
{
if (group == null) return;
group.alpha = alpha;
group.blocksRaycasts = blocksRaycasts;
}
}
}