Passion & UI

This commit is contained in:
SoulliesOfficial
2026-06-12 17:11:39 -04:00
parent 7bc1e1722c
commit 6d7ebc5825
3444 changed files with 865284 additions and 463132 deletions

View File

@@ -0,0 +1,26 @@
using SLSUtilities.UI;
using SLSUtilities.WwiseAssistance;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Cielonos.UI
{
public class CieButton : UIElementBase
{
public Button button;
private void Start()
{
button ??= GetComponent<Button>();
var trigger = button.gameObject.AddComponent<EventTrigger>();
//悬停和按下时播放声音
var pointerEnter = new EventTrigger.Entry { eventID = EventTriggerType.PointerEnter };
pointerEnter.callback.AddListener(_ => AudioManager.Post(AK.EVENTS.UI_HOVER, button.gameObject));
trigger.triggers.Add(pointerEnter);
var pointerDown = new EventTrigger.Entry { eventID = EventTriggerType.PointerDown };
pointerDown.callback.AddListener(_ => AudioManager.Post(AK.EVENTS.UI_CLICK, button.gameObject));
trigger.triggers.Add(pointerDown);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7979f5a4eaa18c24997479b9744fe8c7

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a03a9ea48aa335d4296ffed48fec6213
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 确认页面的单个按钮配置。
/// </summary>
public class ConfirmButtonConfig
{
/// <summary>按钮显示文本(传入时应已完成本地化)。</summary>
public string Label { get; }
/// <summary>
/// 点击回调。为 null 表示仅关闭确认页面,不执行额外操作。
/// 回调在页面关闭动画结束后触发。
/// </summary>
public Action OnClick { get; }
public ConfirmButtonConfig(string label, Action onClick = null)
{
Label = label;
OnClick = onClick;
}
}
/// <summary>
/// 确认页面的完整配置数据,用于动态创建 <see cref="ConfirmUIPage"/>。
/// </summary>
public class ConfirmPageConfig
{
/// <summary>标题文本。</summary>
public string Title { get; set; } = string.Empty;
/// <summary>描述文本。</summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 按钮列表,按从左到右的顺序排列。
/// 至少需要一个按钮。
/// </summary>
public List<ConfirmButtonConfig> Buttons { get; set; } = new();
/// <summary>是否允许按 ESC 关闭(等同于取消),默认 true。</summary>
public bool AllowEscClose { get; set; } = true;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d24d0a11550bba44b9c9dbff81a0d9eb

View File

@@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using DG.Tweening;
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 通用确认 / 信息提示页面。
/// <para>
/// 作为动态 Prefab 实例化,使用完毕后自动销毁。
/// 通过 <see cref="Show"/> 静态方法创建并显示。
/// </para>
/// <para>
/// 支持任意数量的按钮:
/// - 两个按钮(确认 / 取消)用于需要用户确认的场景。
/// - 单个按钮(确定)用于信息提示场景。
/// </para>
/// </summary>
[RequireComponent(typeof(CanvasGroup))]
public class ConfirmUIPage : UIPageBase
{
// ──────────────────── 常量 ────────────────────
private const float FadeInDuration = 0.2f;
private const float FadeOutDuration = 0.15f;
// ──────────────────── 序列化引用 ────────────────────
[Header("Content")]
[SerializeField] private TMP_Text titleText;
[SerializeField] private TMP_Text descriptionText;
[Header("Buttons")]
[SerializeField] private Transform buttonContainer;
[SerializeField] private Button buttonPrefab;
// ──────────────────── 运行时状态 ────────────────────
private readonly List<Button> spawnedButtons = new();
private Action pendingCallback;
private bool allowEscClose = true;
/// <inheritdoc/>
public override bool CloseOnEsc => allowEscClose;
protected override void Start()
{
}
// ──────────────────── 静态入口 ────────────────────
/// <summary>
/// 创建并显示一个确认页面实例。
/// </summary>
/// <param name="config">页面配置,包含标题、描述和按钮。</param>
/// <returns>已创建的页面实例。</returns>
public static ConfirmUIPage Show(ConfirmPageConfig config)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
var manager = UIPageManager.Instance;
var go = manager.InstantiateDynamicPage(manager.ConfirmPagePrefab);
var page = go.GetComponent<ConfirmUIPage>();
if (page == null)
{
Debug.LogError("[ConfirmUIPage] Prefab is missing ConfirmUIPage component.");
Destroy(go);
return null;
}
page.Initialize(config);
page.Open();
return page;
}
// ──────────────────── 初始化 ────────────────────
/// <summary>
/// 根据配置初始化页面内容和按钮。
/// </summary>
private void Initialize(ConfirmPageConfig config)
{
allowEscClose = config.AllowEscClose;
if (titleText != null)
titleText.text = config.Title;
if (descriptionText != null)
descriptionText.text = config.Description;
CreateButtons(config.Buttons);
}
private void CreateButtons(List<ConfirmButtonConfig> buttonConfigs)
{
if (buttonPrefab == null)
{
Debug.LogError("[ConfirmUIPage] buttonPrefab is not assigned.");
return;
}
// 隐藏模板按钮
buttonPrefab.gameObject.SetActive(false);
foreach (var btnConfig in buttonConfigs)
{
var button = Instantiate(buttonPrefab, buttonContainer);
button.gameObject.SetActive(true);
var label = button.GetComponentInChildren<TMP_Text>();
if (label != null)
label.text = btnConfig.Label;
var callback = btnConfig.OnClick;
button.onClick.AddListener(() => OnButtonClicked(callback));
spawnedButtons.Add(button);
}
}
// ──────────────────── 生命周期 ────────────────────
public override void Open()
{
if (IsOpen) return;
IsOpen = true;
gameObject.SetActive(true);
canvasGroup.alpha = 0f;
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
UIPageManager.Instance.RegisterPage(this);
canvasGroup.DOFade(1f, FadeInDuration)
.SetUpdate(true)
.OnComplete(() =>
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
OnPageOpened();
}).Play();
}
public override void Close()
{
if (!IsOpen) return;
IsOpen = false;
UIPageManager.Instance.UnregisterPage(this);
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
canvasGroup.DOFade(0f, FadeOutDuration)
.SetUpdate(true)
.OnComplete(() =>
{
OnPageClosed();
Destroy(gameObject);
}).Play();
}
protected override void OnPageClosed()
{
// 先触发基类事件
base.OnPageClosed();
// 执行挂起的按钮回调
var callback = pendingCallback;
pendingCallback = null;
callback?.Invoke();
// 清理按钮监听
foreach (var btn in spawnedButtons)
{
if (btn != null)
btn.onClick.RemoveAllListeners();
}
spawnedButtons.Clear();
}
// ──────────────────── 按钮处理 ────────────────────
private void OnButtonClicked(Action callback)
{
if (!IsOpen) return;
pendingCallback = callback;
Close();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 56cd460c1559ee647aebac48a61fbc28

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f006c806bdd62b04a965e96c18c13637
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Cielonos.UI
{
/// <summary>
/// 交互选项列表中单个条目的 UI 组件。
/// 由 InteractionUIPage 生成并驱动。
/// 支持三种视觉状态:普通、高亮(选中)、不可用(灰色)。
/// </summary>
public class InteractionChoiceItem : UIElementBase
{
[SerializeField] private TMP_Text labelText;
[Tooltip("普通状态下的背景(可选)。")]
[SerializeField] private Image backgroundImage;
[Tooltip("高亮指示器,例如左侧箭头图标(当选中时显示)。")]
[SerializeField] private GameObject highlightIndicator;
[Header("颜色配置")]
[SerializeField] private Color normalColor = Color.white;
[SerializeField] private Color highlightedColor = new Color(0.4f, 1f, 1f); // 青色
[SerializeField] private Color disabledColor = new Color(0.45f, 0.45f, 0.45f); // 灰色
private bool _isInteractable = true;
// ====================================================================
// 对外接口
// ====================================================================
/// <summary>
/// 初始化条目内容。由 InteractionUIPage 在 Show() 时调用。
/// </summary>
/// <param name="choiceName">选项名称。</param>
/// <param name="isInteractable">false 时以灰色显示且不可高亮。</param>
public void Setup(string choiceName, bool isInteractable)
{
_isInteractable = isInteractable;
if (labelText != null)
{
labelText.text = choiceName;
labelText.color = isInteractable ? normalColor : disabledColor;
}
if (highlightIndicator != null)
{
highlightIndicator.SetActive(false);
}
}
/// <summary>
/// 设置高亮状态。由 InteractionUIPage.UpdateHighlight() 调用。
/// 不可交互的选项不会显示高亮。
/// </summary>
public void SetHighlighted(bool highlighted)
{
bool showHighlight = highlighted && _isInteractable;
if (highlightIndicator != null)
{
highlightIndicator.SetActive(showHighlight);
}
if (labelText != null)
{
if (!_isInteractable)
{
labelText.color = disabledColor;
}
else
{
labelText.color = showHighlight ? highlightedColor : normalColor;
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 664206634872acf4ea30f2cc8be87424

View File

@@ -0,0 +1,130 @@
using System.Collections.Generic;
using Cielonos.Core.Interaction;
using DG.Tweening;
using Sirenix.OdinInspector;
using SLSUtilities.UI;
using UnityEngine;
namespace Cielonos.UI
{
/// <summary>
/// 交互选项列表 UI 页面。
/// 玩家进入可交互物体范围时由 PlayerInteractionSubcontroller 调用 Show(),离开时调用 Hide()。
/// 当选项数量为 1 时,直接显示单个选项而不需要导航;
/// 当选项数量大于 1 时,玩家可用鼠标滚轮或上/下键切换高亮选项R 键执行。
/// </summary>
public class InteractionUIArea : UIElementBase
{
[Title("容器")]
[Tooltip("单个选项条目的预制体,需包含 InteractionChoiceItem 组件。")]
[SerializeField] private GameObject choiceItemPrefab;
[Tooltip("选项条目的父节点Layout Group。")]
[SerializeField] private RectTransform choiceContainer;
private readonly List<InteractionChoiceItem> _items = new List<InteractionChoiceItem>();
private void Start()
{
Hide();
}
// ====================================================================
// 对外接口
// ====================================================================
/// <summary>
/// 显示选项列表并高亮指定索引的选项。
/// 由 PlayerInteractionSubcontroller.SetCurrentInteractable() 调用。
/// </summary>
public void Show(List<InteractionChoice> choices, int selectedIndex)
{
gameObject.SetActive(true);
canvasGroup.DOFade(1f, 0.25f).From(0).OnComplete(() =>
{
canvasGroup.interactable = true;
canvasGroup.blocksRaycasts = true;
}).Play();
rectTransform.DOLocalMoveY(-50f, 0.25f).From(-250f).Play();
ClearItems();
if (choices == null || choices.Count == 0)
{
gameObject.SetActive(false);
return;
}
foreach (InteractionChoice choice in choices)
{
InteractionChoiceItem item = CreateItem();
if (item != null)
{
item.Setup(choice.choiceName, choice.isInteractable);
}
}
UpdateHighlight(selectedIndex);
}
/// <summary>
/// 更新高亮选项。由 PlayerInteractionSubcontroller.NavigateChoice() 调用。
/// </summary>
public void UpdateHighlight(int selectedIndex)
{
for (int i = 0; i < _items.Count; i++)
{
_items[i].SetHighlighted(i == selectedIndex);
}
}
/// <summary>
/// 隐藏选项列表。由 PlayerInteractionSubcontroller.RemoveCurrentInteractable() 调用。
/// </summary>
public override void Hide()
{
canvasGroup.interactable = false;
canvasGroup.blocksRaycasts = false;
canvasGroup.DOFade(0f, 0.25f).OnComplete(() =>
{
ClearItems();
gameObject.SetActive(false);
}).Play();
rectTransform.DOLocalMoveY(-250f, 0.25f).Play();
}
// ====================================================================
// 内部工具
// ====================================================================
private InteractionChoiceItem CreateItem()
{
if (choiceItemPrefab == null || choiceContainer == null)
{
Debug.LogError("[InteractionUIPage] choiceItemPrefab 或 choiceContainer 未在 Inspector 中配置。");
return null;
}
GameObject go = Instantiate(choiceItemPrefab, choiceContainer);
InteractionChoiceItem item = go.GetComponent<InteractionChoiceItem>();
if (item == null)
{
Debug.LogError("[InteractionUIPage] choiceItemPrefab 缺少 InteractionChoiceItem 组件。");
Destroy(go);
return null;
}
_items.Add(item);
return item;
}
private void ClearItems()
{
foreach (InteractionChoiceItem item in _items)
{
if (item != null) Destroy(item.gameObject);
}
_items.Clear();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3c8943c86810b074d955a3585bc5e71e

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3b5632578b757434d86d7140c9f6a154
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,462 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Cielonos.MainGame;
using Cielonos.Settings;
using Cielonos.Settings.UI;
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace Cielonos.UI
{
/// <summary>
/// 设置界面页面(单页滚动式 + 右侧面板)。
/// <para>
/// <b>左侧:</b>所有设置分类在同一页面中按顺序展示,每个分类前插入标题。
/// 通过反射读取各设置类的公共字段,按类型自动选择 Entry Prefab 实例化。
/// 同时支持手动添加的按钮条目(如"Key Bindings"按钮)。
/// </para>
/// <para>
/// <b>右侧:</b>包含两种面板状态:
/// <list type="number">
/// <item>说明面板(<see cref="descriptionPanel"/>):鼠标悬停条目时显示说明文本,离开后隐藏。</item>
/// <item>详情页面(如 <see cref="keyBindingPage"/>):点击按钮后打开的二级 UIPage
/// 打开期间悬停说明被抑制,需手动关闭。</item>
/// </list>
/// </para>
/// </summary>
public class SettingsUIPage : UIPageBase
{
// ──────────────────── Entry Prefabs ──────────────────
[Header("Entry Prefabs")]
[Tooltip("bool 字段使用的 Toggle 条目预制体,需挂载 SettingsEntryToggle。")]
[SerializeField] private GameObject toggleEntryPrefab;
[Tooltip("带 [Range] int 使 Slider SettingsEntrySlider")]
[SerializeField] private GameObject sliderEntryPrefab;
[Tooltip("enum 字段使用的 Dropdown 条目预制体,需挂载 SettingsEntryDropdown。")]
[SerializeField] private GameObject dropdownEntryPrefab;
[Tooltip("按钮条目预制体,需挂载 SettingsEntryButton。")]
[SerializeField] private GameObject buttonEntryPrefab;
// ──────────────────── Section Header ──────────────────
[Header("Section Header")]
[Tooltip("分类标题预制体,需包含 TMP_Text 组件。")]
[SerializeField] private GameObject sectionHeaderPrefab;
// ──────────────────── Left Content ────────────────────
[Header("Left Content")]
[Tooltip("条目的父容器,应挂载 VerticalLayoutGroup 和 ContentSizeFitter。")]
[SerializeField] private RectTransform contentContainer;
// ──────────────────── Right Panel ─────────────────────
[Header("Right Panel - Description")]
[Tooltip("右侧悬停说明面板,显示条目的标题和说明文本。")]
[SerializeField] private SettingsDescriptionPanel descriptionPanel;
[Header("Right Panel - Detail Pages")]
[Tooltip("键位绑定二级页面。")]
[SerializeField] private KeyBindingPage keyBindingPage;
// ──────────────────── Input ───────────────────────────
[Header("Input")]
[Tooltip("用于键位绑定页面的 InputActionAsset。为空时从 Player 获取。")]
[SerializeField] private InputActionAsset inputActionAsset;
// ──────────────────── 底部按钮 ────────────────────
[Header("Bottom Buttons")]
[SerializeField] private Button resetButton;
[SerializeField] private Button backButton;
// ──────────────────── 运行时状态 ──────────────────
private readonly List<SettingsEntryBase> activeFieldEntries = new();
private readonly List<SettingsEntryButton> activeButtonEntries = new();
private readonly List<GameObject> spawnedObjects = new();
/// <summary>是否有详情页面处于打开状态(抑制悬停说明)。</summary>
private bool isDetailPageOpen;
// ──────────────────── 分类定义 ────────────────────
/// <summary>
/// 分类定义显示名称、设置实例、Apply/Reset 回调、附加按钮列表。
/// </summary>
private struct SectionDefinition
{
public string displayName;
public object settingsInstance;
public Action applyAction;
public Action resetAction;
public List<ButtonDefinition> buttons;
}
/// <summary>
/// 手动添加的按钮条目定义。
/// </summary>
private struct ButtonDefinition
{
public string label;
public string description;
public Action onClick;
}
// ──────────────────── 生命周期 ────────────────────
protected override void Start()
{
base.Start();
resetButton?.onClick.AddListener(OnResetClicked);
backButton?.onClick.AddListener(OnBackClicked);
// 监听详情页面的开关状态
if (keyBindingPage != null)
{
keyBindingPage.PageOpened += OnDetailPageOpened;
keyBindingPage.PageClosed += OnDetailPageClosed;
}
}
protected override void OnPageOpened()
{
BuildAllSections();
}
protected override void OnPageClosed()
{
// 先关闭可能打开的详情页面
if (isDetailPageOpen && keyBindingPage != null && keyBindingPage.IsOpen)
keyBindingPage.Close();
ClearAll();
descriptionPanel?.Hide();
GameSettingsManager.Instance?.Save();
}
// ──────────────────── 详情页面状态 ────────────────
private void OnDetailPageOpened()
{
isDetailPageOpen = true;
descriptionPanel?.Hide();
}
private void OnDetailPageClosed()
{
isDetailPageOpen = false;
}
// ──────────────────── 悬停回调 ────────────────────
private void HandleHoverEnter(string title, string description)
{
if (isDetailPageOpen) return;
descriptionPanel?.Show(title, description);
}
private void HandleHoverExit()
{
if (isDetailPageOpen) return;
descriptionPanel?.Hide();
}
// ──────────────────── 分类定义构建 ────────────────
/// <summary>
/// 构建所有分类定义。扩展时在此处追加。
/// </summary>
private List<SectionDefinition> BuildSectionDefinitions()
{
var manager = GameSettingsManager.Instance;
if (manager == null) return null;
return new List<SectionDefinition>
{
new()
{
displayName = "Gameplay",
settingsInstance = manager.gameplay,
applyAction = manager.ApplyGameplay,
resetAction = manager.ResetGameplayToDefault
},
new()
{
displayName = "Graphics",
settingsInstance = manager.graphics,
applyAction = manager.ApplyGraphics,
resetAction = manager.ResetGraphicsToDefault
},
new()
{
displayName = "Sound",
settingsInstance = manager.sound,
applyAction = manager.ApplySound,
resetAction = manager.ResetSoundToDefault
},
new()
{
displayName = "Controls",
settingsInstance = manager.controls,
applyAction = manager.ApplyControls,
resetAction = manager.ResetControlsToDefault,
buttons = new List<ButtonDefinition>
{
new()
{
label = "Key Bindings",
description = "Customize keyboard and mouse bindings for all game actions.",
onClick = OpenKeyBindingPage
}
}
}
};
}
// ──────────────────── 条目生成 ────────────────────
/// <summary>
/// 构建所有分类的标题、字段条目和按钮条目。
/// </summary>
private void BuildAllSections()
{
ClearAll();
var sections = BuildSectionDefinitions();
if (sections == null)
{
Debug.LogError("[SettingsUIPage] GameSettingsManager not found.");
return;
}
foreach (var section in sections)
{
BuildSection(section);
}
}
/// <summary>
/// 构建单个分类的标题、字段条目和按钮条目。
/// </summary>
private void BuildSection(SectionDefinition section)
{
FieldInfo[] fields = section.settingsInstance.GetType()
.GetFields(BindingFlags.Public | BindingFlags.Instance);
// 预扫描:确认该分类至少有一个可显示的字段或按钮
bool hasVisibleContent = section.buttons is { Count: > 0 };
if (!hasVisibleContent)
{
foreach (FieldInfo field in fields)
{
if (field.GetCustomAttribute<SettingsIgnoreAttribute>() == null
&& ResolvePrefab(field) != null)
{
hasVisibleContent = true;
break;
}
}
}
if (!hasVisibleContent) return;
// 插入分类标题
SpawnSectionHeader(section.displayName);
// 生成字段条目
Action applyAction = section.applyAction;
foreach (FieldInfo field in fields)
{
if (field.GetCustomAttribute<SettingsIgnoreAttribute>() != null)
continue;
GameObject prefab = ResolvePrefab(field);
if (prefab == null)
{
Debug.LogWarning(
$"[SettingsUIPage] No matching prefab for field '{field.Name}' " +
$"(type: {field.FieldType.Name}). Skipping.");
continue;
}
GameObject entryObj = Instantiate(prefab, contentContainer);
var entry = entryObj.GetComponent<SettingsEntryBase>();
if (entry == null)
{
Debug.LogError(
$"[SettingsUIPage] Prefab for field '{field.Name}' " +
"is missing a SettingsEntryBase component.");
Destroy(entryObj);
continue;
}
entry.Initialize(section.settingsInstance, field, () => applyAction?.Invoke());
RegisterFieldEntryHover(entry);
activeFieldEntries.Add(entry);
spawnedObjects.Add(entryObj);
}
// 生成按钮条目
if (section.buttons != null)
{
foreach (var btnDef in section.buttons)
{
SpawnButtonEntry(btnDef);
}
}
}
/// <summary>
/// 为字段条目注册悬停回调。
/// </summary>
private void RegisterFieldEntryHover(SettingsEntryBase entry)
{
entry.OnHoverEnter = HandleHoverEnter;
entry.OnHoverExit = HandleHoverExit;
}
/// <summary>
/// 实例化按钮条目。
/// </summary>
private void SpawnButtonEntry(ButtonDefinition def)
{
if (buttonEntryPrefab == null)
{
Debug.LogWarning("[SettingsUIPage] buttonEntryPrefab is not assigned.");
return;
}
GameObject entryObj = Instantiate(buttonEntryPrefab, contentContainer);
var buttonEntry = entryObj.GetComponent<SettingsEntryButton>();
if (buttonEntry == null)
{
Debug.LogError("[SettingsUIPage] buttonEntryPrefab is missing SettingsEntryButton.");
Destroy(entryObj);
return;
}
buttonEntry.Initialize(def.label, def.description, def.onClick);
buttonEntry.OnHoverEnter = HandleHoverEnter;
buttonEntry.OnHoverExit = HandleHoverExit;
activeButtonEntries.Add(buttonEntry);
spawnedObjects.Add(entryObj);
}
/// <summary>
/// 实例化分类标题预制体。
/// </summary>
private void SpawnSectionHeader(string title)
{
if (sectionHeaderPrefab == null)
{
Debug.LogWarning("[SettingsUIPage] sectionHeaderPrefab is not assigned.");
return;
}
GameObject headerObj = Instantiate(sectionHeaderPrefab, contentContainer);
var headerText = headerObj.GetComponentInChildren<TMP_Text>();
if (headerText != null)
headerText.text = title;
spawnedObjects.Add(headerObj);
}
/// <summary>
/// 根据字段类型选择对应的预制体。
/// </summary>
private GameObject ResolvePrefab(FieldInfo field)
{
Type fieldType = field.FieldType;
if (fieldType == typeof(bool))
return toggleEntryPrefab;
if (fieldType == typeof(int))
return sliderEntryPrefab;
if (fieldType.IsEnum)
return dropdownEntryPrefab;
return null;
}
// ──────────────────── 详情页面入口 ────────────────
private void OpenKeyBindingPage()
{
if (keyBindingPage == null)
{
Debug.LogWarning("[SettingsUIPage] keyBindingPage is not assigned.");
return;
}
// 优先使用配置的 InputActionAsset否则尝试从 Player 获取
InputActionAsset asset = inputActionAsset;
if (asset == null)
{
var player = MainGameManager.Player;
if (player != null)
asset = player.inputSc.inputActions.asset;
}
if (asset != null)
{
keyBindingPage.Open(asset);
}
else
{
Debug.LogError("[SettingsUIPage] No InputActionAsset available for KeyBindingPage.");
}
}
// ──────────────────── 底部按钮回调 ────────────────
private void OnResetClicked()
{
GameSettingsManager.Instance?.ResetAllToDefault();
BuildAllSections();
}
private void OnBackClicked()
{
Close();
}
// ──────────────────── 清理 ────────────────────────
private void ClearAll()
{
foreach (var obj in spawnedObjects)
{
if (obj != null)
Destroy(obj);
}
activeFieldEntries.Clear();
activeButtonEntries.Clear();
spawnedObjects.Clear();
}
private void OnDestroy()
{
if (keyBindingPage != null)
{
keyBindingPage.PageOpened -= OnDetailPageOpened;
keyBindingPage.PageClosed -= OnDetailPageClosed;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 06a515f761ecf184399f3506f8cbf989