UI系统组件

This commit is contained in:
2026-06-06 09:00:11 +08:00
parent fe4fd60083
commit d794b83ebe
107 changed files with 25690 additions and 476 deletions

View File

@@ -313,9 +313,25 @@ namespace BaseGames.Editor
// ── 流式加载系统 ──────────────────────────────────────────────────
ScaffoldStreamingSystem(services, report);
// ── 多设备 UI 焦点守护EventSystem 上挂 UISelectionRestorer──────────
EnsureUISelectionRestorer(report);
MarkDirtyAndLog("Persistent 场景脚手架", root, report);
}
/// <summary>确保场景中的 EventSystem 挂有 <see cref="BaseGames.UI.UISelectionRestorer"/>(多设备焦点守护,幂等)。</summary>
private static void EnsureUISelectionRestorer(List<string> report)
{
var es = Object.FindObjectOfType<UnityEngine.EventSystems.EventSystem>();
if (es == null)
{
report.Add("未找到 EventSystemUISelectionRestorer 未挂载(键盘/手柄丢失焦点后无法自动恢复)。请确认 Persistent 场景含 EventSystem + InputSystemUIInputModule。");
return;
}
if (es.GetComponent<BaseGames.UI.UISelectionRestorer>() == null)
Undo.AddComponent<BaseGames.UI.UISelectionRestorer>(es.gameObject);
}
// ─────────────────────────────────────────────────────────────────────
// Scaffold Main Menu Scene
// ─────────────────────────────────────────────────────────────────────
@@ -357,6 +373,7 @@ namespace BaseGames.Editor
var subTmp = GetOrAddComponent<TextMeshProUGUI>(subtitleRt.gameObject);
subTmp.text = "A 2D Action Adventure"; subTmp.fontSize = 40f; subTmp.alignment = TextAlignmentOptions.Center;
subTmp.color = new Color(0.7f, 0.66f, 0.55f, 0.9f); subTmp.raycastTarget = false; subTmp.characterSpacing = 8f;
BindLocalizedText(subtitleRt.gameObject, "MENU_SUBTITLE");
// ── 主菜单控制器 ──────────────────────────────────────────────────
MainMenuController menuCtrl = GetOrAddComponent<MainMenuController>(canvasGo);
@@ -386,6 +403,11 @@ namespace BaseGames.Editor
var le = GetOrAddComponent<LayoutElement>(b);
le.preferredHeight = 64f; le.minHeight = 56f;
}
BindLocalizedButton(btnNewGameGo, "MENU_NEW_GAME");
BindLocalizedButton(btnContinueGo, "MENU_CONTINUE");
BindLocalizedButton(btnSettingsGo, "MENU_SETTINGS");
BindLocalizedButton(btnCreditsGo, "MENU_CREDITS");
BindLocalizedButton(btnQuitGo, "MENU_QUIT");
AssignReference(menuCtrl, "_btnNewGame", btnNewGameGo.GetComponent<Button>());
AssignReference(menuCtrl, "_btnContinue", btnContinueGo.GetComponent<Button>());
@@ -416,6 +438,7 @@ namespace BaseGames.Editor
var slotTitleTmp = GetOrAddComponent<TextMeshProUGUI>(slotTitleRt.gameObject);
slotTitleTmp.text = "Select Save"; slotTitleTmp.fontSize = 56f; slotTitleTmp.fontStyle = FontStyles.Bold;
slotTitleTmp.alignment = TextAlignmentOptions.Center; slotTitleTmp.color = GoldText; slotTitleTmp.raycastTarget = false;
BindLocalizedText(slotTitleRt.gameObject, "SAVESLOT_TITLE");
// 卡片容器(居中竖排)
var slotsContainerRt = GetOrCreateUIChild(saveSlotPanelGo.transform, "SlotsContainer");
@@ -450,6 +473,7 @@ namespace BaseGames.Editor
SetRect(slotBackRt, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
new Vector2(0f, 70f), new Vector2(260f, 64f));
StyleAsTextButton(slotBackGo, 30f);
BindLocalizedButton(slotBackGo, "BTN_BACK");
AssignReference(menuCtrl, "_btnCloseSaveSlot", slotBackGo.GetComponent<Button>());
// ── ConfirmDialog覆盖 / 删除确认)─────────────────────
@@ -460,15 +484,17 @@ namespace BaseGames.Editor
NewGameModeController modeCtrl = BuildNewGameMode(saveSlotPanelGo.transform);
AssignReference(saveSlotCtrl, "_modeSelect", modeCtrl);
// ── SettingsPanel ─────────────────────────────────────────────────
// ── SettingsPanel(音量 / 画面 / 可访问性 / 语言)──────────────────
var settingsPanelRt = GetOrCreateUIChild(canvasGo.transform, "SettingsPanel");
StretchFull(settingsPanelRt);
BuildSettingsPanel(settingsPanelRt.gameObject, menuCtrl, report);
settingsPanelRt.gameObject.SetActive(false);
AssignReference(menuCtrl, "_settingsPanel", settingsPanelRt.gameObject);
// ── CreditsPanel ──────────────────────────────────────────────────
// ── CreditsPanel(制作团队)────────────────────────────────────────
var creditsPanelRt = GetOrCreateUIChild(canvasGo.transform, "CreditsPanel");
StretchFull(creditsPanelRt);
BuildCreditsPanel(creditsPanelRt.gameObject, menuCtrl, report);
creditsPanelRt.gameObject.SetActive(false);
AssignReference(menuCtrl, "_creditsPanel", creditsPanelRt.gameObject);
@@ -505,20 +531,38 @@ namespace BaseGames.Editor
bgImg.type = Image.Type.Simple; bgImg.preserveAspect = true; bgImg.enabled = false;
bgImg.transform.SetSiblingIndex(1);
// 全覆盖选择按钮(透明,金色高亮;位于信息层之下,靠 raycast 接收点击
// 选中/悬停高亮叠加(基色白不透明;显隐由按钮 ColorBlock 的 alpha 控制 → 可见的选中反馈
var hlImg = GetOrCreateImage(slotGo.transform, "Highlight", Color.white, false);
hlImg.transform.SetSiblingIndex(2);
// 全覆盖选择按钮(透明,靠 raycast 接收点击;着色目标为 Highlight 叠加图)
GameObject selectGo = GetOrCreateButtonChild(slotGo.transform, "SelectButton", "");
StretchFull((RectTransform)selectGo.transform);
var selImg = selectGo.GetComponent<Image>();
if (selImg != null) selImg.color = new Color(1f, 1f, 1f, 0f);
var selLabel = GetButtonLabel(selectGo);
if (selLabel != null) selLabel.gameObject.SetActive(false);
var selBtn = selectGo.GetComponent<Button>();
if (selBtn != null)
{
selBtn.targetGraphic = hlImg;
var cc = selBtn.colors;
cc.normalColor = new Color(1f, 0.85f, 0.45f, 0f); // 静态:透明
cc.highlightedColor = new Color(1f, 0.86f, 0.5f, 0.08f); // 悬停:极淡金
cc.selectedColor = new Color(1f, 0.86f, 0.5f, 0.13f); // 键盘/手柄选中:淡金叠加
cc.pressedColor = new Color(1f, 0.86f, 0.5f, 0.22f);
cc.disabledColor = new Color(1f, 1f, 1f, 0f);
cc.colorMultiplier = 1f; cc.fadeDuration = 0.1f;
selBtn.colors = cc;
}
// 空槽提示
var emptyRt = GetOrCreateUIChild(slotGo.transform, "EmptyIndicator");
StretchFull(emptyRt);
GameObject emptyGo = emptyRt.gameObject;
GetOrCreateText(emptyGo.transform, "EmptyText", "Empty Slot · New Game", 34f,
var emptyText = GetOrCreateText(emptyGo.transform, "EmptyText", "Empty Slot · New Game", 34f,
new Color(0.7f, 0.66f, 0.55f, 0.85f), TextAlignmentOptions.Center);
BindLocalizedText(emptyText.gameObject, "SAVESLOT_EMPTY");
// 有档信息区(左侧竖排:区域 / 时长 / 时间)+ 右侧(灵珠 / 生命 / 钢魂)
var dataRt = GetOrCreateUIChild(slotGo.transform, "DataIndicator");
@@ -540,7 +584,8 @@ namespace BaseGames.Editor
var badgeRt = GetOrCreateUIChild(dataGo.transform, "SteelSoulBadge");
SetRect(badgeRt, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(0f, 4f), new Vector2(120f, 40f));
GetOrAddComponent<Image>(badgeRt.gameObject).color = new Color(0.5f, 0.55f, 0.6f, 0.5f);
GetOrCreateText(badgeRt.transform, "BadgeText", "STEEL", 22f, new Color(0.85f,0.9f,1f,1f), TextAlignmentOptions.Center);
var badgeText = GetOrCreateText(badgeRt.transform, "BadgeText", "STEEL", 22f, new Color(0.85f,0.9f,1f,1f), TextAlignmentOptions.Center);
BindLocalizedText(badgeText.gameObject, "BADGE_STEELSOUL");
GameObject badgeGo = badgeRt.gameObject;
// 删除按钮(右上角小 ×
@@ -597,6 +642,8 @@ namespace BaseGames.Editor
yesGo.GetComponent<Image>().color = new Color(0.45f, 0.12f, 0.12f, 0.85f);
GameObject noGo = GetOrCreateButtonChild(boxRt.transform, "Btn_Cancel", "Cancel");
SetRect((RectTransform)noGo.transform, new Vector2(0.5f,0f), new Vector2(0.5f,0f), new Vector2(0.5f,0f), new Vector2(150f,40f), new Vector2(220f,64f));
BindLocalizedButton(yesGo, "CONFIRM_YES");
BindLocalizedButton(noGo, "CONFIRM_NO");
AssignReference(confirmCtrl, "_root", confirmGo);
AssignReference(confirmCtrl, "_titleText", titleTmp);
@@ -638,6 +685,10 @@ namespace BaseGames.Editor
GameObject backGo = GetOrCreateButtonChild(boxRt.transform, "Btn_Back", "Back");
SetRect((RectTransform)backGo.transform, new Vector2(0.5f,0f), new Vector2(0.5f,0f), new Vector2(0.5f,0f), new Vector2(0f,46f), new Vector2(260f,60f));
StyleAsTextButton(backGo, 28f);
BindLocalizedText(modeTitle.gameObject, "MODE_SELECT_TITLE");
BindLocalizedButton(normalGo, "MODE_NORMAL");
BindLocalizedButton(steelGo, "MODE_STEELSOUL");
BindLocalizedButton(backGo, "BTN_BACK");
AssignReference(modeCtrl, "_root", modeGo);
AssignReference(modeCtrl, "_btnNormal", normalGo.GetComponent<Button>());
@@ -1073,18 +1124,273 @@ namespace BaseGames.Editor
return t != null ? t.GetComponent<TextMeshProUGUI>() : null;
}
/// <summary>将按钮改造为"纯文字"风格(透明底,仅金色高亮),用于主菜单主按钮列表。</summary>
/// <summary>
/// 将按钮改造为"纯文字"风格(透明底)。关键:把 Button 的 targetGraphic 指向文字 Label
/// 这样鼠标悬停Highlighted与键盘/手柄选中Selected会直接给文字着色 → 导航有可见反馈。
/// Label 基色设为白,由 ColorBlock 决定静态/高亮/选中的可见色。
/// </summary>
private static void StyleAsTextButton(GameObject buttonGo, float fontSize = 34f)
{
var img = buttonGo.GetComponent<Image>();
if (img != null) img.color = new Color(1f, 1f, 1f, 0f); // 透明底,仍可作 raycast target
if (img != null) { img.color = new Color(1f, 1f, 1f, 0f); img.raycastTarget = true; } // 透明底作 raycast target
var label = GetButtonLabel(buttonGo);
if (label != null)
if (label == null) return;
label.fontSize = fontSize;
label.fontStyle = FontStyles.Normal;
label.color = Color.white; // 基色白,实际可见色由 ColorBlock × 该色得到
var btn = buttonGo.GetComponent<Button>();
if (btn == null) return;
btn.targetGraphic = label; // 文字作为着色目标 → 选中/悬停可见
var c = btn.colors;
c.normalColor = new Color(0.60f, 0.56f, 0.42f, 1f); // 静态:暗金
c.highlightedColor = new Color(1f, 0.95f, 0.72f, 1f); // 鼠标悬停:亮金
c.selectedColor = new Color(1f, 0.95f, 0.72f, 1f); // 键盘/手柄选中:亮金
c.pressedColor = new Color(1f, 0.82f, 0.38f, 1f);
c.disabledColor = new Color(0.4f, 0.4f, 0.4f, 0.5f);
c.colorMultiplier = 1f; c.fadeDuration = 0.1f;
btn.colors = c;
}
/// <summary>
/// 给含 TMP_Text 的节点挂上 <see cref="BaseGames.Localization.LocalizedText"/> 并绑定本地化 keyUI 表)。
/// 自动绑定 FontConfig语言切换时换 CJK 字体),并立即刷新编辑器预览(显示当前语言文本)。
/// </summary>
private static void BindLocalizedText(GameObject textGo, string key)
{
if (textGo == null || textGo.GetComponent<TMP_Text>() == null) return;
var loc = GetOrAddComponent<BaseGames.Localization.LocalizedText>(textGo);
var so = new SerializedObject(loc);
so.FindProperty("_key").stringValue = key;
var fontCfg = FindFirstAssetByType<BaseGames.Localization.LanguageFontConfigSO>("FontConfig");
if (fontCfg != null) so.FindProperty("_fontConfig").objectReferenceValue = fontCfg;
so.ApplyModifiedPropertiesWithoutUndo();
loc.UpdateEditorPreview();
}
/// <summary>给 GetOrCreateButtonChild 生成的按钮的 "Label" 子节点绑定本地化 key。</summary>
private static void BindLocalizedButton(GameObject buttonGo, string key)
{
var label = GetButtonLabel(buttonGo);
if (label != null) BindLocalizedText(label.gameObject, key);
}
// ─────────────────────────────────────────────────────────────────────
// 设置控件辅助(复用 Unity DefaultControls / TMP_DefaultControls 标准控件层级)
// ─────────────────────────────────────────────────────────────────────
private static UnityEngine.UI.DefaultControls.Resources _uiRes;
private static UnityEngine.UI.DefaultControls.Resources UIRes()
{
if (_uiRes.standard == null)
_uiRes = new UnityEngine.UI.DefaultControls.Resources
{
standard = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd"),
background = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Background.psd"),
inputField = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/InputFieldBackground.psd"),
knob = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Knob.psd"),
checkmark = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Checkmark.psd"),
dropdown = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/DropdownArrow.psd"),
mask = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UIMask.psd"),
};
return _uiRes;
}
private static TMPro.TMP_DefaultControls.Resources _tmpRes;
private static TMPro.TMP_DefaultControls.Resources TMPRes()
{
if (_tmpRes.standard == null)
_tmpRes = new TMPro.TMP_DefaultControls.Resources
{
standard = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd"),
background = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Background.psd"),
inputField = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/InputFieldBackground.psd"),
knob = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Knob.psd"),
checkmark = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/Checkmark.psd"),
dropdown = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/DropdownArrow.psd"),
mask = AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UIMask.psd"),
};
return _tmpRes;
}
/// <summary>将控件 RectTransform 放到行的右半区(横向拉伸、固定高度、垂直居中)。</summary>
private static void PlaceRightHalf(RectTransform rt, float height, float rightInset = 12f)
{
rt.anchorMin = new Vector2(0.5f, 0.5f);
rt.anchorMax = new Vector2(1f, 0.5f);
rt.pivot = new Vector2(0.5f, 0.5f);
rt.offsetMin = new Vector2(8f, -height / 2f);
rt.offsetMax = new Vector2(-rightInset, height / 2f);
}
/// <summary>创建/获取一行设置项(左侧本地化标签 + 右半区控件,由调用方填充),返回行 RectTransform。</summary>
private static RectTransform CreateSettingRow(Transform content, string name, string labelKey)
{
var row = GetOrCreateUIChild(content, name);
var le = GetOrAddComponent<LayoutElement>(row.gameObject);
le.preferredHeight = 56f; le.minHeight = 50f;
var label = GetOrCreateText(row, "Label", labelKey, 26f, GoldText, TextAlignmentOptions.MidlineLeft);
SetRect((RectTransform)label.transform, new Vector2(0f, 0f), new Vector2(0.5f, 1f), new Vector2(0f, 0.5f), new Vector2(12f, 0f), Vector2.zero);
((RectTransform)label.transform).offsetMin = new Vector2(12f, 0f);
((RectTransform)label.transform).offsetMax = new Vector2(0f, 0f);
BindLocalizedText(label.gameObject, labelKey);
return row;
}
private static Slider GetOrCreateSliderInRow(RectTransform row, float min, float max, float val, float rightInset = 12f)
{
var existing = row.Find("Slider");
GameObject go;
if (existing != null && existing.GetComponent<Slider>() != null) go = existing.gameObject;
else
{
label.fontSize = fontSize;
label.fontStyle = FontStyles.Normal;
label.color = GoldText;
if (existing != null) Undo.DestroyObjectImmediate(existing.gameObject);
go = UnityEngine.UI.DefaultControls.CreateSlider(UIRes());
go.name = "Slider";
Undo.RegisterCreatedObjectUndo(go, "Create Slider");
go.transform.SetParent(row, false);
}
PlaceRightHalf((RectTransform)go.transform, 20f, rightInset);
var s = go.GetComponent<Slider>();
s.minValue = min; s.maxValue = max; s.value = val;
return s;
}
private static Toggle GetOrCreateToggleInRow(RectTransform row)
{
var existing = row.Find("Toggle");
GameObject go;
if (existing != null && existing.GetComponent<Toggle>() != null) go = existing.gameObject;
else
{
if (existing != null) Undo.DestroyObjectImmediate(existing.gameObject);
go = UnityEngine.UI.DefaultControls.CreateToggle(UIRes());
go.name = "Toggle";
Undo.RegisterCreatedObjectUndo(go, "Create Toggle");
go.transform.SetParent(row, false);
}
// 隐藏勾选框自带的 Label设置项标签由行左侧统一提供
var lbl = go.transform.Find("Label");
if (lbl != null) lbl.gameObject.SetActive(false);
PlaceRightHalf((RectTransform)go.transform, 28f);
return go.GetComponent<Toggle>();
}
private static TMPro.TMP_Dropdown GetOrCreateDropdownInRow(RectTransform row, string[] options)
{
var existing = row.Find("Dropdown");
GameObject go;
if (existing != null && existing.GetComponent<TMPro.TMP_Dropdown>() != null) go = existing.gameObject;
else
{
if (existing != null) Undo.DestroyObjectImmediate(existing.gameObject);
go = TMPro.TMP_DefaultControls.CreateDropdown(TMPRes());
go.name = "Dropdown";
Undo.RegisterCreatedObjectUndo(go, "Create Dropdown");
go.transform.SetParent(row, false);
}
PlaceRightHalf((RectTransform)go.transform, 36f);
var dd = go.GetComponent<TMPro.TMP_Dropdown>();
dd.options.Clear();
foreach (var o in options) dd.options.Add(new TMPro.TMP_Dropdown.OptionData(o));
dd.value = 0;
dd.RefreshShownValue();
return dd;
}
// ─────────────────────────────────────────────────────────────────────
// 设置面板 / 制作团队面板构建器
// ─────────────────────────────────────────────────────────────────────
/// <summary>构建设置面板内容(音量 / 画面 / 可访问性 / 语言)并绑定 SettingsPanelController 全部字段。</summary>
private static void BuildSettingsPanel(GameObject panelGo, MainMenuController menuCtrl, List<string> report)
{
var ctrl = GetOrAddComponent<BaseGames.UI.SettingsPanelController>(panelGo);
GetOrCreateImage(panelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true).transform.SetAsFirstSibling();
var titleTmp = GetOrCreateText(panelGo.transform, "PanelTitle", "Settings", 56f, GoldText, TextAlignmentOptions.Center);
titleTmp.fontStyle = FontStyles.Bold;
SetRect((RectTransform)titleTmp.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -70f), new Vector2(900f, 80f));
BindLocalizedText(titleTmp.gameObject, "SETTINGS_TITLE");
var content = GetOrCreateUIChild(panelGo.transform, "Content");
SetRect(content, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, -10f), new Vector2(860f, 720f));
var vlg = GetOrAddComponent<VerticalLayoutGroup>(content.gameObject);
vlg.spacing = 8f; vlg.childAlignment = TextAnchor.UpperCenter;
vlg.childControlWidth = true; vlg.childControlHeight = true;
vlg.childForceExpandWidth = true; vlg.childForceExpandHeight = false;
// 音量
var rMaster = CreateSettingRow(content, "Row_Master", "SETTINGS_MASTER_VOLUME");
var rBgm = CreateSettingRow(content, "Row_BGM", "SETTINGS_BGM_VOLUME");
var rSfx = CreateSettingRow(content, "Row_SFX", "SETTINGS_SFX_VOLUME");
var rAmbient = CreateSettingRow(content, "Row_Ambient", "SETTINGS_AMBIENT_VOLUME");
var sMaster = GetOrCreateSliderInRow(rMaster, 0f, 1f, 1f);
var sBgm = GetOrCreateSliderInRow(rBgm, 0f, 1f, 0.8f);
var sSfx = GetOrCreateSliderInRow(rSfx, 0f, 1f, 1f);
var sAmbient = GetOrCreateSliderInRow(rAmbient, 0f, 1f, 0.8f);
// 画面
var rVsync = CreateSettingRow(content, "Row_VSync", "SETTINGS_VSYNC");
var tVsync = GetOrCreateToggleInRow(rVsync);
var rFps = CreateSettingRow(content, "Row_FPS", "SETTINGS_FPS");
var dFps = GetOrCreateDropdownInRow(rFps, new[] { "30", "60", "120", "∞" });
// 可访问性
var rUiScale = CreateSettingRow(content, "Row_UIScale", "SETTINGS_UI_SCALE");
var sUiScale = GetOrCreateSliderInRow(rUiScale, 0.8f, 1.5f, 1f, 76f);
var uiScaleVal = GetOrCreateText(rUiScale, "ValueText", "100%", 22f, new Color(0.8f,0.78f,0.7f,1f), TextAlignmentOptions.MidlineRight);
SetRect((RectTransform)uiScaleVal.transform, new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(1f, 0.5f), new Vector2(-12f, 0f), new Vector2(64f, 36f));
var rColorblind = CreateSettingRow(content, "Row_Colorblind", "SETTINGS_COLORBLIND");
var dColorblind = GetOrCreateDropdownInRow(rColorblind, new[] { "关闭", "红色盲", "绿色盲", "蓝黄色盲" });
var rShake = CreateSettingRow(content, "Row_ScreenShake", "SETTINGS_SCREEN_SHAKE");
var tShake = GetOrCreateToggleInRow(rShake);
// 语言
var rLang = CreateSettingRow(content, "Row_Language", "SETTINGS_LANGUAGE");
var dLang = GetOrCreateDropdownInRow(rLang, new[] { "中文", "English", "日本語", "한국어" });
// 绑定 SettingsPanelController 字段
AssignReference(ctrl, "_masterVolume", sMaster);
AssignReference(ctrl, "_bgmVolume", sBgm);
AssignReference(ctrl, "_sfxVolume", sSfx);
AssignReference(ctrl, "_ambientVolume", sAmbient);
AssignReference(ctrl, "_vSyncToggle", tVsync);
AssignReference(ctrl, "_fpsDropdown", dFps);
AssignReference(ctrl, "_uiScaleSlider", sUiScale);
AssignReference(ctrl, "_uiScaleValueText", uiScaleVal);
AssignReference(ctrl, "_colorblindDropdown", dColorblind);
AssignReference(ctrl, "_screenShakeToggle", tShake);
AssignReference(ctrl, "_languageDropdown", dLang);
// 返回按钮
GameObject backGo = GetOrCreateButtonChild(panelGo.transform, "BackButton", "Back");
SetRect((RectTransform)backGo.transform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 60f), new Vector2(260f, 64f));
StyleAsTextButton(backGo, 30f);
BindLocalizedButton(backGo, "BTN_BACK");
AssignReference(menuCtrl, "_btnCloseSettings", backGo.GetComponent<Button>());
}
/// <summary>构建制作团队面板(标题 + 滚动正文 + 返回)。</summary>
private static void BuildCreditsPanel(GameObject panelGo, MainMenuController menuCtrl, List<string> report)
{
GetOrCreateImage(panelGo.transform, "Overlay", new Color(0.04f, 0.05f, 0.08f, 0.97f), true).transform.SetAsFirstSibling();
var titleTmp = GetOrCreateText(panelGo.transform, "PanelTitle", "Credits", 56f, GoldText, TextAlignmentOptions.Center);
titleTmp.fontStyle = FontStyles.Bold;
SetRect((RectTransform)titleTmp.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -70f), new Vector2(900f, 80f));
BindLocalizedText(titleTmp.gameObject, "CREDITS_TITLE");
var body = GetOrCreateText(panelGo.transform, "Body", "Credits", 30f, new Color(0.82f, 0.8f, 0.74f, 1f), TextAlignmentOptions.Top);
SetRect((RectTransform)body.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 0f), new Vector2(900f, 600f));
BindLocalizedText(body.gameObject, "CREDITS_BODY");
GameObject backGo = GetOrCreateButtonChild(panelGo.transform, "BackButton", "Back");
SetRect((RectTransform)backGo.transform, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 60f), new Vector2(260f, 64f));
StyleAsTextButton(backGo, 30f);
BindLocalizedButton(backGo, "BTN_BACK");
AssignReference(menuCtrl, "_btnCloseCredits", backGo.GetComponent<Button>());
}
private static void AssignReference(Object target, string propertyName, Object value)