Files
WarGame/Assets/Scripts/Editor/AutoBattlerSetupHelper.cs

936 lines
45 KiB
C#
Raw Normal View History

2026-09-30 23:45:58 +01:00
using System.Collections.Generic;
using System.IO;
using TMPro;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
using WarGame.Data;
using WarGame.Deployment;
using WarGame.Managers;
using WarGame.UI;
using WarGame.Units;
namespace WarGame.EditorTools
{
[InitializeOnLoad]
public static class AutoBattlerSetupHelper
{
private const string DataPath = "Assets/Data/Units";
private const string PrefabPath = "Assets/Prefabs/Units";
private const string UIPath = "Assets/Prefabs/UI";
static AutoBattlerSetupHelper()
{
EditorApplication.delayCall += CheckInitialSetup;
}
private static void CheckInitialSetup()
{
if (!File.Exists("Assets/Prefabs/Units/PF_BaseUnit.prefab"))
{
SetupBatch();
}
}
[MenuItem("Tools/WarGame/Setup Complete AutoBattler (Assets + Scene)", false, 1)]
public static void SetupCompleteAutoBattler()
{
ExecuteSetup(isInteractive: true);
}
public static void SetupBatch()
{
ExecuteSetup(isInteractive: false);
}
public static void ExecuteSetup(bool isInteractive)
{
EnsureDirectories();
// 1. Create Unit Prefabs
GameObject unitBasePrefab = CreateOrUpdateUnitPrefab();
GameObject cardPrefab = CreateOrUpdateShopCardPrefab();
// 2. Create UnitData ScriptableObjects
List<UnitDataSO> playerUnits = CreatePlayerUnitData(unitBasePrefab);
List<UnitDataSO> enemyUnits = CreateEnemyUnitData(unitBasePrefab);
// 3. Setup Scene Hierarchy
SetupSceneHierarchy(playerUnits, enemyUnits, cardPrefab, unitBasePrefab);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
if (isInteractive)
{
EditorUtility.DisplayDialog(
"WarGame Setup Complete",
"Successfully generated all ScriptableObjects, Prefabs, UI, and Scene Managers!\n\n" +
"• Player Units: Swordsman, Archer, Knight\n" +
"• Enemy Types: Bandit Warrior, Bandit Archer\n" +
"• Advanced Tactics: Flanking (Lv.2), Feint Retreat (Lv.3)\n" +
"• HUD & Shop: Live Unit Counts, Economy, Battle Results\n\n" +
"You can now press Play to test recruitment, leveling, and auto-battles!",
"Awesome!"
);
}
else
{
Debug.Log("[WarGame] AutoBattler setup completed successfully.");
}
}
private static void EnsureDirectories()
{
if (!Directory.Exists(DataPath)) Directory.CreateDirectory(DataPath);
if (!Directory.Exists(PrefabPath)) Directory.CreateDirectory(PrefabPath);
if (!Directory.Exists(UIPath)) Directory.CreateDirectory(UIPath);
AssetDatabase.Refresh();
}
private static GameObject CreateOrUpdateUnitPrefab()
{
string prefabFilePath = $"{PrefabPath}/PF_BaseUnit.prefab";
GameObject existing = AssetDatabase.LoadAssetAtPath<GameObject>(prefabFilePath);
if (existing != null) return existing;
// Create temporary instance
GameObject root = new GameObject("PF_BaseUnit");
// Add Collider & NavMeshAgent
CapsuleCollider collider = root.AddComponent<CapsuleCollider>();
collider.center = new Vector3(0, 1f, 0);
collider.radius = 0.5f;
collider.height = 2f;
NavMeshAgent agent = root.AddComponent<NavMeshAgent>();
agent.speed = 3.5f;
agent.angularSpeed = 360f;
agent.acceleration = 12f;
agent.stoppingDistance = 1.2f;
agent.radius = 0.5f;
agent.height = 2f;
// Visual mesh (Capsule)
GameObject visual = GameObject.CreatePrimitive(PrimitiveType.Capsule);
visual.name = "Visual";
visual.transform.SetParent(root.transform);
visual.transform.localPosition = new Vector3(0, 1f, 0);
visual.transform.localScale = new Vector3(0.9f, 0.9f, 0.9f);
Object.DestroyImmediate(visual.GetComponent<Collider>()); // remove inner collider
Renderer renderer = visual.GetComponent<Renderer>();
// Forward direction indicator (Nose / visor)
GameObject visor = GameObject.CreatePrimitive(PrimitiveType.Cube);
visor.name = "Visor";
visor.transform.SetParent(visual.transform);
visor.transform.localPosition = new Vector3(0, 0.4f, 0.45f);
visor.transform.localScale = new Vector3(0.5f, 0.2f, 0.3f);
Object.DestroyImmediate(visor.GetComponent<Collider>());
// Health Bar World Canvas
GameObject healthBarObj = new GameObject("HealthBar_Canvas");
healthBarObj.transform.SetParent(root.transform);
healthBarObj.transform.localPosition = new Vector3(0, 2.3f, 0);
Canvas canvas = healthBarObj.AddComponent<Canvas>();
canvas.renderMode = RenderMode.WorldSpace;
RectTransform canvasRect = canvas.GetComponent<RectTransform>();
canvasRect.sizeDelta = new Vector2(1.5f, 0.6f);
healthBarObj.transform.localScale = new Vector3(0.015f, 0.015f, 0.015f);
// Slider Background
GameObject sliderObj = new GameObject("HealthSlider");
sliderObj.transform.SetParent(healthBarObj.transform);
RectTransform sliderRect = sliderObj.AddComponent<RectTransform>();
sliderRect.sizeDelta = new Vector2(100f, 15f);
sliderRect.anchoredPosition = new Vector2(0, 5f);
Slider slider = sliderObj.AddComponent<Slider>();
// Slider Background Image
GameObject bgObj = new GameObject("Background");
bgObj.transform.SetParent(sliderObj.transform);
RectTransform bgRect = bgObj.AddComponent<RectTransform>();
bgRect.anchorMin = Vector2.zero;
bgRect.anchorMax = Vector2.one;
bgRect.sizeDelta = Vector2.zero;
Image bgImg = bgObj.AddComponent<Image>();
bgImg.color = new Color(0.15f, 0.15f, 0.15f, 0.85f);
// Fill Area & Fill
GameObject fillArea = new GameObject("Fill Area");
fillArea.transform.SetParent(sliderObj.transform);
RectTransform fillAreaRect = fillArea.AddComponent<RectTransform>();
fillAreaRect.anchorMin = Vector2.zero;
fillAreaRect.anchorMax = Vector2.one;
fillAreaRect.sizeDelta = Vector2.zero;
GameObject fillObj = new GameObject("Fill");
fillObj.transform.SetParent(fillArea.transform);
RectTransform fillRect = fillObj.AddComponent<RectTransform>();
fillRect.anchorMin = Vector2.zero;
fillRect.anchorMax = Vector2.one;
fillRect.sizeDelta = Vector2.zero;
Image fillImg = fillObj.AddComponent<Image>();
fillImg.color = new Color(0.2f, 0.8f, 0.2f);
slider.targetGraphic = fillImg;
slider.fillRect = fillRect;
slider.value = 1f;
// Level Text
GameObject levelTextObj = new GameObject("LevelText");
levelTextObj.transform.SetParent(healthBarObj.transform);
RectTransform levelRect = levelTextObj.AddComponent<RectTransform>();
levelRect.anchoredPosition = new Vector2(-60f, 5f);
levelRect.sizeDelta = new Vector2(50f, 20f);
TextMeshProUGUI levelText = levelTextObj.AddComponent<TextMeshProUGUI>();
levelText.text = "Lv.1";
levelText.fontSize = 18;
levelText.alignment = TextAlignmentOptions.Center;
levelText.color = Color.yellow;
// Unit Name Text
GameObject nameTextObj = new GameObject("NameText");
nameTextObj.transform.SetParent(healthBarObj.transform);
RectTransform nameRect = nameTextObj.AddComponent<RectTransform>();
nameRect.anchoredPosition = new Vector2(0, 25f);
nameRect.sizeDelta = new Vector2(150f, 20f);
TextMeshProUGUI nameText = nameTextObj.AddComponent<TextMeshProUGUI>();
nameText.text = "Unit";
nameText.fontSize = 16;
nameText.alignment = TextAlignmentOptions.Center;
nameText.color = Color.white;
// Tactic Status Text
GameObject tacticTextObj = new GameObject("TacticText");
tacticTextObj.transform.SetParent(healthBarObj.transform);
RectTransform tacticRect = tacticTextObj.AddComponent<RectTransform>();
tacticRect.anchoredPosition = new Vector2(0, 48f);
tacticRect.sizeDelta = new Vector2(180f, 22f);
TextMeshProUGUI tacticText = tacticTextObj.AddComponent<TextMeshProUGUI>();
tacticText.text = "FLANKING!";
tacticText.fontSize = 18;
tacticText.fontStyle = FontStyles.Bold;
tacticText.alignment = TextAlignmentOptions.Center;
tacticText.color = Color.yellow;
tacticTextObj.SetActive(false);
// Setup UnitHealthBar component
UnitHealthBar healthBarComp = healthBarObj.AddComponent<UnitHealthBar>();
SerializedObject hbSo = new SerializedObject(healthBarComp);
hbSo.FindProperty("healthSlider").objectReferenceValue = slider;
hbSo.FindProperty("fillImage").objectReferenceValue = fillImg;
hbSo.FindProperty("levelText").objectReferenceValue = levelText;
hbSo.FindProperty("tacticStatusText").objectReferenceValue = tacticText;
hbSo.FindProperty("unitNameText").objectReferenceValue = nameText;
hbSo.ApplyModifiedProperties();
// Setup Unit component
Unit unitComp = root.AddComponent<Unit>();
SerializedObject unitSo = new SerializedObject(unitComp);
unitSo.FindProperty("agent").objectReferenceValue = agent;
unitSo.FindProperty("healthBar").objectReferenceValue = healthBarComp;
unitSo.FindProperty("mainRenderer").objectReferenceValue = renderer;
unitSo.ApplyModifiedProperties();
// Save as Prefab
GameObject prefabAsset = PrefabUtility.SaveAsPrefabAsset(root, prefabFilePath);
Object.DestroyImmediate(root);
return prefabAsset;
}
private static GameObject CreateOrUpdateShopCardPrefab()
{
string prefabFilePath = $"{UIPath}/PF_ShopCard.prefab";
GameObject existing = AssetDatabase.LoadAssetAtPath<GameObject>(prefabFilePath);
if (existing != null) return existing;
GameObject cardRoot = new GameObject("PF_ShopCard");
RectTransform rootRect = cardRoot.AddComponent<RectTransform>();
rootRect.sizeDelta = new Vector2(230f, 130f);
Image bg = cardRoot.AddComponent<Image>();
bg.color = new Color(0.12f, 0.15f, 0.2f, 0.95f);
// Unit Name
GameObject nameObj = new GameObject("UnitName");
nameObj.transform.SetParent(cardRoot.transform);
RectTransform nameRect = nameObj.AddComponent<RectTransform>();
nameRect.anchorMin = new Vector2(0f, 1f);
nameRect.anchorMax = new Vector2(1f, 1f);
nameRect.pivot = new Vector2(0.5f, 1f);
nameRect.anchoredPosition = new Vector2(10f, -8f);
nameRect.sizeDelta = new Vector2(-20f, 24f);
TextMeshProUGUI nameText = nameObj.AddComponent<TextMeshProUGUI>();
nameText.text = "Swordsman";
nameText.fontSize = 18;
nameText.fontStyle = FontStyles.Bold;
nameText.color = Color.white;
// Class Type
GameObject classObj = new GameObject("ClassType");
classObj.transform.SetParent(cardRoot.transform);
RectTransform classRect = classObj.AddComponent<RectTransform>();
classRect.anchorMin = new Vector2(0f, 1f);
classRect.anchorMax = new Vector2(1f, 1f);
classRect.pivot = new Vector2(0.5f, 1f);
classRect.anchoredPosition = new Vector2(10f, -32f);
classRect.sizeDelta = new Vector2(-20f, 18f);
TextMeshProUGUI classText = classObj.AddComponent<TextMeshProUGUI>();
classText.text = "Melee Infantry";
classText.fontSize = 13;
classText.color = new Color(0.6f, 0.8f, 1f);
// Stats Text
GameObject statsObj = new GameObject("StatsText");
statsObj.transform.SetParent(cardRoot.transform);
RectTransform statsRect = statsObj.AddComponent<RectTransform>();
statsRect.anchorMin = new Vector2(0f, 1f);
statsRect.anchorMax = new Vector2(1f, 1f);
statsRect.pivot = new Vector2(0.5f, 1f);
statsRect.anchoredPosition = new Vector2(10f, -52f);
statsRect.sizeDelta = new Vector2(-20f, 18f);
TextMeshProUGUI statsText = statsObj.AddComponent<TextMeshProUGUI>();
statsText.text = "HP: 120 | ATK: 18 | DEF: 3";
statsText.fontSize = 12;
statsText.color = new Color(0.85f, 0.85f, 0.85f);
// Tactics Text
GameObject tacticsObj = new GameObject("TacticsText");
tacticsObj.transform.SetParent(cardRoot.transform);
RectTransform tacticsRect = tacticsObj.AddComponent<RectTransform>();
tacticsRect.anchorMin = new Vector2(0f, 1f);
tacticsRect.anchorMax = new Vector2(1f, 1f);
tacticsRect.pivot = new Vector2(0.5f, 1f);
tacticsRect.anchoredPosition = new Vector2(10f, -72f);
tacticsRect.sizeDelta = new Vector2(-20f, 18f);
TextMeshProUGUI tacticsText = tacticsObj.AddComponent<TextMeshProUGUI>();
tacticsText.text = "Tactics: [Lv.2: Flanking] [Lv.3: Feint]";
tacticsText.fontSize = 11;
tacticsText.color = new Color(1f, 0.85f, 0.3f);
// Buy Button
GameObject btnObj = new GameObject("BuyButton");
btnObj.transform.SetParent(cardRoot.transform);
RectTransform btnRect = btnObj.AddComponent<RectTransform>();
btnRect.anchorMin = new Vector2(0f, 0f);
btnRect.anchorMax = new Vector2(1f, 0f);
btnRect.pivot = new Vector2(0.5f, 0f);
btnRect.anchoredPosition = new Vector2(0f, 8f);
btnRect.sizeDelta = new Vector2(-20f, 28f);
Image btnImg = btnObj.AddComponent<Image>();
btnImg.color = new Color(0.2f, 0.6f, 0.25f);
Button btn = btnObj.AddComponent<Button>();
GameObject btnTextObj = new GameObject("BtnText");
btnTextObj.transform.SetParent(btnObj.transform);
RectTransform btnTextRect = btnTextObj.AddComponent<RectTransform>();
btnTextRect.anchorMin = Vector2.zero;
btnTextRect.anchorMax = Vector2.one;
btnTextRect.sizeDelta = Vector2.zero;
TextMeshProUGUI btnText = btnTextObj.AddComponent<TextMeshProUGUI>();
btnText.text = "Buy (50 Gold)";
btnText.fontSize = 14;
btnText.fontStyle = FontStyles.Bold;
btnText.alignment = TextAlignmentOptions.Center;
btnText.color = Color.white;
// Setup ShopCardUI
ShopCardUI cardUI = cardRoot.AddComponent<ShopCardUI>();
SerializedObject so = new SerializedObject(cardUI);
so.FindProperty("unitNameText").objectReferenceValue = nameText;
so.FindProperty("classTypeText").objectReferenceValue = classText;
so.FindProperty("costText").objectReferenceValue = btnText;
so.FindProperty("statsSummaryText").objectReferenceValue = statsText;
so.FindProperty("tacticsInfoText").objectReferenceValue = tacticsText;
so.FindProperty("buyButton").objectReferenceValue = btn;
so.ApplyModifiedProperties();
GameObject prefab = PrefabUtility.SaveAsPrefabAsset(cardRoot, prefabFilePath);
Object.DestroyImmediate(cardRoot);
return prefab;
}
private static List<UnitDataSO> CreatePlayerUnitData(GameObject unitPrefab)
{
List<UnitDataSO> list = new List<UnitDataSO>();
// 1. Swordsman
UnitDataSO swordsman = ScriptableObject.CreateInstance<UnitDataSO>();
swordsman.unitName = "Swordsman";
swordsman.unitClass = UnitClassType.MeleeInfantry;
swordsman.description = "Frontline fighter that unlocks flanking and feint retreat.";
swordsman.goldCost = 50;
swordsman.unitPrefab = unitPrefab;
swordsman.maxHealth = 120f;
swordsman.attackDamage = 18f;
swordsman.attackRange = 1.8f;
swordsman.attackInterval = 1.2f;
swordsman.moveSpeed = 3.6f;
swordsman.defenseArmor = 3f;
swordsman.criticalChance = 0.12f;
swordsman.healthGrowth = 25f;
swordsman.damageGrowth = 4f;
swordsman.defenseGrowth = 1.2f;
swordsman.baseExpRequirement = 40;
swordsman.unlockedTactics = new List<UnlockedTactic>
{
new UnlockedTactic
{
tacticType = UnitTacticType.Flanking,
requiredLevel = 2,
description = "Maneuvers around enemy flanks or rear to deal +50% bonus flank damage.",
valueMultiplier = 1.5f,
cooldown = 4f
},
new UnlockedTactic
{
tacticType = UnitTacticType.FeintRetreat,
requiredLevel = 3,
description = "When HP drops below 35%, feigns retreat toward friendly lines to disrupt enemies, then rallies!",
triggerThreshold = 0.35f,
duration = 3.0f,
cooldown = 20f
}
};
SaveOrUpdateSO(swordsman, $"{DataPath}/SO_Swordsman.asset");
list.Add(swordsman);
// 2. Archer
UnitDataSO archer = ScriptableObject.CreateInstance<UnitDataSO>();
archer.unitName = "Archer";
archer.unitClass = UnitClassType.Archer;
archer.description = "Ranged specialist providing steady high damage from afar.";
archer.goldCost = 65;
archer.unitPrefab = unitPrefab;
archer.maxHealth = 80f;
archer.attackDamage = 24f;
archer.attackRange = 8.5f;
archer.attackInterval = 1.5f;
archer.moveSpeed = 3.2f;
archer.defenseArmor = 1f;
archer.criticalChance = 0.18f;
archer.healthGrowth = 16f;
archer.damageGrowth = 5.5f;
archer.defenseGrowth = 0.6f;
archer.baseExpRequirement = 45;
archer.unlockedTactics = new List<UnlockedTactic>
{
new UnlockedTactic
{
tacticType = UnitTacticType.Flanking,
requiredLevel = 2,
description = "Reposition to open angles to fire at vulnerable targets.",
valueMultiplier = 1.4f,
cooldown = 5f
}
};
SaveOrUpdateSO(archer, $"{DataPath}/SO_Archer.asset");
list.Add(archer);
// 3. Knight / Cavalry
UnitDataSO knight = ScriptableObject.CreateInstance<UnitDataSO>();
knight.unitName = "Knight";
knight.unitClass = UnitClassType.Cavalry;
knight.description = "Fast, durable shock warrior suited for aggressive flanking charges.";
knight.goldCost = 90;
knight.unitPrefab = unitPrefab;
knight.maxHealth = 180f;
knight.attackDamage = 22f;
knight.attackRange = 2.0f;
knight.attackInterval = 1.3f;
knight.moveSpeed = 4.8f;
knight.defenseArmor = 6f;
knight.criticalChance = 0.15f;
knight.healthGrowth = 35f;
knight.damageGrowth = 4.5f;
knight.defenseGrowth = 2f;
knight.baseExpRequirement = 55;
knight.unlockedTactics = new List<UnlockedTactic>
{
new UnlockedTactic
{
tacticType = UnitTacticType.Flanking,
requiredLevel = 2,
description = "Cavalry flank charge dealing +75% massive flank damage!",
valueMultiplier = 1.75f,
cooldown = 4f
},
new UnlockedTactic
{
tacticType = UnitTacticType.FeintRetreat,
requiredLevel = 3,
description = "Feigns a tactical retreat pulling enemies out of formation before charging back.",
triggerThreshold = 0.30f,
duration = 3.5f,
cooldown = 15f
}
};
SaveOrUpdateSO(knight, $"{DataPath}/SO_Knight.asset");
list.Add(knight);
return list;
}
private static List<UnitDataSO> CreateEnemyUnitData(GameObject unitPrefab)
{
List<UnitDataSO> list = new List<UnitDataSO>();
// Bandit Warrior
UnitDataSO bandit = ScriptableObject.CreateInstance<UnitDataSO>();
bandit.unitName = "Bandit Raider";
bandit.unitClass = UnitClassType.MeleeInfantry;
bandit.goldCost = 40;
bandit.unitPrefab = unitPrefab;
bandit.maxHealth = 95f;
bandit.attackDamage = 14f;
bandit.attackRange = 1.6f;
bandit.attackInterval = 1.25f;
bandit.moveSpeed = 3.4f;
bandit.defenseArmor = 2f;
bandit.healthGrowth = 20f;
bandit.damageGrowth = 3f;
bandit.baseExpRequirement = 35;
SaveOrUpdateSO(bandit, $"{DataPath}/SO_Bandit_Raider.asset");
list.Add(bandit);
// Bandit Archer
UnitDataSO banditArcher = ScriptableObject.CreateInstance<UnitDataSO>();
banditArcher.unitName = "Bandit Archer";
banditArcher.unitClass = UnitClassType.Archer;
banditArcher.goldCost = 50;
banditArcher.unitPrefab = unitPrefab;
banditArcher.maxHealth = 70f;
banditArcher.attackDamage = 18f;
banditArcher.attackRange = 7.5f;
banditArcher.attackInterval = 1.6f;
banditArcher.moveSpeed = 3.0f;
banditArcher.defenseArmor = 1f;
banditArcher.healthGrowth = 14f;
banditArcher.damageGrowth = 4f;
banditArcher.baseExpRequirement = 35;
SaveOrUpdateSO(banditArcher, $"{DataPath}/SO_Bandit_Archer.asset");
list.Add(banditArcher);
return list;
}
private static void SaveOrUpdateSO(ScriptableObject so, string path)
{
UnitDataSO existing = AssetDatabase.LoadAssetAtPath<UnitDataSO>(path);
if (existing != null)
{
EditorUtility.CopySerialized(so, existing);
}
else
{
AssetDatabase.CreateAsset(so, path);
}
}
private static void SetupSceneHierarchy(
List<UnitDataSO> playerUnits,
List<UnitDataSO> enemyUnits,
GameObject cardPrefab,
GameObject unitPrefab)
{
// 1. Create Ground / Arena if not present
GameObject ground = GameObject.Find("ArenaGround");
if (ground == null)
{
ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
ground.name = "ArenaGround";
ground.transform.position = Vector3.zero;
ground.transform.localScale = new Vector3(5f, 1f, 5f);
}
// 2. Deployment Zones
GameObject playerZoneObj = GameObject.Find("PlayerDeploymentZone");
if (playerZoneObj == null)
{
playerZoneObj = new GameObject("PlayerDeploymentZone");
playerZoneObj.transform.position = new Vector3(0, 0, -8f);
}
DeploymentZone playerZone = playerZoneObj.GetComponent<DeploymentZone>();
if (playerZone == null) playerZone = playerZoneObj.AddComponent<DeploymentZone>();
GameObject enemyZoneObj = GameObject.Find("EnemyDeploymentZone");
if (enemyZoneObj == null)
{
enemyZoneObj = new GameObject("EnemyDeploymentZone");
enemyZoneObj.transform.position = new Vector3(0, 0, 8f);
}
DeploymentZone enemyZone = enemyZoneObj.GetComponent<DeploymentZone>();
if (enemyZone == null) enemyZone = enemyZoneObj.AddComponent<DeploymentZone>();
// 3. Battle Manager & Game Manager
GameObject mgrObj = GameObject.Find("GameManagers");
if (mgrObj == null)
{
mgrObj = new GameObject("GameManagers");
}
GameManager gm = mgrObj.GetComponent<GameManager>();
if (gm == null) gm = mgrObj.AddComponent<GameManager>();
BattleManager bm = mgrObj.GetComponent<BattleManager>();
if (bm == null) bm = mgrObj.AddComponent<BattleManager>();
SerializedObject bmSo = new SerializedObject(bm);
bmSo.FindProperty("playerDeploymentZone").objectReferenceValue = playerZone;
bmSo.FindProperty("enemyDeploymentZone").objectReferenceValue = enemyZone;
bmSo.FindProperty("defaultUnitPrefab").objectReferenceValue = unitPrefab;
bmSo.FindProperty("startingGold").intValue = 150;
// Configure predefined waves
SerializedProperty wavesProp = bmSo.FindProperty("predefinedWaves");
wavesProp.ClearArray();
// Wave 1: 2 Bandit Raiders
wavesProp.InsertArrayElementAtIndex(0);
SerializedProperty w1 = wavesProp.GetArrayElementAtIndex(0);
w1.FindPropertyRelative("waveName").stringValue = "Wave 1 - Scouts";
w1.FindPropertyRelative("rewardGold").intValue = 60;
w1.FindPropertyRelative("waveBonusExp").intValue = 30;
SerializedProperty w1Enemies = w1.FindPropertyRelative("enemies");
w1Enemies.InsertArrayElementAtIndex(0);
w1Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("unitData").objectReferenceValue = enemyUnits[0];
w1Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("count").intValue = 2;
w1Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("level").intValue = 1;
// Wave 2: 3 Raiders + 1 Archer
wavesProp.InsertArrayElementAtIndex(1);
SerializedProperty w2 = wavesProp.GetArrayElementAtIndex(1);
w2.FindPropertyRelative("waveName").stringValue = "Wave 2 - Raiding Party";
w2.FindPropertyRelative("rewardGold").intValue = 90;
w2.FindPropertyRelative("waveBonusExp").intValue = 45;
SerializedProperty w2Enemies = w2.FindPropertyRelative("enemies");
w2Enemies.InsertArrayElementAtIndex(0);
w2Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("unitData").objectReferenceValue = enemyUnits[0];
w2Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("count").intValue = 3;
w2Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("level").intValue = 1;
w2Enemies.InsertArrayElementAtIndex(1);
w2Enemies.GetArrayElementAtIndex(1).FindPropertyRelative("unitData").objectReferenceValue = enemyUnits[1];
w2Enemies.GetArrayElementAtIndex(1).FindPropertyRelative("count").intValue = 1;
w2Enemies.GetArrayElementAtIndex(1).FindPropertyRelative("level").intValue = 2;
// Wave 3: 4 Raiders + 2 Archers (Lv 2-3)
wavesProp.InsertArrayElementAtIndex(2);
SerializedProperty w3 = wavesProp.GetArrayElementAtIndex(2);
w3.FindPropertyRelative("waveName").stringValue = "Wave 3 - Veteran Warband";
w3.FindPropertyRelative("rewardGold").intValue = 130;
w3.FindPropertyRelative("waveBonusExp").intValue = 60;
SerializedProperty w3Enemies = w3.FindPropertyRelative("enemies");
w3Enemies.InsertArrayElementAtIndex(0);
w3Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("unitData").objectReferenceValue = enemyUnits[0];
w3Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("count").intValue = 4;
w3Enemies.GetArrayElementAtIndex(0).FindPropertyRelative("level").intValue = 2;
w3Enemies.InsertArrayElementAtIndex(1);
w3Enemies.GetArrayElementAtIndex(1).FindPropertyRelative("unitData").objectReferenceValue = enemyUnits[1];
w3Enemies.GetArrayElementAtIndex(1).FindPropertyRelative("count").intValue = 2;
w3Enemies.GetArrayElementAtIndex(1).FindPropertyRelative("level").intValue = 3;
bmSo.ApplyModifiedProperties();
// 4. Setup Canvas and UI
CreateOrUpdateCanvasUI(playerUnits, cardPrefab);
// 5. Position Camera
Camera cam = Camera.main;
if (cam != null)
{
cam.transform.position = new Vector3(0, 16f, -16f);
cam.transform.rotation = Quaternion.Euler(48f, 0, 0);
}
}
private static void CreateOrUpdateCanvasUI(List<UnitDataSO> playerUnits, GameObject cardPrefab)
{
GameObject canvasObj = GameObject.Find("BattleCanvas");
if (canvasObj == null)
{
canvasObj = new GameObject("BattleCanvas");
Canvas canvas = canvasObj.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
CanvasScaler scaler = canvasObj.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920, 1080);
canvasObj.AddComponent<GraphicRaycaster>();
}
// Ensure EventSystem
if (Object.FindFirstObjectByType<UnityEngine.EventSystems.EventSystem>() == null)
{
GameObject esObj = new GameObject("EventSystem");
esObj.AddComponent<UnityEngine.EventSystems.EventSystem>();
esObj.AddComponent<UnityEngine.EventSystems.StandaloneInputModule>();
}
// TOP HUD PANEL
GameObject hudPanel = new GameObject("TopHUD");
hudPanel.transform.SetParent(canvasObj.transform, false);
RectTransform hudRect = hudPanel.AddComponent<RectTransform>();
hudRect.anchorMin = new Vector2(0f, 1f);
hudRect.anchorMax = new Vector2(1f, 1f);
hudRect.pivot = new Vector2(0.5f, 1f);
hudRect.sizeDelta = new Vector2(0, 90f);
Image hudBg = hudPanel.AddComponent<Image>();
hudBg.color = new Color(0.08f, 0.1f, 0.14f, 0.85f);
// Ongoing Unit Count: Player Alive
GameObject pAliveObj = new GameObject("PlayerAliveText");
pAliveObj.transform.SetParent(hudPanel.transform, false);
RectTransform pAliveRect = pAliveObj.AddComponent<RectTransform>();
pAliveRect.anchorMin = new Vector2(0f, 0.5f);
pAliveRect.anchorMax = new Vector2(0f, 0.5f);
pAliveRect.anchoredPosition = new Vector2(180f, 0);
pAliveRect.sizeDelta = new Vector2(300f, 40f);
TextMeshProUGUI pAliveText = pAliveObj.AddComponent<TextMeshProUGUI>();
pAliveText.text = "Player Army: 0 / 0";
pAliveText.fontSize = 22;
pAliveText.fontStyle = FontStyles.Bold;
pAliveText.color = new Color(0.3f, 0.8f, 1f);
// Ongoing Unit Count: Enemy Alive
GameObject eAliveObj = new GameObject("EnemyAliveText");
eAliveObj.transform.SetParent(hudPanel.transform, false);
RectTransform eAliveRect = eAliveObj.AddComponent<RectTransform>();
eAliveRect.anchorMin = new Vector2(1f, 0.5f);
eAliveMaxAnchor(eAliveRect);
eAliveRect.anchoredPosition = new Vector2(-180f, 0);
eAliveRect.sizeDelta = new Vector2(300f, 40f);
TextMeshProUGUI eAliveText = eAliveObj.AddComponent<TextMeshProUGUI>();
eAliveText.text = "Enemy Force: 0 / 0";
eAliveText.fontSize = 22;
eAliveText.fontStyle = FontStyles.Bold;
eAliveText.alignment = TextAlignmentOptions.Right;
eAliveText.color = new Color(1f, 0.35f, 0.35f);
// Round & Phase Text (Center)
GameObject waveObj = new GameObject("WaveText");
waveObj.transform.SetParent(hudPanel.transform, false);
RectTransform waveRect = waveObj.AddComponent<RectTransform>();
waveRect.anchorMin = new Vector2(0.5f, 0.5f);
waveRect.anchorMax = new Vector2(0.5f, 0.5f);
waveRect.anchoredPosition = new Vector2(0, 15f);
waveRect.sizeDelta = new Vector2(400f, 30f);
TextMeshProUGUI waveText = waveObj.AddComponent<TextMeshProUGUI>();
waveText.text = "Battle Round: 1";
waveText.fontSize = 24;
waveText.fontStyle = FontStyles.Bold;
waveText.alignment = TextAlignmentOptions.Center;
waveText.color = Color.white;
GameObject statusObj = new GameObject("PhaseStatusText");
statusObj.transform.SetParent(hudPanel.transform, false);
RectTransform statusRect = statusObj.AddComponent<RectTransform>();
statusRect.anchorMin = new Vector2(0.5f, 0.5f);
statusRect.anchorMax = new Vector2(0.5f, 0.5f);
statusRect.anchoredPosition = new Vector2(0, -18f);
statusRect.sizeDelta = new Vector2(500f, 25f);
TextMeshProUGUI statusText = statusObj.AddComponent<TextMeshProUGUI>();
statusText.text = "PHASE: RECRUITMENT & PREPARATION";
statusText.fontSize = 16;
statusText.alignment = TextAlignmentOptions.Center;
statusText.color = new Color(0.3f, 0.8f, 1f);
// Game Speed Controls (HUD Left)
GameObject speed1x = CreateButton(hudPanel.transform, "Speed1x", "1x", new Vector2(380f, 0), new Vector2(50f, 35f));
GameObject speed2x = CreateButton(hudPanel.transform, "Speed2x", "2x", new Vector2(440f, 0), new Vector2(50f, 35f));
GameObject speed4x = CreateButton(hudPanel.transform, "Speed4x", "4x", new Vector2(500f, 0), new Vector2(50f, 35f));
// Start Battle Button (HUD Right-ish or Bottom)
GameObject startBtnObj = CreateButton(canvasObj.transform, "StartBattleBtn", "START BATTLE", new Vector2(0, 190f), new Vector2(260f, 55f));
RectTransform startBtnRect = startBtnObj.GetComponent<RectTransform>();
startBtnRect.anchorMin = new Vector2(0.5f, 0f);
startBtnRect.anchorMax = new Vector2(0.5f, 0f);
startBtnObj.GetComponent<Image>().color = new Color(0.85f, 0.4f, 0.1f);
startBtnObj.GetComponentInChildren<TextMeshProUGUI>().fontSize = 22;
// Wire BattleHUD
BattleHUD battleHUD = canvasObj.AddComponent<BattleHUD>();
SerializedObject hudSo = new SerializedObject(battleHUD);
hudSo.FindProperty("playerUnitsCountText").objectReferenceValue = pAliveText;
hudSo.FindProperty("enemyUnitsCountText").objectReferenceValue = eAliveText;
hudSo.FindProperty("waveTitleText").objectReferenceValue = waveText;
hudSo.FindProperty("battleStatusText").objectReferenceValue = statusText;
hudSo.FindProperty("startBattleButton").objectReferenceValue = startBtnObj.GetComponent<Button>();
hudSo.FindProperty("speed1xButton").objectReferenceValue = speed1x.GetComponent<Button>();
hudSo.FindProperty("speed2xButton").objectReferenceValue = speed2x.GetComponent<Button>();
hudSo.FindProperty("speed4xButton").objectReferenceValue = speed4x.GetComponent<Button>();
hudSo.ApplyModifiedProperties();
// RECRUITMENT SHOP PANEL (BOTTOM)
GameObject shopPanel = new GameObject("ShopPanel");
shopPanel.transform.SetParent(canvasObj.transform, false);
RectTransform shopRect = shopPanel.AddComponent<RectTransform>();
shopRect.anchorMin = new Vector2(0f, 0f);
shopRect.anchorMax = new Vector2(1f, 0f);
shopRect.pivot = new Vector2(0.5f, 0f);
shopRect.sizeDelta = new Vector2(0, 160f);
Image shopBg = shopPanel.AddComponent<Image>();
shopBg.color = new Color(0.06f, 0.08f, 0.12f, 0.92f);
// Gold Display
GameObject goldObj = new GameObject("GoldText");
goldObj.transform.SetParent(shopPanel.transform, false);
RectTransform goldRect = goldObj.AddComponent<RectTransform>();
goldRect.anchorMin = new Vector2(0f, 0.5f);
goldRect.anchorMax = new Vector2(0f, 0.5f);
goldRect.anchoredPosition = new Vector2(120f, 0);
goldRect.sizeDelta = new Vector2(200f, 40f);
TextMeshProUGUI goldText = goldObj.AddComponent<TextMeshProUGUI>();
goldText.text = "Gold: 150";
goldText.fontSize = 24;
goldText.fontStyle = FontStyles.Bold;
goldText.color = new Color(1f, 0.85f, 0.2f);
// Shop Cards Container
GameObject cardsContainer = new GameObject("CardsContainer");
cardsContainer.transform.SetParent(shopPanel.transform, false);
RectTransform containerRect = cardsContainer.AddComponent<RectTransform>();
containerRect.anchorMin = new Vector2(0.2f, 0f);
containerRect.anchorMax = new Vector2(0.95f, 1f);
containerRect.sizeDelta = Vector2.zero;
HorizontalLayoutGroup hlg = cardsContainer.AddComponent<HorizontalLayoutGroup>();
hlg.spacing = 15f;
hlg.childAlignment = TextAnchor.MiddleCenter;
hlg.childControlWidth = false;
hlg.childControlHeight = false;
ShopUI shopUI = shopPanel.AddComponent<ShopUI>();
SerializedObject shopSo = new SerializedObject(shopUI);
shopSo.FindProperty("goldDisplayText").objectReferenceValue = goldText;
shopSo.FindProperty("cardsContainer").objectReferenceValue = cardsContainer.transform;
shopSo.FindProperty("cardPrefab").objectReferenceValue = cardPrefab;
shopSo.FindProperty("shopPanel").objectReferenceValue = shopPanel;
SerializedProperty catalogProp = shopSo.FindProperty("purchasableUnits");
catalogProp.ClearArray();
for (int i = 0; i < playerUnits.Count; i++)
{
catalogProp.InsertArrayElementAtIndex(i);
catalogProp.GetArrayElementAtIndex(i).objectReferenceValue = playerUnits[i];
}
shopSo.ApplyModifiedProperties();
// POST-BATTLE RESULTS MODAL
GameObject resultsModal = new GameObject("ResultsModal");
resultsModal.transform.SetParent(canvasObj.transform, false);
RectTransform modalRect = resultsModal.AddComponent<RectTransform>();
modalRect.anchorMin = new Vector2(0.5f, 0.5f);
modalRect.anchorMax = new Vector2(0.5f, 0.5f);
modalRect.sizeDelta = new Vector2(500f, 360f);
Image modalBg = resultsModal.AddComponent<Image>();
modalBg.color = new Color(0.08f, 0.1f, 0.15f, 0.97f);
// Victory Title
GameObject resTitle = new GameObject("ResultTitle");
resTitle.transform.SetParent(resultsModal.transform, false);
RectTransform resTitleRect = resTitle.AddComponent<RectTransform>();
resTitleRect.anchoredPosition = new Vector2(0, 130f);
resTitleRect.sizeDelta = new Vector2(460f, 50f);
TextMeshProUGUI resTitleText = resTitle.AddComponent<TextMeshProUGUI>();
resTitleText.text = "VICTORY!";
resTitleText.fontSize = 32;
resTitleText.fontStyle = FontStyles.Bold;
resTitleText.alignment = TextAlignmentOptions.Center;
resTitleText.color = new Color(0.2f, 1f, 0.4f);
// Gold Reward
GameObject goldRew = new GameObject("GoldReward");
goldRew.transform.SetParent(resultsModal.transform, false);
RectTransform goldRewRect = goldRew.AddComponent<RectTransform>();
goldRewRect.anchoredPosition = new Vector2(0, 80f);
goldRewRect.sizeDelta = new Vector2(460f, 30f);
TextMeshProUGUI goldRewText = goldRew.AddComponent<TextMeshProUGUI>();
goldRewText.text = "Gold Earned: +95";
goldRewText.fontSize = 20;
goldRewText.alignment = TextAlignmentOptions.Center;
goldRewText.color = new Color(1f, 0.85f, 0.2f);
// Casualties Text
GameObject casObj = new GameObject("CasualtiesText");
casObj.transform.SetParent(resultsModal.transform, false);
RectTransform casRect = casObj.AddComponent<RectTransform>();
casRect.anchoredPosition = new Vector2(0, 35f);
casRect.sizeDelta = new Vector2(460f, 40f);
TextMeshProUGUI casText = casObj.AddComponent<TextMeshProUGUI>();
casText.text = "Surviving Units: 3 / 3\nEnemies Defeated: 2";
casText.fontSize = 17;
casText.alignment = TextAlignmentOptions.Center;
casText.color = Color.white;
// Level Up Report Text
GameObject lvlObj = new GameObject("LevelReportText");
lvlObj.transform.SetParent(resultsModal.transform, false);
RectTransform lvlRect = lvlObj.AddComponent<RectTransform>();
lvlRect.anchoredPosition = new Vector2(0, -35f);
lvlRect.sizeDelta = new Vector2(440f, 75f);
TextMeshProUGUI lvlText = lvlObj.AddComponent<TextMeshProUGUI>();
lvlText.text = "• Swordsman reached Level 2! Unlocked [Flanking]!";
lvlText.fontSize = 15;
lvlText.alignment = TextAlignmentOptions.Center;
lvlText.color = new Color(0.4f, 0.9f, 1f);
// Continue Button
GameObject contBtn = CreateButton(resultsModal.transform, "ContinueBtn", "CONTINUE", new Vector2(0, -135f), new Vector2(220f, 45f));
contBtn.GetComponent<Image>().color = new Color(0.2f, 0.65f, 0.35f);
contBtn.GetComponentInChildren<TextMeshProUGUI>().fontSize = 20;
BattleResultUI resultUI = resultsModal.AddComponent<BattleResultUI>();
SerializedObject resSo = new SerializedObject(resultUI);
resSo.FindProperty("resultPanel").objectReferenceValue = resultsModal;
resSo.FindProperty("resultTitleText").objectReferenceValue = resTitleText;
resSo.FindProperty("goldRewardText").objectReferenceValue = goldRewText;
resSo.FindProperty("casualtiesText").objectReferenceValue = casText;
resSo.FindProperty("levelUpReportText").objectReferenceValue = lvlText;
resSo.FindProperty("continueButton").objectReferenceValue = contBtn.GetComponent<Button>();
resSo.ApplyModifiedProperties();
resultsModal.SetActive(false);
}
private static void eAliveMaxAnchor(RectTransform rect)
{
rect.anchorMax = new Vector2(1f, 0.5f);
}
private static GameObject CreateButton(Transform parent, string name, string label, Vector2 pos, Vector2 size)
{
GameObject btnObj = new GameObject(name);
btnObj.transform.SetParent(parent, false);
RectTransform rect = btnObj.AddComponent<RectTransform>();
rect.anchoredPosition = pos;
rect.sizeDelta = size;
Image img = btnObj.AddComponent<Image>();
img.color = new Color(0.25f, 0.3f, 0.4f);
btnObj.AddComponent<Button>();
GameObject textObj = new GameObject("Text");
textObj.transform.SetParent(btnObj.transform, false);
RectTransform tRect = textObj.AddComponent<RectTransform>();
tRect.anchorMin = Vector2.zero;
tRect.anchorMax = Vector2.one;
tRect.sizeDelta = Vector2.zero;
TextMeshProUGUI tmp = textObj.AddComponent<TextMeshProUGUI>();
tmp.text = label;
tmp.fontSize = 15;
tmp.fontStyle = FontStyles.Bold;
tmp.alignment = TextAlignmentOptions.Center;
tmp.color = Color.white;
return btnObj;
}
}
}