722 lines
34 KiB
C#
722 lines
34 KiB
C#
using UnityEngine;
|
||
using UnityEditor;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
using GameFramework;
|
||
|
||
/// <summary>
|
||
/// 一键搭建游戏 UI:HUD Canvas + 游戏结果 Canvas + 主菜单标题。
|
||
/// 菜单路径:Tools > 搭建游戏UI
|
||
/// </summary>
|
||
public static class UIBuilder
|
||
{
|
||
[MenuItem("Tools/搭建游戏UI (HUD + ResultScreen + MainMenu + Scoring)")]
|
||
public static void BuildAll()
|
||
{
|
||
BuildMainMenu();
|
||
BuildHUD();
|
||
BuildResultScreen();
|
||
BuildScoringUI();
|
||
Debug.Log("[UIBuilder] 全部 UI 搭建完成!请在 Inspector 中检查引用是否正确。");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在 MainMenu 场景中搭建主菜单 Canvas。
|
||
/// </summary>
|
||
[MenuItem("Tools/搭建 MainMenu Canvas")]
|
||
public static void BuildMainMenu()
|
||
{
|
||
// 加载美术资源
|
||
var bgSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/MainMenu/bg.jpg");
|
||
var titleSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/MainMenu/title.png");
|
||
var startSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/MainMenu/kaishiyouxi.png");
|
||
var replaySprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/MainMenu/juqinghuifang.png");
|
||
var creditsSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/MainMenu/zhizuorenyuan.png");
|
||
|
||
// 创建 MainMenu Canvas
|
||
var canvasObj = new GameObject("MainMenu_Canvas");
|
||
var canvas = canvasObj.AddComponent<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = 0;
|
||
var scaler = canvasObj.AddComponent<CanvasScaler>();
|
||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||
canvasObj.AddComponent<GraphicRaycaster>();
|
||
|
||
// 添加 MainMenuUIController 脚本
|
||
var controller = canvasObj.AddComponent<MainMenuUIController>();
|
||
|
||
// ===== 背景图 =====
|
||
if (bgSprite != null)
|
||
{
|
||
var bgObj = new GameObject("Background");
|
||
bgObj.transform.SetParent(canvasObj.transform, false);
|
||
var bgImg = bgObj.AddComponent<Image>();
|
||
bgImg.sprite = bgSprite;
|
||
bgImg.preserveAspect = false;
|
||
var bgRect = bgObj.GetComponent<RectTransform>();
|
||
bgRect.anchorMin = Vector2.zero;
|
||
bgRect.anchorMax = Vector2.one;
|
||
bgRect.sizeDelta = Vector2.zero;
|
||
}
|
||
|
||
// ===== 标题图片 =====
|
||
if (titleSprite != null)
|
||
{
|
||
var titleObj = new GameObject("TitleImage");
|
||
titleObj.transform.SetParent(canvasObj.transform, false);
|
||
var titleImg = titleObj.AddComponent<Image>();
|
||
titleImg.sprite = titleSprite;
|
||
titleImg.preserveAspect = true;
|
||
var titleRect = titleObj.GetComponent<RectTransform>();
|
||
titleRect.anchorMin = new Vector2(0.5f, 0.65f);
|
||
titleRect.anchorMax = new Vector2(0.5f, 0.65f);
|
||
titleRect.pivot = new Vector2(0.5f, 0.5f);
|
||
titleRect.sizeDelta = new Vector2(titleSprite.rect.width * 0.5f, titleSprite.rect.height * 0.5f);
|
||
|
||
// 连接 titleImage 引用
|
||
var so = new SerializedObject(controller);
|
||
so.FindProperty("titleImage").objectReferenceValue = titleImg;
|
||
so.ApplyModifiedProperties();
|
||
}
|
||
|
||
// ===== 按钮区域(居中偏下) =====
|
||
var buttonArea = new GameObject("ButtonArea");
|
||
buttonArea.transform.SetParent(canvasObj.transform, false);
|
||
var areaRect = buttonArea.AddComponent<RectTransform>();
|
||
areaRect.anchorMin = new Vector2(0.5f, 0.15f);
|
||
areaRect.anchorMax = new Vector2(0.5f, 0.15f);
|
||
areaRect.pivot = new Vector2(0.5f, 0.5f);
|
||
var layout = buttonArea.AddComponent<VerticalLayoutGroup>();
|
||
layout.spacing = 20;
|
||
layout.childForceExpandWidth = false;
|
||
layout.childForceExpandHeight = false;
|
||
layout.childAlignment = TextAnchor.UpperCenter;
|
||
buttonArea.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||
|
||
// 开始游戏按钮
|
||
Button startBtn = null;
|
||
if (startSprite != null)
|
||
{
|
||
var btn = CreateImageButton(buttonArea.transform, "StartButton", startSprite);
|
||
startBtn = btn.GetComponent<Button>();
|
||
}
|
||
|
||
// 剧情回放按钮
|
||
Button replayBtn = null;
|
||
if (replaySprite != null)
|
||
{
|
||
var btn = CreateImageButton(buttonArea.transform, "StoryReplayButton", replaySprite);
|
||
replayBtn = btn.GetComponent<Button>();
|
||
}
|
||
|
||
// 制作人员按钮
|
||
Button creditsBtn = null;
|
||
if (creditsSprite != null)
|
||
{
|
||
var btn = CreateImageButton(buttonArea.transform, "CreditsButton", creditsSprite);
|
||
creditsBtn = btn.GetComponent<Button>();
|
||
}
|
||
|
||
// ===== 连接 MainMenuUIController 引用 =====
|
||
var cso = new SerializedObject(controller);
|
||
cso.FindProperty("startButton").objectReferenceValue = startBtn;
|
||
cso.FindProperty("storyReplayButton").objectReferenceValue = replayBtn;
|
||
cso.FindProperty("creditsButton").objectReferenceValue = creditsBtn;
|
||
cso.ApplyModifiedProperties();
|
||
|
||
Debug.Log("[UIBuilder] MainMenu Canvas 搭建完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在 Gameplay 场景中搭建 HUD Canvas。
|
||
/// </summary>
|
||
[MenuItem("Tools/搭建 HUD Canvas")]
|
||
public static void BuildHUD()
|
||
{
|
||
// 加载 HUD 美术资源(可选,没有则用占位)
|
||
var soulIconSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/HUD/soul_icon.png");
|
||
var lifeIconSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/HUD/life_icon.png");
|
||
var sprintIconSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/HUD/skill_sprint.png");
|
||
var bellIconSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/HUD/skill_bell.png");
|
||
var lanternIconSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/HUD/skill_lantern.png");
|
||
|
||
// 创建 HUD Canvas
|
||
var hudCanvasObj = new GameObject("HUD_Canvas");
|
||
var canvas = hudCanvasObj.AddComponent<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = 10001;
|
||
hudCanvasObj.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
hudCanvasObj.GetComponent<CanvasScaler>().referenceResolution = new Vector2(1920, 1080);
|
||
hudCanvasObj.AddComponent<GraphicRaycaster>();
|
||
|
||
// 添加 GameHUD 脚本
|
||
var hud = hudCanvasObj.AddComponent<GameHUD>();
|
||
var so = new SerializedObject(hud);
|
||
|
||
// ===== 左上角:魂灵计数 =====
|
||
var soulArea = CreateChild(hudCanvasObj, "SoulArea", new Vector2(20, -20));
|
||
var soulLayout = soulArea.AddComponent<HorizontalLayoutGroup>();
|
||
soulLayout.spacing = 8;
|
||
soulLayout.childForceExpandWidth = false;
|
||
soulLayout.childForceExpandHeight = true;
|
||
soulLayout.childAlignment = TextAnchor.MiddleLeft;
|
||
soulArea.AddComponent<ContentSizeFitter>().horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||
|
||
// 魂灵 Icon
|
||
var soulIconObj = CreateIcon(soulArea.transform, "SoulIcon", soulIconSprite, new Vector2(40, 40), Color.cyan);
|
||
so.FindProperty("soulIcon").objectReferenceValue = soulIconObj.GetComponent<Image>();
|
||
|
||
// 魂灵数字
|
||
var soulCountObj = CreateText(soulArea.transform, "SoulCount", "0", 28, TextAnchor.MiddleLeft);
|
||
soulCountObj.GetComponent<Text>().fontStyle = FontStyle.Bold;
|
||
soulCountObj.GetComponent<RectTransform>().sizeDelta = new Vector2(60, 40);
|
||
so.FindProperty("soulCountText").objectReferenceValue = soulCountObj.GetComponent<Text>();
|
||
|
||
// ===== 左下角:生命图标(5个) =====
|
||
var lifeArea = CreateChild(hudCanvasObj, "LifeArea", new Vector2(20, -20));
|
||
var lifeAreaRect = lifeArea.GetComponent<RectTransform>();
|
||
lifeAreaRect.anchorMin = new Vector2(0, 0);
|
||
lifeAreaRect.anchorMax = new Vector2(0, 0);
|
||
lifeAreaRect.pivot = new Vector2(0, 0);
|
||
lifeAreaRect.anchoredPosition = new Vector2(20, 20);
|
||
|
||
var lifeLayout = lifeArea.AddComponent<HorizontalLayoutGroup>();
|
||
lifeLayout.spacing = 5;
|
||
lifeLayout.childForceExpandWidth = false;
|
||
lifeLayout.childForceExpandHeight = true;
|
||
lifeLayout.childAlignment = TextAnchor.LowerLeft;
|
||
|
||
var lifeIconsProp = so.FindProperty("lifeIcons");
|
||
lifeIconsProp.arraySize = 5;
|
||
for (int i = 0; i < 5; i++)
|
||
{
|
||
var lifeIconObj = CreateIcon(lifeArea.transform, $"LifeIcon_{i}", lifeIconSprite, new Vector2(36, 36), Color.red);
|
||
lifeIconsProp.GetArrayElementAtIndex(i).objectReferenceValue = lifeIconObj.GetComponent<Image>();
|
||
}
|
||
|
||
// ===== 右下角:技能图标(冲刺/摇铃/灵灯) =====
|
||
var skillArea = CreateChild(hudCanvasObj, "SkillArea", new Vector2(-20, -20));
|
||
var skillAreaRect = skillArea.GetComponent<RectTransform>();
|
||
skillAreaRect.anchorMin = new Vector2(1, 0);
|
||
skillAreaRect.anchorMax = new Vector2(1, 0);
|
||
skillAreaRect.pivot = new Vector2(1, 0);
|
||
skillAreaRect.anchoredPosition = new Vector2(-20, 20);
|
||
|
||
var skillLayout = skillArea.AddComponent<HorizontalLayoutGroup>();
|
||
skillLayout.spacing = 12;
|
||
skillLayout.childForceExpandWidth = false;
|
||
skillLayout.childForceExpandHeight = true;
|
||
skillLayout.childAlignment = TextAnchor.LowerRight;
|
||
|
||
// 冲刺技能
|
||
var sprintObj = CreateSkillIcon(skillArea.transform, "SkillSprint", sprintIconSprite, new Color(0.3f, 0.8f, 1f), "空格");
|
||
so.FindProperty("skillSprintIcon").objectReferenceValue = sprintObj.transform.Find("Icon").GetComponent<Image>();
|
||
so.FindProperty("skillSprintCD").objectReferenceValue = sprintObj.transform.Find("CDOverlay").GetComponent<Image>();
|
||
|
||
// 摇铃技能
|
||
var bellObj = CreateSkillIcon(skillArea.transform, "SkillBell", bellIconSprite, new Color(1f, 0.85f, 0.2f), "E");
|
||
so.FindProperty("skillBellIcon").objectReferenceValue = bellObj.transform.Find("Icon").GetComponent<Image>();
|
||
so.FindProperty("skillBellCD").objectReferenceValue = bellObj.transform.Find("CDOverlay").GetComponent<Image>();
|
||
|
||
// 灵灯技能
|
||
var lanternObj = CreateSkillIcon(skillArea.transform, "SkillLantern", lanternIconSprite, new Color(1f, 0.6f, 0.2f), "鼠标左键");
|
||
so.FindProperty("skillLanternIcon").objectReferenceValue = lanternObj.transform.Find("Icon").GetComponent<Image>();
|
||
so.FindProperty("skillLanternCD").objectReferenceValue = lanternObj.transform.Find("CDOverlay").GetComponent<Image>();
|
||
|
||
// ===== 右上角:暂停按钮 =====
|
||
var topRight = CreateChild(hudCanvasObj, "TopRight", new Vector2(-20, -20));
|
||
var topRightRect = topRight.GetComponent<RectTransform>();
|
||
topRightRect.anchorMin = new Vector2(1, 1);
|
||
topRightRect.anchorMax = new Vector2(1, 1);
|
||
topRightRect.pivot = new Vector2(1, 1);
|
||
var pauseObj = CreateButton(topRight.transform, "PauseButton", "||", new Vector2(50, 50));
|
||
so.FindProperty("pauseButton").objectReferenceValue = pauseObj.GetComponent<Button>();
|
||
|
||
so.ApplyModifiedProperties();
|
||
Debug.Log("[UIBuilder] HUD Canvas 搭建完成(魂灵+生命+技能CD)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在 Gameplay 场景中搭建游戏结果 Canvas。
|
||
/// </summary>
|
||
[MenuItem("Tools/搭建 ResultScreen Canvas")]
|
||
public static void BuildResultScreen()
|
||
{
|
||
// 创建 Result Canvas
|
||
var resultCanvasObj = new GameObject("ResultScreen_Canvas");
|
||
var canvas = resultCanvasObj.AddComponent<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = 10002;
|
||
resultCanvasObj.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
resultCanvasObj.GetComponent<CanvasScaler>().referenceResolution = new Vector2(1920, 1080);
|
||
resultCanvasObj.AddComponent<GraphicRaycaster>();
|
||
|
||
// 添加 GameResultScreen 脚本
|
||
var resultScreen = resultCanvasObj.AddComponent<GameResultScreen>();
|
||
|
||
// 半透明黑色背景
|
||
var bgObj = new GameObject("Overlay");
|
||
bgObj.transform.SetParent(resultCanvasObj.transform, false);
|
||
var bgImage = bgObj.AddComponent<Image>();
|
||
bgImage.color = new Color(0, 0, 0, 0.7f);
|
||
var bgRect = bgObj.GetComponent<RectTransform>();
|
||
bgRect.anchorMin = Vector2.zero;
|
||
bgRect.anchorMax = Vector2.one;
|
||
bgRect.sizeDelta = Vector2.zero;
|
||
|
||
// ===== 胜利面板 =====
|
||
var winPanel = CreateCenterPanel(resultCanvasObj, "WinPanel", new Color(0.1f, 0.3f, 0.1f, 0.9f));
|
||
var winTitle = CreateText(winPanel.transform, "WinTitle", "胜利!", 40, TextAnchor.MiddleCenter);
|
||
winTitle.GetComponent<Text>().color = new Color(1f, 0.85f, 0.2f);
|
||
winTitle.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 60);
|
||
var winScore = CreateText(winPanel.transform, "WinScore", "分数: 0", 24, TextAnchor.MiddleCenter);
|
||
winScore.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 0);
|
||
var winBtn = CreateButton(winPanel.transform, "WinConfirmBtn", "确认", new Vector2(160, 50));
|
||
winBtn.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -60);
|
||
|
||
// ===== 失败面板 =====
|
||
var losePanel = CreateCenterPanel(resultCanvasObj, "LosePanel", new Color(0.3f, 0.1f, 0.1f, 0.9f));
|
||
var loseTitle = CreateText(losePanel.transform, "LoseTitle", "游戏结束", 40, TextAnchor.MiddleCenter);
|
||
loseTitle.GetComponent<Text>().color = new Color(1f, 0.3f, 0.3f);
|
||
loseTitle.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 60);
|
||
var loseScore = CreateText(losePanel.transform, "LoseScore", "分数: 0", 24, TextAnchor.MiddleCenter);
|
||
loseScore.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 0);
|
||
var loseBtn = CreateButton(losePanel.transform, "LoseConfirmBtn", "确认", new Vector2(160, 50));
|
||
loseBtn.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, -60);
|
||
|
||
// 初始隐藏两个面板
|
||
winPanel.SetActive(false);
|
||
losePanel.SetActive(false);
|
||
|
||
// ===== 连接 GameResultScreen 引用 =====
|
||
var so = new SerializedObject(resultScreen);
|
||
so.FindProperty("resultCanvas").objectReferenceValue = canvas;
|
||
so.FindProperty("winPanel").objectReferenceValue = winPanel;
|
||
so.FindProperty("winConfirmButton").objectReferenceValue = winBtn.GetComponent<Button>();
|
||
so.FindProperty("winTitleText").objectReferenceValue = winTitle.GetComponent<Text>();
|
||
so.FindProperty("winScoreText").objectReferenceValue = winScore.GetComponent<Text>();
|
||
so.FindProperty("losePanel").objectReferenceValue = losePanel;
|
||
so.FindProperty("loseConfirmButton").objectReferenceValue = loseBtn.GetComponent<Button>();
|
||
so.FindProperty("loseTitleText").objectReferenceValue = loseTitle.GetComponent<Text>();
|
||
so.FindProperty("loseScoreText").objectReferenceValue = loseScore.GetComponent<Text>();
|
||
so.ApplyModifiedProperties();
|
||
|
||
Debug.Log("[UIBuilder] ResultScreen Canvas 搭建完成");
|
||
}
|
||
|
||
// ===== 辅助方法 =====
|
||
|
||
private static GameObject CreateChild(GameObject parent, string name, Vector2 pos)
|
||
{
|
||
var obj = new GameObject(name);
|
||
obj.transform.SetParent(parent.transform, false);
|
||
var rect = obj.AddComponent<RectTransform>();
|
||
rect.anchorMin = new Vector2(0, 1);
|
||
rect.anchorMax = new Vector2(0, 1);
|
||
rect.pivot = new Vector2(0, 1);
|
||
rect.anchoredPosition = pos;
|
||
return obj;
|
||
}
|
||
|
||
private static GameObject CreateText(Transform parent, string name, string content, int fontSize, TextAnchor alignment)
|
||
{
|
||
var obj = new GameObject(name);
|
||
obj.transform.SetParent(parent, false);
|
||
obj.AddComponent<RectTransform>();
|
||
var text = obj.AddComponent<Text>();
|
||
text.text = content;
|
||
text.fontSize = fontSize;
|
||
text.alignment = alignment;
|
||
text.color = Color.white;
|
||
text.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
|
||
if (text.font == null) text.font = Font.CreateDynamicFontFromOSFont("Arial", fontSize);
|
||
text.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||
text.verticalOverflow = VerticalWrapMode.Overflow;
|
||
return obj;
|
||
}
|
||
|
||
private static GameObject CreateButton(Transform parent, string name, string label, Vector2 size)
|
||
{
|
||
var btnObj = new GameObject(name);
|
||
btnObj.transform.SetParent(parent, false);
|
||
var rect = btnObj.AddComponent<RectTransform>();
|
||
rect.sizeDelta = size;
|
||
var img = btnObj.AddComponent<Image>();
|
||
img.color = new Color(0.3f, 0.3f, 0.3f, 0.9f);
|
||
var btn = btnObj.AddComponent<Button>();
|
||
|
||
var textObj = CreateText(btnObj.transform, "Text", label, 18, TextAnchor.MiddleCenter);
|
||
var textRect = textObj.GetComponent<RectTransform>();
|
||
textRect.anchorMin = Vector2.zero;
|
||
textRect.anchorMax = Vector2.one;
|
||
textRect.sizeDelta = Vector2.zero;
|
||
|
||
return btnObj;
|
||
}
|
||
|
||
private static GameObject CreateIcon(Transform parent, string name, Sprite sprite, Vector2 size, Color placeholderColor)
|
||
{
|
||
var obj = new GameObject(name);
|
||
obj.transform.SetParent(parent, false);
|
||
obj.AddComponent<RectTransform>().sizeDelta = size;
|
||
var img = obj.AddComponent<Image>();
|
||
if (sprite != null)
|
||
{
|
||
img.sprite = sprite;
|
||
img.preserveAspect = true;
|
||
}
|
||
else
|
||
{
|
||
img.color = placeholderColor; // 占位色
|
||
}
|
||
return obj;
|
||
}
|
||
|
||
private static GameObject CreateSkillIcon(Transform parent, string name, Sprite sprite, Color cdColor, string keyText)
|
||
{
|
||
var container = new GameObject(name);
|
||
container.transform.SetParent(parent, false);
|
||
container.AddComponent<RectTransform>().sizeDelta = new Vector2(56, 56);
|
||
|
||
// 背景
|
||
var bgObj = new GameObject("BG");
|
||
bgObj.transform.SetParent(container.transform, false);
|
||
var bgRect = bgObj.AddComponent<RectTransform>();
|
||
bgRect.anchorMin = Vector2.zero;
|
||
bgRect.anchorMax = Vector2.one;
|
||
bgRect.sizeDelta = Vector2.zero;
|
||
var bgImg = bgObj.AddComponent<Image>();
|
||
bgImg.color = new Color(0.1f, 0.1f, 0.1f, 0.7f);
|
||
|
||
// 技能图标
|
||
var iconObj = new GameObject("Icon");
|
||
iconObj.transform.SetParent(container.transform, false);
|
||
var iconRect = iconObj.AddComponent<RectTransform>();
|
||
iconRect.anchorMin = new Vector2(0.1f, 0.1f);
|
||
iconRect.anchorMax = new Vector2(0.9f, 0.9f);
|
||
iconRect.sizeDelta = Vector2.zero;
|
||
var iconImg = iconObj.AddComponent<Image>();
|
||
if (sprite != null)
|
||
{
|
||
iconImg.sprite = sprite;
|
||
iconImg.preserveAspect = true;
|
||
}
|
||
else
|
||
{
|
||
iconImg.color = cdColor; // 占位色
|
||
}
|
||
|
||
// CD 遮罩(覆盖在图标上方)
|
||
var cdObj = new GameObject("CDOverlay");
|
||
cdObj.transform.SetParent(container.transform, false);
|
||
var cdRect = cdObj.AddComponent<RectTransform>();
|
||
cdRect.anchorMin = Vector2.zero;
|
||
cdRect.anchorMax = Vector2.one;
|
||
cdRect.sizeDelta = Vector2.zero;
|
||
var cdImg = cdObj.AddComponent<Image>();
|
||
cdImg.color = new Color(0, 0, 0, 0.6f);
|
||
cdImg.type = Image.Type.Filled;
|
||
cdImg.fillMethod = Image.FillMethod.Radial360;
|
||
cdImg.fillOrigin = 2; // Top
|
||
cdImg.fillAmount = 0f;
|
||
cdObj.SetActive(false); // 初始无 CD
|
||
|
||
// 按键提示标签(黑底白字,位于图标上方)
|
||
if (!string.IsNullOrEmpty(keyText))
|
||
{
|
||
var hintObj = new GameObject("KeyHint");
|
||
hintObj.transform.SetParent(container.transform, false);
|
||
var hintRect = hintObj.AddComponent<RectTransform>();
|
||
hintRect.anchorMin = new Vector2(0.5f, 1f);
|
||
hintRect.anchorMax = new Vector2(0.5f, 1f);
|
||
hintRect.pivot = new Vector2(0.5f, 0f);
|
||
hintRect.anchoredPosition = new Vector2(0, 6f);
|
||
hintRect.sizeDelta = new Vector2(60, 22);
|
||
|
||
var hintBg = hintObj.AddComponent<Image>();
|
||
hintBg.color = new Color(0, 0, 0, 0.85f);
|
||
|
||
var textObj = new GameObject("Text");
|
||
textObj.transform.SetParent(hintObj.transform, false);
|
||
var textRect = textObj.AddComponent<RectTransform>();
|
||
textRect.anchorMin = Vector2.zero;
|
||
textRect.anchorMax = Vector2.one;
|
||
textRect.sizeDelta = Vector2.zero;
|
||
|
||
var tmp = textObj.AddComponent<TextMeshProUGUI>();
|
||
tmp.text = keyText;
|
||
tmp.fontSize = 14;
|
||
tmp.fontStyle = FontStyles.Normal;
|
||
tmp.color = Color.white;
|
||
tmp.alignment = TextAlignmentOptions.Center;
|
||
|
||
// 加载中文字体
|
||
var font = AssetDatabase.LoadAssetAtPath<TMPro.TMP_FontAsset>(
|
||
"Assets/UI/Art/No.392-ShangShouNiShangTi-2 SDF.asset");
|
||
if (font != null) tmp.font = font;
|
||
}
|
||
|
||
return container;
|
||
}
|
||
|
||
private static GameObject CreateImageButton(Transform parent, string name, Sprite sprite)
|
||
{
|
||
var btnObj = new GameObject(name);
|
||
btnObj.transform.SetParent(parent, false);
|
||
var rect = btnObj.AddComponent<RectTransform>();
|
||
// 根据 Sprite 原始尺寸设置按钮大小(缩小到合适尺寸)
|
||
float scale = 0.5f;
|
||
rect.sizeDelta = new Vector2(sprite.rect.width * scale, sprite.rect.height * scale);
|
||
var img = btnObj.AddComponent<Image>();
|
||
img.sprite = sprite;
|
||
img.preserveAspect = true;
|
||
var btn = btnObj.AddComponent<Button>();
|
||
// 设置按钮颜色过渡
|
||
var colors = btn.colors;
|
||
colors.normalColor = Color.white;
|
||
colors.highlightedColor = new Color(0.9f, 0.9f, 0.9f);
|
||
colors.pressedColor = new Color(0.7f, 0.7f, 0.7f);
|
||
btn.colors = colors;
|
||
return btnObj;
|
||
}
|
||
|
||
private static GameObject CreateCenterPanel(GameObject parent, string name, Color bgColor)
|
||
{
|
||
var panel = new GameObject(name);
|
||
panel.transform.SetParent(parent.transform, false);
|
||
var rect = panel.AddComponent<RectTransform>();
|
||
rect.anchorMin = new Vector2(0.5f, 0.5f);
|
||
rect.anchorMax = new Vector2(0.5f, 0.5f);
|
||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||
rect.sizeDelta = new Vector2(400, 250);
|
||
var img = panel.AddComponent<Image>();
|
||
img.color = bgColor;
|
||
return panel;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在 Scoring 场景中搭建结算界面 Canvas。
|
||
/// </summary>
|
||
[MenuItem("Tools/搭建 Scoring Canvas")]
|
||
public static void BuildScoringUI()
|
||
{
|
||
// 加载美术资源
|
||
var bgSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/ResultScreen/bg.png");
|
||
var winTitleSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/ResultScreen/nizuodaol.png");
|
||
var newRecordSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/ResultScreen/新纪录.png");
|
||
var scoreLabelSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/ResultScreen/拯救魂灵.png");
|
||
var mainMenuBtnSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/ResultScreen/主界面.png");
|
||
var lineSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/ResultScreen/line.png");
|
||
var bgPointSprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/UI/Art/ResultScreen/bgpoint.png");
|
||
|
||
// 创建 Scoring Canvas
|
||
var canvasObj = new GameObject("ScoringCanvas");
|
||
var canvas = canvasObj.AddComponent<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = 0;
|
||
var scaler = canvasObj.AddComponent<CanvasScaler>();
|
||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||
canvasObj.AddComponent<GraphicRaycaster>();
|
||
|
||
// 添加 ScoringUIController 脚本
|
||
var controller = canvasObj.AddComponent<ScoringUIController>();
|
||
var so = new SerializedObject(controller);
|
||
|
||
// ===== 底图 =====
|
||
var bgObj = new GameObject("Background");
|
||
bgObj.transform.SetParent(canvasObj.transform, false);
|
||
var bgImg = bgObj.AddComponent<Image>();
|
||
if (bgSprite != null) bgImg.sprite = bgSprite;
|
||
bgImg.preserveAspect = false;
|
||
var bgRect = bgObj.GetComponent<RectTransform>();
|
||
bgRect.anchorMin = Vector2.zero;
|
||
bgRect.anchorMax = Vector2.one;
|
||
bgRect.sizeDelta = Vector2.zero;
|
||
so.FindProperty("bgImage").objectReferenceValue = bgImg;
|
||
so.FindProperty("bgSprite").objectReferenceValue = bgSprite;
|
||
|
||
// ===== 胜利面板 =====
|
||
var winPanel = new GameObject("WinPanel");
|
||
winPanel.transform.SetParent(canvasObj.transform, false);
|
||
var winRect = winPanel.AddComponent<RectTransform>();
|
||
winRect.anchorMin = Vector2.zero;
|
||
winRect.anchorMax = Vector2.one;
|
||
winRect.sizeDelta = Vector2.zero;
|
||
winPanel.SetActive(false);
|
||
|
||
// "你做到了" 标题
|
||
var winTitle = CreateImageChild(winPanel.transform, "WinTitle", winTitleSprite, new Vector2(400, 80), new Vector2(0.5f, 0.75f));
|
||
so.FindProperty("winTitleImage").objectReferenceValue = winTitle.GetComponent<Image>();
|
||
so.FindProperty("winTitleSprite").objectReferenceValue = winTitleSprite;
|
||
|
||
// 魂灵数量底图
|
||
var soulBg = CreateImageChild(winPanel.transform, "SoulCountBg", bgPointSprite, new Vector2(200, 50), new Vector2(0.5f, 0.55f));
|
||
so.FindProperty("soulCountBgImage").objectReferenceValue = soulBg.GetComponent<Image>();
|
||
so.FindProperty("soulCountBgSprite").objectReferenceValue = bgPointSprite;
|
||
|
||
// 魂灵数量文字
|
||
var soulText = CreateText(winPanel.transform, "SoulCountText", "0", 36, TextAnchor.MiddleCenter);
|
||
var soulTextRect = soulText.GetComponent<RectTransform>();
|
||
soulTextRect.anchorMin = new Vector2(0.5f, 0.55f);
|
||
soulTextRect.anchorMax = new Vector2(0.5f, 0.55f);
|
||
soulTextRect.sizeDelta = new Vector2(100, 50);
|
||
so.FindProperty("soulCountText").objectReferenceValue = soulText.GetComponent<Text>();
|
||
|
||
// 新记录图片
|
||
var newRecord = CreateImageChild(winPanel.transform, "NewRecord", newRecordSprite, new Vector2(150, 40), new Vector2(0.5f, 0.45f));
|
||
newRecord.SetActive(false);
|
||
so.FindProperty("newRecordImage").objectReferenceValue = newRecord.GetComponent<Image>();
|
||
so.FindProperty("newRecordSprite").objectReferenceValue = newRecordSprite;
|
||
|
||
// 拯救魂灵标签
|
||
var scoreLabel = CreateImageChild(winPanel.transform, "ScoreLabel", scoreLabelSprite, new Vector2(200, 40), new Vector2(0.5f, 0.65f));
|
||
so.FindProperty("scoreLabelImage").objectReferenceValue = scoreLabel.GetComponent<Image>();
|
||
so.FindProperty("scoreLabelSprite").objectReferenceValue = scoreLabelSprite;
|
||
|
||
so.FindProperty("winPanel").objectReferenceValue = winPanel;
|
||
|
||
// ===== 失败面板 =====
|
||
var losePanel = new GameObject("LosePanel");
|
||
losePanel.transform.SetParent(canvasObj.transform, false);
|
||
var loseRect = losePanel.AddComponent<RectTransform>();
|
||
loseRect.anchorMin = Vector2.zero;
|
||
loseRect.anchorMax = Vector2.one;
|
||
loseRect.sizeDelta = Vector2.zero;
|
||
losePanel.SetActive(false);
|
||
|
||
// "你迷失了" 标题(暂用 nizuodaol.png 副本)
|
||
var loseTitle = CreateImageChild(losePanel.transform, "LoseTitle", winTitleSprite, new Vector2(400, 80), new Vector2(0.5f, 0.6f));
|
||
so.FindProperty("loseTitleImage").objectReferenceValue = loseTitle.GetComponent<Image>();
|
||
so.FindProperty("loseTitleSprite").objectReferenceValue = winTitleSprite;
|
||
|
||
so.FindProperty("losePanel").objectReferenceValue = losePanel;
|
||
|
||
// ===== 排行榜面板 =====
|
||
var lbPanel = new GameObject("LeaderboardPanel");
|
||
lbPanel.transform.SetParent(canvasObj.transform, false);
|
||
var lbRect = lbPanel.AddComponent<RectTransform>();
|
||
lbRect.anchorMin = new Vector2(0.3f, 0.1f);
|
||
lbRect.anchorMax = new Vector2(0.7f, 0.4f);
|
||
lbRect.sizeDelta = Vector2.zero;
|
||
lbPanel.SetActive(false);
|
||
so.FindProperty("leaderboardPanel").objectReferenceValue = lbPanel;
|
||
|
||
// 最终分数
|
||
var finalScore = CreateText(lbPanel.transform, "FinalScore", "0", 24, TextAnchor.MiddleCenter);
|
||
finalScore.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 0);
|
||
so.FindProperty("finalScoreText").objectReferenceValue = finalScore.GetComponent<Text>();
|
||
|
||
// 排行榜容器
|
||
var lbContainer = new GameObject("LeaderboardContainer");
|
||
lbContainer.transform.SetParent(lbPanel.transform, false);
|
||
var lbContRect = lbContainer.AddComponent<RectTransform>();
|
||
lbContRect.anchorMin = Vector2.zero;
|
||
lbContRect.anchorMax = Vector2.one;
|
||
lbContRect.sizeDelta = Vector2.zero;
|
||
var lbLayout = lbContainer.AddComponent<VerticalLayoutGroup>();
|
||
lbLayout.spacing = 2;
|
||
lbLayout.childForceExpandHeight = true;
|
||
so.FindProperty("leaderboardContainer").objectReferenceValue = lbContainer.transform;
|
||
|
||
// 创建10个排行榜条目
|
||
for (int i = 0; i < 10; i++)
|
||
{
|
||
var entry = new GameObject($"Entry_{i}");
|
||
entry.transform.SetParent(lbContainer.transform, false);
|
||
var entryLayout = entry.AddComponent<HorizontalLayoutGroup>();
|
||
entryLayout.spacing = 10;
|
||
entryLayout.childForceExpandWidth = true;
|
||
entryLayout.childForceExpandHeight = true;
|
||
|
||
CreateText(entry.transform, "Rank", $"{i + 1}.", 14, TextAnchor.MiddleCenter);
|
||
CreateText(entry.transform, "Score", "---", 14, TextAnchor.MiddleCenter);
|
||
CreateText(entry.transform, "Name", "---", 14, TextAnchor.MiddleCenter);
|
||
}
|
||
|
||
// ===== 新纪录输入面板 =====
|
||
var nhsPanel = new GameObject("NewHighScorePanel");
|
||
nhsPanel.transform.SetParent(canvasObj.transform, false);
|
||
var nhsRect = nhsPanel.AddComponent<RectTransform>();
|
||
nhsRect.anchorMin = new Vector2(0.3f, 0.3f);
|
||
nhsRect.anchorMax = new Vector2(0.7f, 0.5f);
|
||
nhsRect.sizeDelta = Vector2.zero;
|
||
nhsPanel.SetActive(false);
|
||
so.FindProperty("newHighScorePanel").objectReferenceValue = nhsPanel;
|
||
|
||
var nameInput = new GameObject("NameInput");
|
||
nameInput.transform.SetParent(nhsPanel.transform, false);
|
||
nameInput.AddComponent<RectTransform>().sizeDelta = new Vector2(200, 40);
|
||
nameInput.AddComponent<Image>().color = new Color(1, 1, 1, 0.8f);
|
||
var inputField = nameInput.AddComponent<InputField>();
|
||
var textArea = new GameObject("Text");
|
||
textArea.transform.SetParent(nameInput.transform, false);
|
||
var taRect = textArea.AddComponent<RectTransform>();
|
||
taRect.anchorMin = Vector2.zero;
|
||
taRect.anchorMax = Vector2.one;
|
||
taRect.sizeDelta = Vector2.zero;
|
||
var taText = textArea.AddComponent<Text>();
|
||
taText.text = "";
|
||
taText.alignment = TextAnchor.MiddleCenter;
|
||
taText.color = Color.black;
|
||
inputField.textComponent = taText;
|
||
so.FindProperty("nameInputField").objectReferenceValue = inputField;
|
||
|
||
var submitBtn = CreateButton(nhsPanel.transform, "SubmitButton", "提交", new Vector2(100, 40));
|
||
submitBtn.GetComponent<RectTransform>().anchoredPosition = new Vector2(-60, -50);
|
||
so.FindProperty("submitButton").objectReferenceValue = submitBtn.GetComponent<Button>();
|
||
|
||
var cancelBtn = CreateButton(nhsPanel.transform, "CancelButton", "取消", new Vector2(100, 40));
|
||
cancelBtn.GetComponent<RectTransform>().anchoredPosition = new Vector2(60, -50);
|
||
so.FindProperty("cancelButton").objectReferenceValue = cancelBtn.GetComponent<Button>();
|
||
|
||
// ===== 装饰 =====
|
||
var lineObj = CreateImageChild(canvasObj.transform, "Line", lineSprite, new Vector2(300, 4), new Vector2(0.5f, 0.5f));
|
||
so.FindProperty("lineImage").objectReferenceValue = lineObj.GetComponent<Image>();
|
||
so.FindProperty("lineSprite").objectReferenceValue = lineSprite;
|
||
|
||
var bgPointObj = CreateImageChild(canvasObj.transform, "BgPoint", bgPointSprite, new Vector2(60, 60), new Vector2(0.15f, 0.85f));
|
||
so.FindProperty("bgPointImage").objectReferenceValue = bgPointObj.GetComponent<Image>();
|
||
so.FindProperty("bgPointSprite").objectReferenceValue = bgPointSprite;
|
||
|
||
// ===== 返回主菜单按钮 =====
|
||
var menuBtn = CreateButton(canvasObj.transform, "MainMenuButton", "返回主菜单", new Vector2(160, 50));
|
||
var menuBtnRect = menuBtn.GetComponent<RectTransform>();
|
||
menuBtnRect.anchorMin = new Vector2(0.5f, 0.05f);
|
||
menuBtnRect.anchorMax = new Vector2(0.5f, 0.05f);
|
||
menuBtnRect.pivot = new Vector2(0.5f, 0.5f);
|
||
so.FindProperty("mainMenuButton").objectReferenceValue = menuBtn.GetComponent<Button>();
|
||
|
||
// 设置按钮图片
|
||
if (mainMenuBtnSprite != null)
|
||
{
|
||
var btnImg = menuBtn.GetComponent<Image>();
|
||
btnImg.sprite = mainMenuBtnSprite;
|
||
so.FindProperty("mainMenuButtonImage").objectReferenceValue = btnImg;
|
||
so.FindProperty("mainMenuBtnSprite").objectReferenceValue = mainMenuBtnSprite;
|
||
}
|
||
|
||
so.ApplyModifiedProperties();
|
||
Debug.Log("[UIBuilder] Scoring Canvas 搭建完成");
|
||
}
|
||
|
||
private static GameObject CreateImageChild(Transform parent, string name, Sprite sprite, Vector2 size, Vector2 anchor)
|
||
{
|
||
var obj = new GameObject(name);
|
||
obj.transform.SetParent(parent, false);
|
||
var rect = obj.AddComponent<RectTransform>();
|
||
rect.anchorMin = anchor;
|
||
rect.anchorMax = anchor;
|
||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||
rect.sizeDelta = size;
|
||
var img = obj.AddComponent<Image>();
|
||
if (sprite != null)
|
||
{
|
||
img.sprite = sprite;
|
||
img.preserveAspect = true;
|
||
}
|
||
return obj;
|
||
}
|
||
}
|