From 55fdc2ec4afee737aa312bd25b2a9a3926a5c044 Mon Sep 17 00:00:00 2001 From: KoalaTrapLord <265133236+KoalaTrapLord@users.noreply.github.com> Date: Mon, 30 Mar 2026 20:31:09 -0400 Subject: [PATCH] feat: add CreateBattlegroundScene editor script to build full scene programmatically --- .../Assets/Editor/CreateBattlegroundScene.cs | 390 ++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 unity-client/Assets/Editor/CreateBattlegroundScene.cs diff --git a/unity-client/Assets/Editor/CreateBattlegroundScene.cs b/unity-client/Assets/Editor/CreateBattlegroundScene.cs new file mode 100644 index 0000000..1965673 --- /dev/null +++ b/unity-client/Assets/Editor/CreateBattlegroundScene.cs @@ -0,0 +1,390 @@ +// Assets/Editor/CreateBattlegroundScene.cs +// Unity menu: Commander AI Lab ▶ Create Battleground Scene +// Builds the entire Battleground scene hierarchy and wires every SerializeField. +// Run once on an empty scene, then hit Play. + +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.EventSystems; +using TMPro; +using CommanderAILab.UI; +using CommanderAILab.Services; + +namespace CommanderAILab.Editor +{ + public static class CreateBattlegroundScene + { + // ── Phase names in enum order (matches PhaseTrackerWidget.Phase) ────── + private static readonly string[] PhaseNames = + { + "Untap", "Upkeep", "Draw", "Main1", + "Begin Combat", "Attackers", "Blockers", + "1st Strike", "Damage", "End Combat", + "Main2", "End Step", "Cleanup" + }; + + // ── Seat labels ─────────────────────────────────────────────────────── + private static readonly string[] SeatNames = { "Bottom", "Right", "Top", "Left" }; + + // ───────────────────────────────────────────────────────────────────── + [MenuItem("Commander AI Lab/Create Battleground Scene")] + public static void Build() + { + // 1. Open a fresh scene + if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return; + var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); + + // 2. Services (DontDestroyOnLoad singletons — just need to exist in scene) + var services = NewGO("Services"); + var apiClient = services.AddComponent(); + // GameWebSocketClient added below so BattlegroundController can find it + var wsClientGO = NewGO("GameWebSocketClient", services.transform); + + // 3. GameStateManager singleton + var gsmGO = NewGO("GameStateManager"); + gsmGO.AddComponent(); + + // 4. EventSystem (required for all UI input) + var esGO = NewGO("EventSystem"); + esGO.AddComponent(); + esGO.AddComponent(); + + // 5. Main Camera + var camGO = NewGO("Main Camera"); + var cam = camGO.AddComponent(); + cam.clearFlags = CameraClearFlags.SolidColor; + cam.backgroundColor = new Color(0.08f, 0.12f, 0.08f); // dark green + cam.orthographic = true; + cam.orthographicSize = 5f; + cam.tag = "MainCamera"; + camGO.AddComponent(); + + // 6. Root Canvas + var canvasGO = NewGO("Canvas"); + var canvas = canvasGO.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvasGO.AddComponent(); + canvasGO.AddComponent(); + + // ── 7. Seat Panels (4 RectTransforms around the board) ──────────── + var seatsRoot = NewGO("SeatPanels", canvasGO.transform); + var seatPanels = new RectTransform[4]; + for (int i = 0; i < 4; i++) + { + var sp = NewGO($"SeatPanel_{SeatNames[i]}", seatsRoot.transform); + var rt = sp.AddComponent(); + var img = sp.AddComponent(); + img.color = new Color(0f, 0f, 0f, 0.3f); + StretchFill(rt); + seatPanels[i] = rt; + } + + // ── 8. Center panel ─────────────────────────────────────────────── + var centerGO = NewGO("BattlefieldCenter", canvasGO.transform); + StretchFill(centerGO.GetComponent() ?? centerGO.AddComponent()); + + // ── 9. PhaseTrackerWidget ───────────────────────────────────────── + var ptwGO = NewGO("PhaseTracker", centerGO.transform); + var ptWidget = ptwGO.AddComponent(); + SetAnchored(ptwGO, new Vector2(0f, 0f), new Vector2(1f, 0.08f)); // bottom strip + + // -- Active player + turn number labels + var aplGO = MakeTMPText("ActivePlayerLabel", ptwGO.transform, "Player's Turn", 18); + var tnlGO = MakeTMPText("TurnNumberLabel", ptwGO.transform, "Turn 1", 16); + + // -- 13 PhaseStepButtons + var stepButtons = new PhaseStepButton[13]; + for (int i = 0; i < 13; i++) + { + var btnGO = NewGO($"PhaseBtn_{PhaseNames[i]}", ptwGO.transform); + var img = btnGO.AddComponent(); + img.color = new Color(0.2f, 0.2f, 0.2f); + + var psb = btnGO.AddComponent(); + + // PhaseStepButton.background + label — set via reflection (private SerializeFields) + var labelGO = MakeTMPText("Label", btnGO.transform, PhaseNames[i], 11); + SetPrivateField(psb, "background", img); + SetPrivateField(psb, "label", labelGO.GetComponent()); + + stepButtons[i] = psb; + } + + // Wire PhaseTrackerWidget SerializeFields + SetPrivateField(ptWidget, "stepButtons", stepButtons); + SetPrivateField(ptWidget, "activePlayerLabel", aplGO.GetComponent()); + SetPrivateField(ptWidget, "turnNumberLabel", tnlGO.GetComponent()); + + // ── 10. TurnOrderBar ────────────────────────────────────────────── + var tobGO = NewGO("TurnOrderBar", centerGO.transform); + var tobBar = tobGO.AddComponent(); + SetAnchored(tobGO, new Vector2(0f, 0.92f), new Vector2(1f, 1f)); // top strip + + // ActiveBorder (sliding highlight) + var borderGO = NewGO("ActiveBorder", tobGO.transform); + var borderRT = borderGO.AddComponent(); + var borderImg = borderGO.AddComponent(); + borderImg.color = new Color(1f, 0.85f, 0f, 0.35f); + + // 4 TurnOrderPills + var pills = new TurnOrderPill[4]; + for (int i = 0; i < 4; i++) + { + var pillGO = NewGO($"Pill_{i}", tobGO.transform); + var pillImg = pillGO.AddComponent(); + pillImg.color = new Color(0.25f, 0.25f, 0.25f); + var pill = pillGO.AddComponent(); + + var nameLabel = MakeTMPText("PlayerName", pillGO.transform, $"Seat {i}", 13); + var lifeLabel = MakeTMPText("Life", pillGO.transform, "40", 13); + var youBadge = NewGO("YouBadge", pillGO.transform); + MakeTMPText("YouText", youBadge.transform, "YOU", 11); + youBadge.SetActive(i == 0); + + SetPrivateField(pill, "playerNameLabel", nameLabel.GetComponent()); + SetPrivateField(pill, "lifeLabel", lifeLabel.GetComponent()); + SetPrivateField(pill, "pillBackground", pillImg); + SetPrivateField(pill, "youBadge", youBadge); + + pills[i] = pill; + } + + SetPrivateField(tobBar, "pills", pills); + SetPrivateField(tobBar, "activeBorder", borderRT); + + // ── 11. LobbySetupModal ─────────────────────────────────────────── + var modalGO = NewGO("LobbySetupModal", canvasGO.transform); + var modal = modalGO.AddComponent(); + StretchFill(modalGO.GetComponent() ?? modalGO.AddComponent()); + + // Dark overlay panel + var panelGO = NewGO("Panel", modalGO.transform); + var panelImg = panelGO.AddComponent(); + panelImg.color = new Color(0f, 0f, 0f, 0.85f); + SetAnchored(panelGO, new Vector2(0.2f, 0.1f), new Vector2(0.8f, 0.9f)); + + MakeTMPText("Title", panelGO.transform, "Configure Seats", 24); + + // 4 seat rows + var seatRows = new LobbyRowUI[4]; + for (int i = 0; i < 4; i++) + { + var rowGO = NewGO($"SeatRow_{i}", panelGO.transform); + rowGO.AddComponent(); + var row = rowGO.AddComponent(); + + var seatLbl = MakeTMPText("SeatLabel", rowGO.transform, $"Seat {i}", 14); + var togGO = MakeToggle("HumanToggle", rowGO.transform); + var deckDD = MakeTMPDropdown("DeckDropdown", rowGO.transform); + var styleDD = MakeTMPDropdown("AiStyleDropdown", rowGO.transform); + + SetPrivateField(row, "seatLabel", seatLbl.GetComponent()); + SetPrivateField(row, "humanToggle", togGO.GetComponent()); + SetPrivateField(row, "deckDropdown", deckDD.GetComponent()); + SetPrivateField(row, "aiStyleDropdown", styleDD.GetComponent()); + + seatRows[i] = row; + } + + // Error label (starts inactive) + var errGO = MakeTMPText("ErrorLabel", panelGO.transform, "", 13); + errGO.GetComponent().color = Color.red; + errGO.SetActive(false); + + // Confirm + Cancel buttons + var confirmBtn = MakeButton("ConfirmButton", panelGO.transform, "Confirm"); + var cancelBtn = MakeButton("CancelButton", panelGO.transform, "Cancel"); + + // Wire LobbySetupModal SerializeFields + SetPrivateField(modal, "seatRows", seatRows); + SetPrivateField(modal, "confirmButton", confirmBtn.GetComponent