using System; using System.Linq; using SepCore.UI; using TMPro; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; namespace SepCore.Editor { public static class BattleUIPrefabBuilder { private const string PrefabPath = "Assets/GameMain/UI/UIForms/BattleForm.prefab"; private const string CharacterAtlasPath = "Assets/GameMain/Textures/Characters.png"; private const string EnemyAtlasPath = "Assets/GameMain/Textures/Monsters.png"; private const string ItemAtlasPath = "Assets/GameMain/Textures/Items.png"; private const string PreviewCameraName = "__BattlePreviewCamera"; private const string PreviewRootName = "__BattlePreview"; private const string PreviousScenePathKey = "BattleUI.PreviousScenePath"; private static readonly Color Overlay = new Color32(8, 7, 12, 184); private static readonly Color Charcoal = new Color32(25, 24, 30, 255); private static readonly Color CharcoalSoft = new Color32(42, 39, 47, 255); private static readonly Color Ivory = new Color32(244, 237, 214, 255); private static readonly Color Ink = new Color32(43, 37, 35, 255); private static readonly Color White = new Color32(248, 248, 246, 255); private static readonly Color MutedWhite = new Color32(192, 190, 184, 255); private static readonly Color Green = new Color32(62, 164, 94, 255); private static readonly Color Blue = new Color32(62, 119, 190, 255); private static readonly Color Red = new Color32(191, 69, 65, 255); private static readonly Color Gold = new Color32(218, 169, 64, 255); private static readonly Color EnemyFrame = new Color32(91, 39, 54, 235); private static TMP_FontAsset font; private static Sprite panelBeige; private static Sprite panelBrown; private static Sprite panelBlue; private static Sprite buttonBlue; private static Sprite buttonBluePressed; private static Sprite buttonBrown; private static Sprite buttonBrownPressed; private static Sprite buttonBeige; private static Sprite buttonBeigePressed; private static Sprite buttonGrey; private static Sprite buttonGreyPressed; private static Sprite escapeIcon; private static Sprite[] playerIcons; private static Sprite[] enemyIcons; private static Sprite attackIcon; private static Sprite skillIcon; private static Sprite itemIcon; [MenuItem("Utility/UI/Build Battle UI")] public static void Build() { LoadAssets(); Scene previousScene = SceneManager.GetActiveScene(); Scene temporaryScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive); GameObject root = null; try { SceneManager.SetActiveScene(temporaryScene); root = BuildRoot(); UISerializationRoot serializationRoot = root.GetComponent(); serializationRoot.RefreshItems(); UIAssetsTools.ApplyGeneratedComponentsToRoot(serializationRoot); bool success; PrefabUtility.SaveAsPrefabAsset(root, PrefabPath, out success); if (!success) { throw new InvalidOperationException("Failed to save BattleForm prefab."); } } finally { if (root != null) { UnityEngine.Object.DestroyImmediate(root); } if (previousScene.IsValid()) { SceneManager.SetActiveScene(previousScene); } EditorSceneManager.CloseScene(temporaryScene, true); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Selection.activeObject = AssetDatabase.LoadAssetAtPath(PrefabPath); Debug.Log("Battle UI prefab rebuilt: " + PrefabPath); } [MenuItem("Utility/UI/Preview Battle UI")] public static void OpenPreview() { ClosePreviewInternal(); Scene previousScene = SceneManager.GetActiveScene(); SessionState.SetString(PreviousScenePathKey, previousScene.path); Scene previewScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive); SceneManager.SetActiveScene(previewScene); GameObject cameraObject = new GameObject(PreviewCameraName, typeof(Camera)); Camera previewCamera = cameraObject.GetComponent(); previewCamera.clearFlags = CameraClearFlags.SolidColor; previewCamera.backgroundColor = new Color(0.22f, 0.25f, 0.23f, 1f); previewCamera.orthographic = true; previewCamera.orthographicSize = 5f; cameraObject.transform.position = new Vector3(0f, 0f, -10f); GameObject prefab = AssetDatabase.LoadAssetAtPath(PrefabPath); if (prefab == null) { throw new InvalidOperationException("Build BattleForm before opening its preview."); } GameObject previewRoot = (GameObject)PrefabUtility.InstantiatePrefab(prefab, previewScene); previewRoot.name = PreviewRootName; Canvas canvas = previewRoot.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceCamera; canvas.worldCamera = previewCamera; canvas.planeDistance = 1f; previewRoot.GetComponent().localScale = Vector3.one; Selection.activeGameObject = previewRoot; Debug.Log("Battle UI preview opened in an unsaved scene."); } [MenuItem("Utility/UI/Close Battle UI Preview")] public static void ClosePreview() { ClosePreviewInternal(); } private static void ClosePreviewInternal() { Camera[] cameras = Resources.FindObjectsOfTypeAll(); Camera previewCamera = cameras.FirstOrDefault(candidate => candidate != null && candidate.gameObject.name == PreviewCameraName && candidate.gameObject.scene.IsValid()); if (previewCamera == null) { return; } Scene previewScene = previewCamera.gameObject.scene; string previousPath = SessionState.GetString(PreviousScenePathKey, string.Empty); for (int i = 0; i < SceneManager.sceneCount; i++) { Scene loadedScene = SceneManager.GetSceneAt(i); if (loadedScene.IsValid() && loadedScene.path == previousPath) { SceneManager.SetActiveScene(loadedScene); break; } } EditorSceneManager.CloseScene(previewScene, true); SessionState.EraseString(PreviousScenePathKey); } private static GameObject BuildRoot() { GameObject root = new GameObject("BattleForm", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster), typeof(CanvasGroup), typeof(UISerializationRoot)); root.layer = LayerMask.NameToLayer("UI"); Canvas canvas = root.GetComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvas.pixelPerfect = true; canvas.overrideSorting = true; canvas.sortingOrder = 500; CanvasScaler scaler = root.GetComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920f, 1080f); scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight; scaler.matchWidthOrHeight = 0.5f; RectTransform rootRect = root.GetComponent(); rootRect.anchorMin = Vector2.zero; rootRect.anchorMax = Vector2.one; rootRect.offsetMin = Vector2.zero; rootRect.offsetMax = Vector2.zero; AddImage(Stretch("BattleOverlay", rootRect, 0f, 0f, 0f, 0f), Overlay, null); RectTransform safeFrame = Fixed("SafeFrame", rootRect, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(1920f, 1080f)); BuildTurnOrder(safeFrame); BuildPlayers(safeFrame); BuildEnemies(safeFrame); BuildActionPanel(safeFrame); return root; } private static void BuildTurnOrder(RectTransform parent) { RectTransform panel = AddPanel( Fixed("TurnOrderPanel", parent, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -24f), new Vector2(1016f, 112f)), panelBrown, Color.white); AddImage(TopStretch("ActiveAccent", panel, 0f, 0f, 0f, 6f), Gold, null); TextMeshProUGUI round = AddText( Fixed("RoundText", panel, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(28f, 0f), new Vector2(156f, 74f)), "ROUND 01", 25, Ivory, TextAlignmentOptions.MidlineLeft, FontStyles.Bold); RegisterReference(round, "roundText"); Sprite[] icons = { playerIcons[3], enemyIcons[2], playerIcons[0], enemyIcons[0], playerIcons[2], enemyIcons[1], playerIcons[1], enemyIcons[3] }; string[] labels = { "P4", "E3", "P1", "E1", "P3", "E2", "P2", "E4" }; for (int i = 0; i < 8; i++) { RectTransform slotRect = Fixed("TurnSlot" + (i + 1), panel, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f), new Vector2(194f + i * 98f, 0f), new Vector2(82f, 82f)); Image background = AddImage(slotRect, i == 0 ? Ivory : CharcoalSoft, null); background.raycastTarget = false; RectTransform marker = Stretch("ActiveMarker", slotRect, -3f, -3f, -3f, -3f); Image markerImage = AddImage(marker, Color.clear, null); Outline markerOutline = marker.gameObject.AddComponent(); markerOutline.effectColor = Gold; markerOutline.effectDistance = new Vector2(3f, -3f); markerOutline.useGraphicAlpha = false; marker.gameObject.SetActive(i == 0); Image icon = AddSprite(Fixed("Icon", slotRect, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -5f), new Vector2(54f, 54f)), icons[i], Color.white, true); TextMeshProUGUI label = AddText( Fixed("Label", slotRect, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 5f), new Vector2(68f, 22f)), labels[i], 15, i == 0 ? Ink : MutedWhite, TextAlignmentOptions.Center, FontStyles.Bold); BattleTurnSlotItem slot = slotRect.gameObject.AddComponent(); slot.ConfigureEditor(marker.gameObject, icon, label); RegisterReference(slot, "turnSlot" + (i + 1)); } } private static void BuildPlayers(RectTransform parent) { int[] hp = { 92, 150, 58, 100 }; int[] maxHp = { 120, 150, 90, 100 }; int[] mp = { 24, 10, 72, 50 }; int[] maxMp = { 40, 30, 80, 50 }; CreatePlayerCard(parent, 0, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(32f, -32f), false, hp[0], maxHp[0], mp[0], maxMp[0], false); CreatePlayerCard(parent, 1, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-32f, -32f), true, hp[1], maxHp[1], mp[1], maxMp[1], false); CreatePlayerCard(parent, 2, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(32f, 28f), false, hp[2], maxHp[2], mp[2], maxMp[2], false); CreatePlayerCard(parent, 3, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-32f, 28f), true, hp[3], maxHp[3], mp[3], maxMp[3], true); } private static void CreatePlayerCard(RectTransform parent, int index, Vector2 anchor, Vector2 pivot, Vector2 position, bool mirror, int hp, int maxHp, int mp, int maxMp, bool active) { RectTransform cardRect = AddPanel(Fixed("PlayerCard" + (index + 1), parent, anchor, pivot, position, new Vector2(400f, 176f)), panelBeige, Color.white); RectTransform marker = TopStretch("ActiveMarker", cardRect, 0f, 0f, 0f, 7f); AddImage(marker, Gold, null); marker.gameObject.SetActive(active); float iconX = mirror ? 278f : 18f; float contentX = mirror ? 18f : 140f; RectTransform iconFrame = Fixed("IconFrame", cardRect, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(iconX, -18f), new Vector2(104f, 140f)); AddImage(iconFrame, Charcoal, null); Image icon = AddSprite(Fixed("Icon", iconFrame, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, -8f), new Vector2(88f, 96f)), playerIcons[index], Color.white, true); AddText(Fixed("PartyIndex", iconFrame, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0f, 7f), new Vector2(84f, 24f)), "P" + (index + 1), 14, MutedWhite, TextAlignmentOptions.Center, FontStyles.Bold); TextMeshProUGUI name = AddText( Fixed("CharacterName", cardRect, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(contentX, -14f), new Vector2(242f, 38f)), "角色 " + (index + 1), 24, Ink, mirror ? TextAlignmentOptions.TopRight : TextAlignmentOptions.TopLeft, FontStyles.Bold); Image hpFill = CreateBar(cardRect, "HP", contentX, 64f, 242f, Green, hp, maxHp, out TextMeshProUGUI hpText); Image mpFill = CreateBar(cardRect, "MP", contentX, 112f, 242f, Blue, mp, maxMp, out TextMeshProUGUI mpText); BattleActorCardItem card = cardRect.gameObject.AddComponent(); card.ConfigureEditor(marker.gameObject, icon, hpFill, mpFill, name, hpText, mpText); RegisterReference(card, "playerCard" + (index + 1)); } private static void BuildEnemies(RectTransform parent) { RectTransform enemyRoot = Fixed("EnemyRoot", parent, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(0f, 32f), new Vector2(820f, 290f)); int[] hp = { 36, 80, 44, 61 }; int[] maxHp = { 50, 80, 60, 80 }; for (int i = 0; i < 4; i++) { RectTransform slotRect = Fixed("EnemySlot" + (i + 1), enemyRoot, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), new Vector2(-300f + i * 200f, 0f), new Vector2(170f, 248f)); Image targetGraphic = AddImage(slotRect, new Color(EnemyFrame.r, EnemyFrame.g, EnemyFrame.b, 0.68f), null); targetGraphic.raycastTarget = true; Button targetButton = slotRect.gameObject.AddComponent