Files
Northbound/Assets/Scripts/CreepCamp.cs

321 lines
11 KiB
C#

using Northbound.Data;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
public class CreepCamp : NetworkBehaviour
{
[Header("Camp Settings")]
[Tooltip("Creep prefabs available to spawn")]
[SerializeField] private List<GameObject> creepPrefabs = new List<GameObject>();
[Header("Reward Settings")]
[Tooltip("Resource pickup prefab to spawn when all creeps are defeated")]
[SerializeField] private GameObject resourcePickupPrefab;
[Tooltip("Base resource amount multiplier (actual amount = this * camp strength)")]
[SerializeField] private int baseResourceAmount = 50;
private float _zPosition;
private float _campStrength;
private float _campCostBudget;
private float _spawnRadius;
private int _maxSpawnAttempts;
private readonly List<EnemyUnit> _spawnedCreeps = new List<EnemyUnit>();
private ResourcePickup _resourcePickup;
private readonly Dictionary<EnemyUnit, System.Action<ulong>> _deathHandlers = new Dictionary<EnemyUnit, System.Action<ulong>>();
public override void OnNetworkSpawn()
{
if (IsServer)
{
SpawnCreeps();
}
}
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
if (IsServer)
{
// 모든 이벤트 구독 해제
foreach (var kvp in _deathHandlers)
{
if (kvp.Key != null && kvp.Value != null)
{
kvp.Key.OnDeath -= kvp.Value;
}
}
// 리스트와 딕셔너리 비우기
_spawnedCreeps.Clear();
_deathHandlers.Clear();
}
}
public void InitializeCamp(float zPosition, float strengthMultiplier, float costBudget, float radius, int maxAttempts)
{
_zPosition = zPosition;
_campStrength = strengthMultiplier;
_campCostBudget = costBudget;
_spawnRadius = radius;
_maxSpawnAttempts = maxAttempts;
}
public void SetCreepPrefabs(List<GameObject> prefabs)
{
creepPrefabs.Clear();
creepPrefabs.AddRange(prefabs);
}
private void SpawnCreeps()
{
if (creepPrefabs.Count == 0)
{
Debug.LogWarning($"[CreepCamp] No creep prefabs assigned!");
return;
}
// 리소스 픽업 스폰 (비활성화 상태로)
SpawnResourcePickup();
float remainingCost = _campCostBudget * _campStrength;
int spawnedCount = 0;
for (int attempt = 0; attempt < _maxSpawnAttempts && remainingCost > 0; attempt++)
{
GameObject selectedCreep = SelectCreepByCost(remainingCost);
if (selectedCreep == null)
{
Debug.LogWarning($"[CreepCamp] No affordable creeps. Remaining cost: {remainingCost:F2}");
break;
}
CreepData creepData = GetCreepDataFromPrefab(selectedCreep);
if (creepData == null)
{
Debug.LogWarning($"[CreepCamp] Could not get creep data from {selectedCreep.name}");
continue;
}
SpawnCreep(selectedCreep);
remainingCost -= creepData.cost;
spawnedCount++;
}
Debug.Log($"<color=green>[CreepCamp] Spawned {spawnedCount} creeps (Cost budget: {_campCostBudget * _campStrength:F2}, Used: {(_campCostBudget * _campStrength) - remainingCost:F2})</color>");
}
private GameObject SelectCreepByCost(float remainingCost)
{
List<GameObject> affordableCreeps = new List<GameObject>();
foreach (var prefab in creepPrefabs)
{
CreepData creepData = GetCreepDataFromPrefab(prefab);
if (creepData != null && creepData.cost <= remainingCost)
{
affordableCreeps.Add(prefab);
}
}
if (affordableCreeps.Count == 0)
{
return null;
}
float totalWeight = 0f;
foreach (var prefab in affordableCreeps)
{
CreepData creepData = GetCreepDataFromPrefab(prefab);
if (creepData != null)
{
totalWeight += creepData.weight;
}
}
if (totalWeight == 0f)
{
return affordableCreeps[Random.Range(0, affordableCreeps.Count)];
}
float randomValue = Random.Range(0f, totalWeight);
float cumulativeWeight = 0f;
foreach (var prefab in affordableCreeps)
{
CreepData creepData = GetCreepDataFromPrefab(prefab);
if (creepData != null)
{
cumulativeWeight += creepData.weight;
if (randomValue <= cumulativeWeight)
{
return prefab;
}
}
}
return affordableCreeps[Random.Range(0, affordableCreeps.Count)];
}
private bool CanSpawnAnyCreep(float remainingCost)
{
foreach (var prefab in creepPrefabs)
{
CreepData creepData = GetCreepDataFromPrefab(prefab);
if (creepData != null && creepData.cost <= remainingCost)
{
return true;
}
}
return false;
}
private void SpawnResourcePickup()
{
if (resourcePickupPrefab == null)
{
Debug.LogWarning($"[CreepCamp] No resource pickup prefab assigned!");
return;
}
GameObject pickup = Instantiate(resourcePickupPrefab, transform.position, Quaternion.identity);
_resourcePickup = pickup.GetComponent<ResourcePickup>();
if (_resourcePickup == null)
{
Debug.LogError($"[CreepCamp] ResourcePickup component not found on prefab!");
Destroy(pickup);
return;
}
// 캠프 강도에 비례하여 리소스 양 설정
_resourcePickup.resourceAmount = Mathf.RoundToInt(baseResourceAmount * _campStrength);
// NetworkObject 추가 및 스폰
NetworkObject networkObj = pickup.GetComponent<NetworkObject>();
if (networkObj == null)
{
networkObj = pickup.AddComponent<NetworkObject>();
}
networkObj.SpawnWithOwnership(NetworkManager.Singleton.LocalClientId);
// 비활성화는 ServerRpc를 통해 처리
DisablePickupClientRpc();
Debug.Log($"<color=cyan>[CreepCamp] Resource pickup spawned (Amount: {_resourcePickup.resourceAmount})</color>");
}
private void HandleCreepDeath(EnemyUnit deadCreep)
{
// 이벤트 구독 해제 (메모리 누수 방지)
if (_deathHandlers.ContainsKey(deadCreep))
{
if (deadCreep != null)
{
deadCreep.OnDeath -= _deathHandlers[deadCreep];
}
_deathHandlers.Remove(deadCreep);
}
// 리스트에서 해당 creep 제거
_spawnedCreeps.Remove(deadCreep);
Debug.Log($"<color=orange>[CreepCamp] Creep died. Remaining creeps: {_spawnedCreeps.Count}</color>");
// 모든 creep이 처치되었으면 ResourcePickup 활성화
if (_spawnedCreeps.Count == 0 && _resourcePickup != null)
{
EnableResourcePickupClientRpc();
Debug.Log($"<color=green>[CreepCamp] All creeps defeated! Resource pickup enabled.</color>");
}
}
[Rpc(SendTo.ClientsAndHost)]
private void EnableResourcePickupClientRpc()
{
if (_resourcePickup != null)
{
_resourcePickup.gameObject.SetActive(true);
}
}
[Rpc(SendTo.ClientsAndHost)]
private void DisablePickupClientRpc()
{
if (_resourcePickup != null)
{
_resourcePickup.gameObject.SetActive(false);
}
}
private CreepData GetCreepDataFromPrefab(GameObject prefab)
{
if (prefab == null) return null;
CreepDataComponent component = prefab.GetComponent<CreepDataComponent>();
if (component != null)
{
return component.creepData;
}
return null;
}
private void SpawnCreep(GameObject prefab)
{
Vector3 spawnOffset = Random.insideUnitSphere * _spawnRadius;
spawnOffset.y = 0;
GameObject creep = Instantiate(prefab, transform.position + spawnOffset, Quaternion.identity);
if (creep.GetComponent<FogOfWarVisibility>() == null)
{
var visibility = creep.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = false;
visibility.updateInterval = 0.2f;
}
NetworkObject networkObj = creep.GetComponent<NetworkObject>();
if (networkObj == null)
{
networkObj = creep.AddComponent<NetworkObject>();
}
// EnemyUnit 참조 저장 및 이벤트 구독
EnemyUnit enemyUnit = creep.GetComponent<EnemyUnit>();
if (enemyUnit != null)
{
_spawnedCreeps.Add(enemyUnit);
// Dictionary에 핸들러 저장 (메모리 누수 방지)
System.Action<ulong> handler = (killerId) => HandleCreepDeath(enemyUnit);
_deathHandlers[enemyUnit] = handler;
enemyUnit.OnDeath += handler;
}
else
{
Debug.LogWarning($"[CreepCamp] EnemyUnit component not found on creep prefab {prefab.name}");
}
networkObj.SpawnWithOwnership(NetworkManager.Singleton.LocalClientId);
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, _spawnRadius);
}
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(1f, 0f, 0f, 0.3f);
Gizmos.DrawSphere(transform.position, _spawnRadius);
}
}
}