多轮审查和修复

This commit is contained in:
2026-05-12 15:34:08 +08:00
parent f55d2a57c3
commit ebbbb7332e
805 changed files with 838724 additions and 1905 deletions

View File

@@ -15,15 +15,10 @@ namespace BaseGames.UI.Menus
[SerializeField] private VoidEventChannelSO _onPlayerDied;
[SerializeField] private VoidEventChannelSO _onDeathScreenConfirmed;
private void OnEnable()
{
if (_onPlayerDied != null) _onPlayerDied.OnEventRaised += OnPlayerDied;
}
private readonly CompositeDisposable _subs = new();
private void OnDisable()
{
if (_onPlayerDied != null) _onPlayerDied.OnEventRaised -= OnPlayerDied;
}
private void OnEnable() => _onPlayerDied?.Subscribe(OnPlayerDied).AddTo(_subs);
private void OnDisable() => _subs.Clear();
private void OnPlayerDied() => StartCoroutine(ShowAfterDelay(1.5f));

View File

@@ -0,0 +1,59 @@
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
/// <summary>
/// 暂停菜单控制器(架构 10_UIModule §5
/// 挂载在 Canvas_Menu → PauseMenuPanel GameObject 上。
/// 按钮绑定在 Awake 中完成;由 UIManager 负责面板开关。
/// </summary>
public class PauseMenuController : MonoBehaviour
{
[SerializeField] private UIManager _uiManager;
[SerializeField] private GameObject _settingsRoot; // SettingsPanel 根 GameObject
[Header("按钮引用")]
[SerializeField] private Button _btnResume;
[SerializeField] private Button _btnSettings;
[SerializeField] private Button _btnMainMenu;
[SerializeField] private Button _btnQuit;
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onResumeRequested;
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
private void Awake()
{
_btnResume.onClick.AddListener(Resume);
_btnSettings.onClick.AddListener(OpenSettings);
_btnMainMenu.onClick.AddListener(GoToMainMenu);
_btnQuit.onClick.AddListener(Application.Quit);
}
// ── 按钮回调 ──────────────────────────────────────────────────────────
private void Resume()
{
_onResumeRequested?.Raise();
_uiManager.CloseTopPanel();
}
private void OpenSettings()
{
if (_settingsRoot != null)
_uiManager.OpenPanel(_settingsRoot);
}
private void GoToMainMenu()
{
_uiManager.CloseTopPanel();
_onSceneLoadRequest?.Raise(new SceneLoadRequest
{
SceneName = "MainMenu",
ShowLoadingScreen = true
});
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8adf13ec10899df439ee33bc9dcbcdeb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,141 @@
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BaseGames.Core.Events;
using BaseGames.Core.Save;
namespace BaseGames.UI.Menus
{
/// <summary>
/// 驱动主菜单存档槽选择面板(新游戏 / 继续 / 删除)。
/// </summary>
public class SaveSlotController : MonoBehaviour
{
[SerializeField] private SaveSlotUI[] _slotUIs; // 3 个存档槽 UI
[SerializeField] private SaveManager _saveManager;
[Header("Event Channels")]
[SerializeField] private IntEventChannelSO _onSlotConfirmed; // 携带槽索引,供 GameManager 监听
private async void OnEnable()
{
await RefreshAsync();
}
private async Task RefreshAsync()
{
if (_saveManager == null) return;
for (int i = 0; i < _slotUIs.Length; i++)
{
if (_slotUIs[i] == null) continue;
var summary = await _saveManager.GetSlotSummaryAsync(i);
_slotUIs[i].Refresh(summary);
}
}
/// <summary>选中指定槽位(新局或继续)。由 SaveSlotUI 内部按钮调用。</summary>
public void OnSlotSelected(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= 3 || _saveManager == null) return;
_ = SelectSlotAsync(slotIndex);
}
private async Task SelectSlotAsync(int slotIndex)
{
if (_saveManager.SlotExists(slotIndex))
await _saveManager.LoadAsync(slotIndex);
else
_saveManager.CreateSlot(slotIndex);
_onSlotConfirmed?.Raise(slotIndex);
}
/// <summary>删除指定槽位存档并刷新 UI。由 SaveSlotUI 内部按钮调用。</summary>
public void OnSlotDeleteRequested(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= 3 || _saveManager == null) return;
_ = DeleteAndRefreshAsync(slotIndex);
}
private async Task DeleteAndRefreshAsync(int slotIndex)
{
await _saveManager.DeleteSlotAsync(slotIndex);
await RefreshAsync();
}
}
/// <summary>
/// 单个存档槽卡片组件,负责显示存档摘要或空槽提示。
/// </summary>
public class SaveSlotUI : MonoBehaviour
{
[SerializeField] private TMP_Text _playtimeText;
[SerializeField] private TMP_Text _regionText;
[SerializeField] private TMP_Text _lastSavedText;
[SerializeField] private Image _formIcon;
[SerializeField] private GameObject _emptyIndicator; // 空槽时显示的"新游戏"提示
[SerializeField] private GameObject _dataIndicator; // 有数据时显示的内容根
[SerializeField] private Button _selectButton;
[SerializeField] private Button _deleteButton;
private int _slotIndex;
private SaveSlotController _controller;
/// <summary>由 SaveSlotController 在 Awake 或初始化时调用以完成按钮绑定。</summary>
public void Init(int slotIndex, SaveSlotController controller)
{
_slotIndex = slotIndex;
_controller = controller;
if (_selectButton != null)
{
_selectButton.onClick.RemoveAllListeners();
_selectButton.onClick.AddListener(() => _controller.OnSlotSelected(_slotIndex));
}
if (_deleteButton != null)
{
_deleteButton.onClick.RemoveAllListeners();
_deleteButton.onClick.AddListener(() => _controller.OnSlotDeleteRequested(_slotIndex));
}
}
/// <summary>用摘要数据刷新显示summary 为 null 表示空槽。</summary>
public void Refresh(SlotSummary summary)
{
bool hasData = summary != null;
if (_emptyIndicator != null) _emptyIndicator.SetActive(!hasData);
if (_dataIndicator != null) _dataIndicator.SetActive(hasData);
if (_deleteButton != null) _deleteButton.gameObject.SetActive(hasData);
if (!hasData) return;
if (_playtimeText != null) _playtimeText.text = FormatPlaytime(summary.Playtime);
if (_regionText != null) _regionText.text = summary.SceneName ?? string.Empty;
if (_lastSavedText != null) _lastSavedText.text = FormatDateTime(summary.LastSaved);
}
private static string FormatPlaytime(float seconds)
{
int h = (int)(seconds / 3600);
int m = (int)((seconds % 3600) / 60);
int s = (int)(seconds % 60);
return $"{h:D2}:{m:D2}:{s:D2}";
}
private static string FormatDateTime(string iso8601)
{
if (string.IsNullOrEmpty(iso8601)) return string.Empty;
if (DateTime.TryParse(iso8601,
System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.RoundtripKind,
out DateTime dt))
{
return dt.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
}
return iso8601;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76a400ef5becc074ca745eb099289143
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
namespace BaseGames.UI
{
/// <summary>
/// 设置面板控制器(架构 10_UIModule §7
/// 驱动 SettingsManager 的音量与画面设置,并从当前配置初始化控件值。
/// </summary>
public class SettingsPanelController : MonoBehaviour
{
[SerializeField] private SettingsManager _settings;
[Header("音量滑条")]
[SerializeField] private Slider _masterVolume;
[SerializeField] private Slider _bgmVolume;
[SerializeField] private Slider _sfxVolume;
[SerializeField] private Slider _ambientVolume;
[Header("画面")]
[SerializeField] private Toggle _vSyncToggle;
[SerializeField] private TMP_Dropdown _fpsDropdown; // 30 / 60 / 120 / 无限
[Header("按键重绑定")]
[SerializeField] private GameObject _rebindPanelRoot; // RebindPanel GameObject
private static readonly int[] FpsOptions = { 30, 60, 120, -1 };
private void Start()
{
if (_settings == null) return;
var data = _settings.Current;
// 初始化控件值(不触发 onChange先移除监听再设置值再添加
InitSlider(_masterVolume, data.MasterVolume, v => _settings.SetMasterVolume(v));
InitSlider(_bgmVolume, data.BGMVolume, v => _settings.SetBGMVolume(v));
InitSlider(_sfxVolume, data.SFXVolume, v => _settings.SetSFXVolume(v));
InitSlider(_ambientVolume,data.AmbientVolume, v => _settings.SetAmbientVolume(v));
if (_vSyncToggle != null)
{
_vSyncToggle.isOn = data.VSync;
_vSyncToggle.onValueChanged.AddListener(v => _settings.SetVSync(v));
}
if (_fpsDropdown != null)
{
int idx = System.Array.IndexOf(FpsOptions, data.TargetFPS);
_fpsDropdown.value = idx >= 0 ? idx : 1; // default 60
_fpsDropdown.onValueChanged.AddListener(i =>
_settings.SetTargetFrameRate(FpsOptions[Mathf.Clamp(i, 0, FpsOptions.Length - 1)]));
}
}
// ── 辅助 ──────────────────────────────────────────────────────────────
private static void InitSlider(Slider slider, float value, UnityEngine.Events.UnityAction<float> onChange)
{
if (slider == null) return;
slider.value = value;
slider.onValueChanged.AddListener(onChange);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93f9600681435a74187c249850a0f71c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: