imtial commit
This commit is contained in:
440
Assets/Scripts/Managers/BattleManager.cs
Normal file
440
Assets/Scripts/Managers/BattleManager.cs
Normal file
@@ -0,0 +1,440 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using WarGame.Data;
|
||||
using WarGame.Deployment;
|
||||
using WarGame.Units;
|
||||
|
||||
namespace WarGame.Managers
|
||||
{
|
||||
public enum BattleState
|
||||
{
|
||||
Preparation,
|
||||
BattleInProgress,
|
||||
Victory,
|
||||
Defeat
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class EnemyWaveConfig
|
||||
{
|
||||
public string waveName = "Wave 1";
|
||||
public List<EnemySpawnInfo> enemies = new List<EnemySpawnInfo>();
|
||||
public int rewardGold = 100;
|
||||
public int waveBonusExp = 40;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class EnemySpawnInfo
|
||||
{
|
||||
public UnitDataSO unitData;
|
||||
public int level = 1;
|
||||
public int count = 1;
|
||||
}
|
||||
|
||||
public class BattleManager : MonoBehaviour
|
||||
{
|
||||
public static BattleManager Instance { get; private set; }
|
||||
|
||||
[Header("Deployment Zones")]
|
||||
[SerializeField] private DeploymentZone playerDeploymentZone;
|
||||
[SerializeField] private DeploymentZone enemyDeploymentZone;
|
||||
|
||||
[Header("Default Unit Prefab (Fallback if UnitData lacks prefab)")]
|
||||
[SerializeField] private GameObject defaultUnitPrefab;
|
||||
|
||||
[Header("Economy & Progression")]
|
||||
[SerializeField] private int startingGold = 150;
|
||||
[SerializeField] private int baseWaveClearGold = 75;
|
||||
[SerializeField] private int killBountyGold = 10;
|
||||
[SerializeField] private int battleParticipationExp = 30;
|
||||
|
||||
[Header("Waves Setup")]
|
||||
[SerializeField] private List<EnemyWaveConfig> predefinedWaves = new List<EnemyWaveConfig>();
|
||||
[SerializeField] private List<UnitDataSO> defaultEnemyUnitTypes = new List<UnitDataSO>();
|
||||
|
||||
// Current state
|
||||
public BattleState CurrentBattleState { get; private set; } = BattleState.Preparation;
|
||||
public int CurrentGold { get; private set; }
|
||||
public int CurrentWave { get; private set; } = 1;
|
||||
|
||||
// Ongoing Live Unit Tracking
|
||||
private readonly List<Unit> allActiveUnits = new List<Unit>();
|
||||
private readonly List<Unit> playerArmyUnits = new List<Unit>();
|
||||
private readonly List<Unit> enemyArmyUnits = new List<Unit>();
|
||||
|
||||
public List<Unit> AllActiveUnits => allActiveUnits;
|
||||
public List<Unit> PlayerArmyUnits => playerArmyUnits;
|
||||
public List<Unit> EnemyArmyUnits => enemyArmyUnits;
|
||||
|
||||
public int PlayerAliveCount { get; private set; }
|
||||
public int EnemyAliveCount { get; private set; }
|
||||
public int PlayerTotalCount => playerArmyUnits.Count;
|
||||
public int EnemyTotalCount => enemyArmyUnits.Count;
|
||||
|
||||
// Events
|
||||
public event Action<BattleState> OnBattleStateChanged;
|
||||
public event Action<int, int> OnUnitCountsChanged; // playerAlive, enemyAlive
|
||||
public event Action<int> OnGoldChanged;
|
||||
public event Action<int> OnWaveChanged;
|
||||
public event Action<BattleResultData> OnBattleCompleted;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
CurrentGold = startingGold;
|
||||
OnGoldChanged?.Invoke(CurrentGold);
|
||||
OnWaveChanged?.Invoke(CurrentWave);
|
||||
SetBattleState(BattleState.Preparation);
|
||||
}
|
||||
|
||||
public void SetBattleState(BattleState newState)
|
||||
{
|
||||
CurrentBattleState = newState;
|
||||
OnBattleStateChanged?.Invoke(newState);
|
||||
}
|
||||
|
||||
#region Economy & Unit Buying
|
||||
|
||||
public bool CanAffordUnit(UnitDataSO unitData)
|
||||
{
|
||||
return unitData != null && CurrentGold >= unitData.goldCost;
|
||||
}
|
||||
|
||||
public Unit BuyAndDeployUnit(UnitDataSO unitData)
|
||||
{
|
||||
if (!CanAffordUnit(unitData))
|
||||
{
|
||||
Debug.LogWarning("Not enough gold to buy " + unitData.unitName);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (CurrentBattleState != BattleState.Preparation)
|
||||
{
|
||||
Debug.LogWarning("Cannot buy units while battle is in progress!");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Deduct gold
|
||||
CurrentGold -= unitData.goldCost;
|
||||
OnGoldChanged?.Invoke(CurrentGold);
|
||||
|
||||
// Spawn unit in player zone
|
||||
Vector3 spawnPos = playerDeploymentZone != null ? playerDeploymentZone.GetNextSpawnPosition() : transform.position;
|
||||
Quaternion spawnRot = Quaternion.identity;
|
||||
|
||||
GameObject prefabToSpawn = unitData.unitPrefab != null ? unitData.unitPrefab : defaultUnitPrefab;
|
||||
if (prefabToSpawn == null)
|
||||
{
|
||||
Debug.LogError("No prefab found for unit: " + unitData.unitName);
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject instance = Instantiate(prefabToSpawn, spawnPos, spawnRot);
|
||||
Unit unit = instance.GetComponent<Unit>();
|
||||
if (unit == null) unit = instance.AddComponent<Unit>();
|
||||
|
||||
unit.Initialize(unitData, UnitTeam.Player, 1);
|
||||
|
||||
playerArmyUnits.Add(unit);
|
||||
allActiveUnits.Add(unit);
|
||||
|
||||
UpdateUnitCounts();
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void AddGold(int amount)
|
||||
{
|
||||
CurrentGold += amount;
|
||||
OnGoldChanged?.Invoke(CurrentGold);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Battle Flow
|
||||
|
||||
public void StartBattle()
|
||||
{
|
||||
if (CurrentBattleState != BattleState.Preparation) return;
|
||||
if (playerArmyUnits.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("Recruit at least 1 unit before starting battle!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up any remaining previous enemies
|
||||
ClearEnemies();
|
||||
|
||||
// Spawn Enemy Wave
|
||||
SpawnEnemyWave(CurrentWave);
|
||||
|
||||
// Reset player positions and heal to full
|
||||
if (playerDeploymentZone != null) playerDeploymentZone.ResetSlots();
|
||||
for (int i = 0; i < playerArmyUnits.Count; i++)
|
||||
{
|
||||
if (playerArmyUnits[i] != null)
|
||||
{
|
||||
playerArmyUnits[i].gameObject.SetActive(true);
|
||||
Vector3 pos = playerDeploymentZone != null ? playerDeploymentZone.GetNextSpawnPosition() : playerArmyUnits[i].transform.position;
|
||||
playerArmyUnits[i].WarpToPosition(pos, Quaternion.LookRotation(Vector3.forward));
|
||||
playerArmyUnits[i].HealToFull();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateUnitCounts();
|
||||
SetBattleState(BattleState.BattleInProgress);
|
||||
|
||||
// Start battle for all units
|
||||
for (int i = 0; i < allActiveUnits.Count; i++)
|
||||
{
|
||||
if (allActiveUnits[i] != null && allActiveUnits[i].gameObject.activeSelf)
|
||||
{
|
||||
allActiveUnits[i].StartBattle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnEnemyWave(int waveNumber)
|
||||
{
|
||||
enemyArmyUnits.Clear();
|
||||
if (enemyDeploymentZone != null) enemyDeploymentZone.ResetSlots();
|
||||
|
||||
// Check if there is a predefined wave configuration
|
||||
EnemyWaveConfig waveConfig = null;
|
||||
if (waveNumber - 1 < predefinedWaves.Count)
|
||||
{
|
||||
waveConfig = predefinedWaves[waveNumber - 1];
|
||||
}
|
||||
|
||||
if (waveConfig != null && waveConfig.enemies.Count > 0)
|
||||
{
|
||||
foreach (EnemySpawnInfo spawnInfo in waveConfig.enemies)
|
||||
{
|
||||
for (int i = 0; i < spawnInfo.count; i++)
|
||||
{
|
||||
SpawnSingleEnemy(spawnInfo.unitData, spawnInfo.level);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Procedural wave scaling if beyond predefined waves
|
||||
int enemyCount = 2 + waveNumber;
|
||||
int enemyLevel = Mathf.Max(1, 1 + (waveNumber / 2));
|
||||
|
||||
for (int i = 0; i < enemyCount; i++)
|
||||
{
|
||||
UnitDataSO randomType = null;
|
||||
if (defaultEnemyUnitTypes.Count > 0)
|
||||
{
|
||||
randomType = defaultEnemyUnitTypes[UnityEngine.Random.Range(0, defaultEnemyUnitTypes.Count)];
|
||||
}
|
||||
|
||||
SpawnSingleEnemy(randomType, enemyLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnSingleEnemy(UnitDataSO unitData, int level)
|
||||
{
|
||||
Vector3 spawnPos = enemyDeploymentZone != null ? enemyDeploymentZone.GetNextSpawnPosition() : transform.position + Vector3.forward * 10f;
|
||||
Quaternion spawnRot = Quaternion.LookRotation(Vector3.back);
|
||||
|
||||
GameObject prefabToSpawn = (unitData != null && unitData.unitPrefab != null) ? unitData.unitPrefab : defaultUnitPrefab;
|
||||
if (prefabToSpawn == null) return;
|
||||
|
||||
GameObject instance = Instantiate(prefabToSpawn, spawnPos, spawnRot);
|
||||
Unit enemyUnit = instance.GetComponent<Unit>();
|
||||
if (enemyUnit == null) enemyUnit = instance.AddComponent<Unit>();
|
||||
|
||||
enemyUnit.Initialize(unitData, UnitTeam.Enemy, level);
|
||||
|
||||
enemyArmyUnits.Add(enemyUnit);
|
||||
allActiveUnits.Add(enemyUnit);
|
||||
}
|
||||
|
||||
private void ClearEnemies()
|
||||
{
|
||||
for (int i = 0; i < enemyArmyUnits.Count; i++)
|
||||
{
|
||||
if (enemyArmyUnits[i] != null)
|
||||
{
|
||||
allActiveUnits.Remove(enemyArmyUnits[i]);
|
||||
Destroy(enemyArmyUnits[i].gameObject);
|
||||
}
|
||||
}
|
||||
enemyArmyUnits.Clear();
|
||||
}
|
||||
|
||||
public void OnUnitDefeated(Unit unit)
|
||||
{
|
||||
if (unit.Team == UnitTeam.Enemy)
|
||||
{
|
||||
AddGold(killBountyGold);
|
||||
}
|
||||
|
||||
UpdateUnitCounts();
|
||||
|
||||
if (CurrentBattleState == BattleState.BattleInProgress)
|
||||
{
|
||||
CheckBattleResolution();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUnitCounts()
|
||||
{
|
||||
int pAlive = 0;
|
||||
for (int i = 0; i < playerArmyUnits.Count; i++)
|
||||
{
|
||||
if (playerArmyUnits[i] != null && playerArmyUnits[i].IsAlive()) pAlive++;
|
||||
}
|
||||
|
||||
int eAlive = 0;
|
||||
for (int i = 0; i < enemyArmyUnits.Count; i++)
|
||||
{
|
||||
if (enemyArmyUnits[i] != null && enemyArmyUnits[i].IsAlive()) eAlive++;
|
||||
}
|
||||
|
||||
PlayerAliveCount = pAlive;
|
||||
EnemyAliveCount = eAlive;
|
||||
|
||||
OnUnitCountsChanged?.Invoke(PlayerAliveCount, EnemyAliveCount);
|
||||
}
|
||||
|
||||
private void CheckBattleResolution()
|
||||
{
|
||||
if (EnemyAliveCount <= 0)
|
||||
{
|
||||
ResolveBattle(isVictory: true);
|
||||
}
|
||||
else if (PlayerAliveCount <= 0)
|
||||
{
|
||||
ResolveBattle(isVictory: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveBattle(bool isVictory)
|
||||
{
|
||||
SetBattleState(isVictory ? BattleState.Victory : BattleState.Defeat);
|
||||
|
||||
// Stop battle on surviving units
|
||||
for (int i = 0; i < allActiveUnits.Count; i++)
|
||||
{
|
||||
if (allActiveUnits[i] != null && allActiveUnits[i].IsAlive())
|
||||
{
|
||||
allActiveUnits[i].EndBattle(restorePosition: false);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate post-battle rewards
|
||||
int earnedGold = isVictory ? (baseWaveClearGold + CurrentWave * 20) : 25;
|
||||
AddGold(earnedGold);
|
||||
|
||||
List<UnitLevelUpReport> levelUps = new List<UnitLevelUpReport>();
|
||||
|
||||
// Award participation XP and battle survival to player units
|
||||
for (int i = 0; i < playerArmyUnits.Count; i++)
|
||||
{
|
||||
Unit pUnit = playerArmyUnits[i];
|
||||
if (pUnit == null || pUnit.Progression == null) continue;
|
||||
|
||||
int prevLevel = pUnit.Progression.CurrentLevel;
|
||||
int xpGain = battleParticipationExp + (pUnit.IsAlive() ? 20 : 0);
|
||||
pUnit.Progression.AddExperience(xpGain);
|
||||
|
||||
if (pUnit.IsAlive())
|
||||
{
|
||||
pUnit.Progression.RecordBattleSurvived();
|
||||
}
|
||||
|
||||
if (pUnit.Progression.CurrentLevel > prevLevel)
|
||||
{
|
||||
levelUps.Add(new UnitLevelUpReport
|
||||
{
|
||||
unitName = pUnit.UnitData != null ? pUnit.UnitData.unitName : "Unit",
|
||||
oldLevel = prevLevel,
|
||||
newLevel = pUnit.Progression.CurrentLevel
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
BattleResultData result = new BattleResultData
|
||||
{
|
||||
isVictory = isVictory,
|
||||
waveNumber = CurrentWave,
|
||||
goldEarned = earnedGold,
|
||||
survivingPlayerUnits = PlayerAliveCount,
|
||||
totalPlayerUnits = PlayerTotalCount,
|
||||
enemyCasualties = EnemyTotalCount,
|
||||
levelUpReports = levelUps
|
||||
};
|
||||
|
||||
OnBattleCompleted?.Invoke(result);
|
||||
}
|
||||
|
||||
public void ContinueToNextRound()
|
||||
{
|
||||
if (CurrentBattleState == BattleState.Victory)
|
||||
{
|
||||
CurrentWave++;
|
||||
OnWaveChanged?.Invoke(CurrentWave);
|
||||
}
|
||||
|
||||
ClearEnemies();
|
||||
|
||||
// Restore player army to deployment positions and heal
|
||||
if (playerDeploymentZone != null) playerDeploymentZone.ResetSlots();
|
||||
for (int i = 0; i < playerArmyUnits.Count; i++)
|
||||
{
|
||||
if (playerArmyUnits[i] != null)
|
||||
{
|
||||
playerArmyUnits[i].gameObject.SetActive(true);
|
||||
Vector3 pos = playerDeploymentZone != null ? playerDeploymentZone.GetNextSpawnPosition() : playerArmyUnits[i].transform.position;
|
||||
playerArmyUnits[i].WarpToPosition(pos, Quaternion.LookRotation(Vector3.forward));
|
||||
playerArmyUnits[i].HealToFull();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateUnitCounts();
|
||||
SetBattleState(BattleState.Preparation);
|
||||
}
|
||||
|
||||
public void SetGameSpeed(float speedMultiplier)
|
||||
{
|
||||
Time.timeScale = Mathf.Clamp(speedMultiplier, 0.5f, 4.0f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class BattleResultData
|
||||
{
|
||||
public bool isVictory;
|
||||
public int waveNumber;
|
||||
public int goldEarned;
|
||||
public int survivingPlayerUnits;
|
||||
public int totalPlayerUnits;
|
||||
public int enemyCasualties;
|
||||
public List<UnitLevelUpReport> levelUpReports;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class UnitLevelUpReport
|
||||
{
|
||||
public string unitName;
|
||||
public int oldLevel;
|
||||
public int newLevel;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Managers/BattleManager.cs.meta
Normal file
2
Assets/Scripts/Managers/BattleManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2687a4f202496ac4daa61593aae80710
|
||||
Reference in New Issue
Block a user