- 新增 TerminalUIAudio 事件通知与 TerminalUIAudioBehaviour 组件 - TerminalButton/TerminalMenuItemView/SelectItem/SliderItem 接入悬停与点击音效 - AudioManager 监听 TerminalUIEvent 播放 menu_hover/menu_click - 新增 PauseMenu 环境音状态及恢复逻辑 - 新增 MainMenuStage FMOD 参数控制 - BaseButton 调用基类 DoStateTransition 保证状态事件正确触发 - 为 TerminalChapterPanel Prefab 添加 TerminalUIAudioBehaviour
338 lines
9.1 KiB
C#
338 lines
9.1 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.Playables;
|
||
|
||
namespace AibisDream.UI
|
||
{
|
||
public enum TerminalPage
|
||
{
|
||
None,
|
||
Start,
|
||
Setting,
|
||
Chapter,
|
||
}
|
||
|
||
enum TerminalBootPhase
|
||
{
|
||
None,
|
||
PressAnyKey,
|
||
PlayingIntro,
|
||
PostIntroPause,
|
||
}
|
||
|
||
/// <summary>
|
||
/// 主菜单终端 Panel 壳:管理 Start / Chapter / Setting 三个 Page。
|
||
/// 冷启动时先走 Intro Timeline(Press Any Key → 播放 → 停顿 → Start);同 session 内回主菜单跳过 Intro。
|
||
/// </summary>
|
||
public class TerminalPanel : MonoBehaviour, IUIPanel, ITerminalPanelHost
|
||
{
|
||
[SerializeField] private TerminalHeaderView header;
|
||
[SerializeField] private TerminalStartPanel startPage;
|
||
[SerializeField] private TerminalSettingPanel settingPage;
|
||
[SerializeField] private TerminalChapterPanel chapterPage;
|
||
[SerializeField] private PlayableDirector introDirector;
|
||
[SerializeField] private float postIntroPause = 0.5f;
|
||
[SerializeField] private GameObject menuFrame;
|
||
|
||
private readonly Stack<TerminalPage> _backStack = new();
|
||
private TerminalPage _currentPage = TerminalPage.None;
|
||
private bool _introCompletedThisSession;
|
||
private TerminalBootPhase _bootPhase = TerminalBootPhase.None;
|
||
|
||
public bool IsOpen =>
|
||
gameObject.activeSelf &&
|
||
(_bootPhase != TerminalBootPhase.None || _currentPage != TerminalPage.None);
|
||
public bool IsCloseable => false;
|
||
public TerminalPage CurrentPage => _currentPage;
|
||
public bool IsInGameContext => false;
|
||
|
||
public void Initialize()
|
||
{
|
||
startPage?.SetOwner(this);
|
||
settingPage?.SetOwner(this);
|
||
chapterPage?.SetOwner(this);
|
||
|
||
if (!HasRequiredPages())
|
||
{
|
||
Debug.LogError("[TerminalPanel] Missing required pages.");
|
||
}
|
||
|
||
Close();
|
||
}
|
||
|
||
public bool HasRequiredPages()
|
||
{
|
||
return startPage != null && settingPage != null && chapterPage != null;
|
||
}
|
||
|
||
public void Show()
|
||
{
|
||
if (_introCompletedThisSession)
|
||
{
|
||
TerminalUIAudio.NotifyMainMenuPhase(TerminalMainMenuPhase.Interactive);
|
||
Open(TerminalPage.Start);
|
||
return;
|
||
}
|
||
|
||
if (introDirector == null)
|
||
{
|
||
Debug.LogError("[TerminalPanel] introDirector is not assigned. Opening Start directly.");
|
||
_introCompletedThisSession = true;
|
||
TerminalUIAudio.NotifyMainMenuPhase(TerminalMainMenuPhase.Interactive);
|
||
Open(TerminalPage.Start);
|
||
return;
|
||
}
|
||
|
||
BeginBootSequence();
|
||
}
|
||
|
||
public void Hide()
|
||
{
|
||
Close();
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
if (_bootPhase != TerminalBootPhase.PressAnyKey)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!TryGetBootInput())
|
||
{
|
||
return;
|
||
}
|
||
|
||
TerminalUIAudio.NotifyMainMenuPhase(TerminalMainMenuPhase.Interactive);
|
||
StartCoroutine(PlayIntroAndRevealStart());
|
||
}
|
||
|
||
private static bool TryGetBootInput()
|
||
{
|
||
if (Input.GetMouseButtonDown(0))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return Input.anyKeyDown;
|
||
}
|
||
|
||
private void BeginBootSequence()
|
||
{
|
||
gameObject.SetActive(true);
|
||
_backStack.Clear();
|
||
_currentPage = TerminalPage.None;
|
||
HideAllPages();
|
||
|
||
_bootPhase = TerminalBootPhase.PressAnyKey;
|
||
TerminalUIAudio.NotifyMainMenuPhase(TerminalMainMenuPhase.Boot);
|
||
}
|
||
|
||
private IEnumerator PlayIntroAndRevealStart()
|
||
{
|
||
_bootPhase = TerminalBootPhase.PlayingIntro;
|
||
introDirector.Play();
|
||
|
||
while (introDirector.state == PlayState.Playing)
|
||
{
|
||
yield return null;
|
||
}
|
||
|
||
_bootPhase = TerminalBootPhase.PostIntroPause;
|
||
if (postIntroPause > 0f)
|
||
{
|
||
yield return new WaitForSecondsRealtime(postIntroPause);
|
||
}
|
||
|
||
_introCompletedThisSession = true;
|
||
_bootPhase = TerminalBootPhase.None;
|
||
menuFrame.SetActive(true);
|
||
Open(TerminalPage.Start);
|
||
}
|
||
|
||
public void Open(TerminalPage page)
|
||
{
|
||
if (!HasRequiredPages() || page == TerminalPage.None)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (!IsOpen)
|
||
{
|
||
_backStack.Clear();
|
||
}
|
||
|
||
gameObject.SetActive(true);
|
||
SwitchTo(page, pushCurrent: false);
|
||
}
|
||
|
||
public void SwitchTo(TerminalPage page, bool pushCurrent = true)
|
||
{
|
||
if (page == TerminalPage.None || page == _currentPage)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (pushCurrent && _currentPage != TerminalPage.None)
|
||
{
|
||
_backStack.Push(_currentPage);
|
||
}
|
||
|
||
var previous = _currentPage;
|
||
_currentPage = page;
|
||
|
||
if (previous == TerminalPage.None)
|
||
{
|
||
ShowPage(_currentPage);
|
||
return;
|
||
}
|
||
|
||
HidePage(previous, () => ShowPage(_currentPage));
|
||
}
|
||
|
||
public void Back()
|
||
{
|
||
if (_backStack.Count > 0)
|
||
{
|
||
var previous = _backStack.Pop();
|
||
var current = _currentPage;
|
||
_currentPage = previous;
|
||
HidePage(current, () => ShowPage(_currentPage));
|
||
return;
|
||
}
|
||
|
||
if (_currentPage != TerminalPage.Start)
|
||
{
|
||
SwitchTo(TerminalPage.Start, pushCurrent: false);
|
||
}
|
||
}
|
||
|
||
public bool HandleEscape()
|
||
{
|
||
if (!gameObject.activeSelf)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (_bootPhase != TerminalBootPhase.None)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
if (_currentPage == TerminalPage.None)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (_currentPage == TerminalPage.Start)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
Back();
|
||
return true;
|
||
}
|
||
|
||
public void CloseThen(Action action)
|
||
{
|
||
Close();
|
||
action?.Invoke();
|
||
}
|
||
|
||
public void Close()
|
||
{
|
||
if (introDirector != null)
|
||
{
|
||
introDirector.Stop();
|
||
}
|
||
|
||
HideAllPages();
|
||
_backStack.Clear();
|
||
_currentPage = TerminalPage.None;
|
||
_bootPhase = TerminalBootPhase.None;
|
||
gameObject.SetActive(false);
|
||
}
|
||
|
||
public void CloseToMainMenu()
|
||
{
|
||
Close();
|
||
GameManager.Instance.QuitGame();
|
||
}
|
||
|
||
public void SetHeader(string title, string status = "")
|
||
{
|
||
header?.SetText(title, status);
|
||
}
|
||
|
||
private void ShowPage(TerminalPage page)
|
||
{
|
||
switch (page)
|
||
{
|
||
case TerminalPage.Start:
|
||
startPage?.Show();
|
||
break;
|
||
case TerminalPage.Setting:
|
||
settingPage?.Show();
|
||
break;
|
||
case TerminalPage.Chapter:
|
||
chapterPage?.Show();
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void HidePage(TerminalPage page, Action onComplete = null)
|
||
{
|
||
switch (page)
|
||
{
|
||
case TerminalPage.Start:
|
||
HidePage(startPage, onComplete);
|
||
break;
|
||
case TerminalPage.Setting:
|
||
HidePage(settingPage, onComplete);
|
||
break;
|
||
case TerminalPage.Chapter:
|
||
HidePage(chapterPage, onComplete);
|
||
break;
|
||
default:
|
||
onComplete?.Invoke();
|
||
break;
|
||
}
|
||
}
|
||
|
||
private static void HidePage(MonoBehaviour page, Action onComplete)
|
||
{
|
||
if (page == null)
|
||
{
|
||
onComplete?.Invoke();
|
||
return;
|
||
}
|
||
|
||
switch (page)
|
||
{
|
||
case TerminalStartPanel startPanel:
|
||
startPanel.Hide(onComplete);
|
||
break;
|
||
case TerminalSettingPanel settingPanel:
|
||
settingPanel.Hide(onComplete);
|
||
break;
|
||
case TerminalChapterPanel chapterPanel:
|
||
chapterPanel.Hide(onComplete);
|
||
break;
|
||
default:
|
||
page.gameObject.SetActive(false);
|
||
onComplete?.Invoke();
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void HideAllPages()
|
||
{
|
||
startPage?.Hide();
|
||
settingPage?.Hide();
|
||
chapterPage?.Hide();
|
||
}
|
||
}
|
||
}
|