데이터 파이프라인 추가 및 수정 + 크립 및 크립 캠프 배치 자동화

Hierachy > System > MapGenerator 에서 크립 관련 변수 설정 가능
Kaykit PlantWarrior 애셋 추가
This commit is contained in:
2026-02-02 19:50:30 +09:00
parent 106fe81c88
commit dc4d71d4b6
81 changed files with 4744 additions and 174 deletions

View File

@@ -1,69 +0,0 @@
using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
/// <summary>
/// 건물에 데미지를 주는 테스트/공격 시스템
/// </summary>
public class BuildingDamageTest : MonoBehaviour
{
[Header("Damage Settings")]
public int damageAmount = 10;
public float damageInterval = 1f;
public KeyCode damageKey = KeyCode.F;
[Header("Target")]
public float maxDistance = 10f;
public LayerMask buildingLayer;
private float _lastDamageTime;
private void Update()
{
if (Input.GetKeyDown(damageKey))
{
TryDamageBuilding();
}
// 자동 데미지 (테스트용)
if (Input.GetKey(KeyCode.LeftShift) && Input.GetKey(damageKey))
{
if (Time.time - _lastDamageTime >= damageInterval)
{
TryDamageBuilding();
_lastDamageTime = Time.time;
}
}
}
private void TryDamageBuilding()
{
// 카메라에서 레이캐스트
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, maxDistance, buildingLayer))
{
Building building = hit.collider.GetComponentInParent<Building>();
if (building != null)
{
ulong attackerId = NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient
? NetworkManager.Singleton.LocalClientId
: 0;
building.TakeDamage(damageAmount, attackerId);
Debug.Log($"<color=yellow>[Test] {building.buildingData?.buildingName ?? ""}에게 {damageAmount} 데미지!</color>");
}
}
}
private void OnGUI()
{
GUILayout.BeginArea(new Rect(10, 10, 300, 100));
GUILayout.Label($"[{damageKey}] 건물에 {damageAmount} 데미지");
GUILayout.Label($"[Shift + {damageKey}] 연속 데미지");
GUILayout.EndArea();
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 62b3bdffc8c849d48886167d316e16b6

View File

@@ -60,6 +60,7 @@ namespace Northbound
if (buildingData == null) return;
// 아이콘 설정
/*
if (iconImage != null && buildingData.icon != null)
{
iconImage.sprite = buildingData.icon;
@@ -69,7 +70,8 @@ namespace Northbound
{
iconImage.enabled = false;
}
*/
iconImage.enabled = false;
// 이름 설정
if (nameText != null)
{

215
Assets/Scripts/CreepCamp.cs Normal file
View File

@@ -0,0 +1,215 @@
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("Spawning Settings")]
[Tooltip("Camp strength multiplier (higher = stronger creeps)")]
[SerializeField] private float campStrength = 1f;
[Tooltip("Cost budget for spawning creeps")]
[SerializeField] private float campCostBudget = 10f;
[Tooltip("Spawn radius around camp")]
[SerializeField] private float spawnRadius = 5f;
[Tooltip("Max creep spawn attempts")]
[SerializeField] private int maxSpawnAttempts = 50;
private float _zPosition;
public override void OnNetworkSpawn()
{
if (IsServer)
{
SpawnCreeps();
}
}
public void InitializeCamp(float zPosition, float strengthMultiplier)
{
_zPosition = zPosition;
campStrength = strengthMultiplier;
}
public void SetCreepPrefabs(List<GameObject> prefabs)
{
creepPrefabs.Clear();
creepPrefabs.AddRange(prefabs);
Debug.Log($"[CreepCamp] SetCreepPrefabs called with {prefabs.Count} prefabs. Current count: {creepPrefabs.Count}");
}
private void SpawnCreeps()
{
Debug.Log($"[CreepCamp] Starting creep spawn at Z={_zPosition:F1}, strength={campStrength:F2}x, prefabs={creepPrefabs.Count}");
if (creepPrefabs.Count == 0)
{
Debug.LogWarning($"[CreepCamp] No creep prefabs assigned!");
return;
}
float remainingCost = campCostBudget * campStrength;
Debug.Log($"[CreepCamp] Cost budget: {campCostBudget} x {campStrength:F2} = {remainingCost:F2}");
int spawnedCount = 0;
for (int attempt = 0; attempt < maxSpawnAttempts && remainingCost > 0; attempt++)
{
GameObject selectedCreep = SelectCreepByCost(remainingCost);
if (selectedCreep == null)
{
Debug.LogWarning($"[CreepCamp] Could not select creep (attempt {attempt + 1}/{maxSpawnAttempts}), remaining cost: {remainingCost:F2}");
break;
}
CreepData creepData = GetCreepDataFromPrefab(selectedCreep);
if (creepData == null)
{
Debug.LogWarning($"[CreepCamp] Could not get creep data from {selectedCreep.name}");
continue;
}
if (creepData.cost > remainingCost)
{
if (!CanSpawnAnyCreep(remainingCost))
{
Debug.LogWarning($"[CreepCamp] No affordable creeps found. Remaining cost: {remainingCost:F2}");
break;
}
continue;
}
Debug.Log($"[CreepCamp] Spawning {selectedCreep.name} (cost: {creepData.cost}, remaining: {remainingCost:F2})");
SpawnCreep(selectedCreep);
remainingCost -= creepData.cost;
spawnedCount++;
}
Debug.Log($"[CreepCamp] Spawned {spawnedCount} creeps at Z={_zPosition:F1} with strength {campStrength:F2}x");
}
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 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>();
}
networkObj.Spawn();
creep.transform.SetParent(transform);
}
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);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d4c5f296c2ebd7d41b2517a03c8b574c

View File

@@ -0,0 +1,62 @@
using Northbound.Data;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
namespace Northbound
{
[RequireComponent(typeof(EnemyUnit))]
[RequireComponent(typeof(EnemyAIController))]
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(NetworkObject))]
public class CreepDataComponent : MonoBehaviour
{
[Header("Data Reference")]
[Tooltip("ScriptableObject containing creep data")]
public CreepData creepData;
[Header("Auto-Apply Settings")]
[Tooltip("Automatically apply stats from creepData on Awake")]
public bool autoApplyOnAwake = true;
private void Awake()
{
if (autoApplyOnAwake && creepData != null)
{
ApplyCreepData();
}
}
public void ApplyCreepData()
{
if (creepData == null)
{
Debug.LogWarning("[CreepDataComponent] creepData is null", this);
return;
}
EnemyUnit enemyUnit = GetComponent<EnemyUnit>();
if (enemyUnit != null)
{
enemyUnit.maxHealth = creepData.maxHp;
}
EnemyAIController aiController = GetComponent<EnemyAIController>();
if (aiController != null)
{
aiController.moveSpeed = creepData.moveSpeed;
aiController.attackDamage = creepData.atkDamage;
aiController.attackRange = creepData.atkRange;
aiController.attackInterval = creepData.atkIntervalSec;
}
NavMeshAgent navAgent = GetComponent<NavMeshAgent>();
if (navAgent != null)
{
navAgent.speed = creepData.moveSpeed;
}
Debug.Log($"[CreepDataComponent] Applied data for {creepData.id} ({creepData.memo})", this);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 85e4c68e85ef1704b83cecb71de7d67a

View File

@@ -59,6 +59,7 @@ namespace Northbound.Editor
prefabSetups = new Dictionary<string, IPrefabSetup>
{
{ "Monster", new MonsterPrefabSetup() },
{ "Creep", new CreepPrefabSetup() },
{ "Tower", new TowerPrefabSetup() },
{ "Player", new PlayerPrefabSetup() }
};

View File

@@ -0,0 +1,118 @@
using Northbound.Data;
using UnityEditor;
using UnityEngine;
namespace Northbound.Editor
{
public class CreepPrefabSetup : IPrefabSetup
{
public string GetTemplateName()
{
return "CreepTemplate";
}
public void SetupPrefab(GameObject prefab, ScriptableObject data)
{
if (!(data is CreepData creepData))
{
Debug.LogWarning($"[CreepPrefabSetup] Expected CreepData, got {data.GetType().Name}");
return;
}
var creepDataComponent = prefab.GetComponent<CreepDataComponent>();
if (creepDataComponent != null)
{
creepDataComponent.creepData = creepData;
creepDataComponent.ApplyCreepData();
}
var animationController = prefab.GetComponent<MonsterAnimationController>();
if (animationController == null)
{
animationController = prefab.AddComponent<MonsterAnimationController>();
Debug.Log($"[CreepPrefabSetup] Added MonsterAnimationController component");
}
if (!string.IsNullOrEmpty(creepData.modelPath))
{
RemoveOldModel(prefab);
if (creepData.modelPath.ToLower().EndsWith(".fbx"))
{
GameObject fbxModel = AssetDatabase.LoadAssetAtPath<GameObject>(creepData.modelPath);
if (fbxModel != null)
{
GameObject fbxInstance = GameObject.Instantiate(fbxModel);
fbxInstance.name = "Model";
fbxInstance.transform.SetParent(prefab.transform, false);
fbxInstance.transform.localPosition = Vector3.zero;
fbxInstance.transform.localRotation = Quaternion.identity;
fbxInstance.transform.localScale = Vector3.one;
Debug.Log($"[CreepPrefabSetup] Applied FBX model: {creepData.modelPath}");
Avatar modelAvatar = fbxModel.GetComponent<Animator>()?.avatar;
if (modelAvatar != null)
{
Animator prefabAnimator = prefab.GetComponent<Animator>();
if (prefabAnimator != null)
{
prefabAnimator.avatar = modelAvatar;
Debug.Log($"[CreepPrefabSetup] Applied Avatar: {modelAvatar.name}");
}
}
}
else
{
Debug.LogWarning($"[CreepPrefabSetup] Could not load FBX model: {creepData.modelPath}");
}
}
else
{
var meshFilter = prefab.GetComponent<MeshFilter>();
if (meshFilter != null)
{
Mesh mesh = AssetDatabase.LoadAssetAtPath<Mesh>(creepData.modelPath);
if (mesh != null)
{
meshFilter.sharedMesh = mesh;
Debug.Log($"[CreepPrefabSetup] Applied mesh: {creepData.modelPath}");
}
else
{
Debug.LogWarning($"[CreepPrefabSetup] Could not load mesh: {creepData.modelPath}");
}
}
}
}
if (!string.IsNullOrEmpty(creepData.animationControllerPath))
{
Animator animator = prefab.GetComponent<Animator>();
if (animator != null)
{
RuntimeAnimatorController controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(creepData.animationControllerPath);
if (controller != null)
{
animator.runtimeAnimatorController = controller;
Debug.Log($"[CreepPrefabSetup] Applied Animator Controller: {creepData.animationControllerPath}");
}
else
{
Debug.LogWarning($"[CreepPrefabSetup] Could not load Animator Controller: {creepData.animationControllerPath}");
}
}
}
}
private void RemoveOldModel(GameObject prefab)
{
Transform oldModel = prefab.transform.Find("Model");
if (oldModel != null)
{
GameObject.DestroyImmediate(oldModel.gameObject);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 74efb75ea8ccbe54a9c86d0fd503f85f

View File

@@ -86,6 +86,27 @@ namespace Northbound
[Tooltip("배치 시도 최대 횟수")]
[SerializeField] private int maxObstacleSpawnAttempts = 50;
[Header("Creep Camp Settings")]
[Tooltip("크립 캠프 프리팹")]
public GameObject creepCampPrefab;
[Tooltip("크립 캠프 간 최소 거리")]
[SerializeField] private float minDistanceBetweenCamps = 30f;
[Tooltip("코어와의 최소 거리")]
[SerializeField] private float minDistanceFromCoreCamps = 30f;
[Tooltip("막사와의 최소 거리")]
[SerializeField] private float minDistanceFromBarracksCamps = 30f;
[Tooltip("초기 자원과의 최소 거리")]
[SerializeField] private float minDistanceFromInitialResource = 30f;
[Tooltip("추가 크립 캠프 개수")]
[Range(0, 10)]
[SerializeField] private int additionalCreepCampCount = 5;
[Tooltip("크립 캠프 강도 기본값")]
[SerializeField] private float baseCampStrength = 1f;
[Tooltip("Z 위치에 따른 강도 증가율 (Z 100당)")]
[SerializeField] private float strengthIncreasePerZ100 = 0.2f;
[Tooltip("자원 보호 캠프 강도 보너스 (far camp보다 얼마나 강할지)")]
[SerializeField] private float resourceCampStrengthBonus = 0.5f;
[Header("Generation Order")]
[Tooltip("자원 먼저 생성 후 장애물 생성 (true) 또는 그 반대 (false)")]
[SerializeField] private bool generateResourcesFirst = true;
@@ -93,6 +114,8 @@ namespace Northbound
private ResourceData[] _generatedResources;
private float _globalProductionMultiplier = 1f;
private List<Vector3> _spawnedPositions = new List<Vector3>();
private List<Vector3> _creepCampPositions = new List<Vector3>();
private List<GameObject> _creepPrefabs = new List<GameObject>();
private Transform _objectsParent;
[System.Serializable]
@@ -177,6 +200,7 @@ namespace Northbound
}
_spawnedPositions.Clear();
_creepCampPositions.Clear();
if (generateResourcesFirst)
{
@@ -189,6 +213,8 @@ namespace Northbound
GenerateResources();
}
GenerateCreepCamps();
Debug.Log($"[MapGenerator] Map generation complete!");
}
@@ -658,6 +684,241 @@ namespace Northbound
#endregion
#region Creep Camp Generation
private void GenerateCreepCamps()
{
if (creepCampPrefab == null)
{
Debug.LogWarning("[MapGenerator] Creep camp prefab not assigned, skipping creep camp generation.");
return;
}
LoadCreepPrefabs();
if (_creepPrefabs.Count == 0)
{
Debug.LogWarning("[MapGenerator] No creep prefabs found in Assets/Prefabs/Creep/, skipping creep camp generation.");
return;
}
Debug.Log($"[MapGenerator] Starting creep camp generation. Resources: {_generatedResources.Length}, Additional camps: {additionalCreepCampCount}");
int totalCampsSpawned = 0;
for (int i = 0; i < _generatedResources.Length; i++)
{
Vector3 campPosition = FindValidCampPositionForResource(_generatedResources[i].position);
if (campPosition != Vector3.zero)
{
float strength = CalculateCampStrength(_generatedResources[i].position.y, isResourceCamp: true);
Debug.Log($"[MapGenerator] Spawning resource camp {i + 1} for resource at {_generatedResources[i].position}, position: {campPosition}, strength: {strength:F2} (with bonus)");
SpawnCreepCamp(campPosition, strength);
_creepCampPositions.Add(campPosition);
totalCampsSpawned++;
}
}
int additionalSpawned = 0;
for (int i = 0; i < additionalCreepCampCount; i++)
{
Vector3 campPosition = FindValidCampPosition();
if (campPosition != Vector3.zero)
{
float strength = CalculateCampStrength(campPosition.z, isResourceCamp: false);
Debug.Log($"[MapGenerator] Spawning additional camp {i + 1}, position: {campPosition}, strength: {strength:F2}");
SpawnCreepCamp(campPosition, strength);
_creepCampPositions.Add(campPosition);
additionalSpawned++;
}
}
Debug.Log($"[MapGenerator] Spawned {totalCampsSpawned} resource-based camps and {additionalSpawned} additional camps. Total: {totalCampsSpawned + additionalSpawned}");
}
private void LoadCreepPrefabs()
{
Debug.Log($"[MapGenerator] LoadCreepPrefabs called, current count: {_creepPrefabs.Count}");
if (_creepPrefabs.Count > 0)
{
Debug.Log($"[MapGenerator] Skipping load, already have {_creepPrefabs.Count} prefabs");
return;
}
#if UNITY_EDITOR
string[] prefabGuids = UnityEditor.AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/Prefabs/Creep" });
Debug.Log($"[MapGenerator] Found {prefabGuids.Length} prefabs in Assets/Prefabs/Creep/");
_creepPrefabs.Clear();
foreach (string guid in prefabGuids)
{
string assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (prefab != null && prefab.GetComponent<CreepDataComponent>() != null)
{
_creepPrefabs.Add(prefab);
Debug.Log($"[MapGenerator] Added creep prefab: {prefab.name}");
}
}
#else
Debug.LogWarning("[MapGenerator] Creep prefabs not loaded in build. Please assign creep prefabs manually in Inspector.");
#endif
if (_creepPrefabs.Count > 0)
{
Debug.Log($"[MapGenerator] Loaded {_creepPrefabs.Count} creep prefabs from Assets/Prefabs/Creep/");
}
else
{
Debug.LogWarning("[MapGenerator] No creep prefabs loaded!");
}
}
private Vector3 FindValidCampPosition()
{
int maxAttempts = 200;
for (int attempt = 0; attempt < maxAttempts; attempt++)
{
Vector3 candidatePosition = GetRandomPositionInPlayableArea();
if (IsValidCreepCampPosition(candidatePosition))
{
return candidatePosition;
}
}
return Vector3.zero;
}
private bool IsValidCreepCampPosition(Vector3 position)
{
if (Vector2.Distance(new Vector2(position.x, position.z), _corePosition) < minDistanceFromCoreCamps)
{
return false;
}
if (Vector2.Distance(new Vector2(position.x, position.z), _barracksPosition) < minDistanceFromBarracksCamps)
{
return false;
}
if (Vector2.Distance(new Vector2(position.x, position.z), _initialResourcePosition) < minDistanceFromInitialResource)
{
return false;
}
foreach (var campPos in _creepCampPositions)
{
if (Vector3.Distance(position, campPos) < minDistanceBetweenCamps)
{
return false;
}
}
if (position.x < -playableAreaWidth / 2f || position.x > playableAreaWidth / 2f)
{
return false;
}
if (position.z < startZ || position.z > endZ)
{
return false;
}
return true;
}
private Vector3 FindValidCampPositionForResource(Vector2 resourcePosition)
{
Vector3 bestPosition = Vector3.zero;
float bestDistance = float.MaxValue;
int maxAttempts = 50;
Debug.Log($"[MapGenerator] FindValidCampPositionForResource: Resource at {resourcePosition}, bounds: X:[{-playableAreaWidth/2f}, {playableAreaWidth/2f}], Z:[{startZ}, {endZ}]");
for (int attempt = 0; attempt < maxAttempts; attempt++)
{
float angle = Random.Range(0f, 360f);
float distance = Random.Range(8f, 20f);
Vector3 candidatePosition = new Vector3(
resourcePosition.x + Mathf.Cos(angle * Mathf.Deg2Rad) * distance,
1f,
resourcePosition.y + Mathf.Sin(angle * Mathf.Deg2Rad) * distance
);
if (IsValidCreepCampPosition(candidatePosition))
{
if (Vector3.Distance(candidatePosition, new Vector3(resourcePosition.x, 0, resourcePosition.y)) < bestDistance)
{
bestDistance = Vector3.Distance(candidatePosition, new Vector3(resourcePosition.x, 0, resourcePosition.y));
bestPosition = candidatePosition;
}
}
}
if (bestPosition == Vector3.zero)
{
Debug.LogWarning($"[MapGenerator] Failed to find camp position for resource at {resourcePosition} after {maxAttempts} attempts");
}
return bestPosition;
}
private float CalculateCampStrength(float zPosition, bool isResourceCamp = false)
{
float normalizedZ = (zPosition - startZ) / (endZ - startZ);
float strengthMultiplier = baseCampStrength + (normalizedZ * (strengthIncreasePerZ100 * (endZ - startZ) / 100f));
if (isResourceCamp)
{
strengthMultiplier *= resourceCampStrengthBonus;
}
return strengthMultiplier;
}
private void SpawnCreepCamp(Vector3 position, float strength)
{
GameObject campObj = Instantiate(creepCampPrefab, position, Quaternion.identity);
CreepCamp creepCamp = campObj.GetComponent<CreepCamp>();
if (creepCamp == null)
{
Debug.LogError($"[MapGenerator] Creep camp prefab doesn't have CreepCamp component!");
Destroy(campObj);
return;
}
creepCamp.InitializeCamp(position.z, strength);
creepCamp.SetCreepPrefabs(_creepPrefabs);
Debug.Log($"[MapGenerator] Camp initialized with {_creepPrefabs.Count} creep prefabs");
NetworkObject networkObj = campObj.GetComponent<NetworkObject>();
if (networkObj == null)
{
networkObj = campObj.AddComponent<NetworkObject>();
}
networkObj.Spawn();
if (groupUnderParent && _objectsParent != null)
{
campObj.transform.SetParent(_objectsParent);
}
if (campObj.GetComponent<FogOfWarVisibility>() == null)
{
var visibility = campObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = false;
visibility.updateInterval = 0.2f;
}
}
#endregion
#region Public Methods
public float GetTotalProduction()
@@ -758,6 +1019,15 @@ namespace Northbound
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(new Vector3(_initialResourcePosition.x, 0, _initialResourcePosition.y), 5f);
}
if (creepCampPrefab != null)
{
Gizmos.color = Color.red;
foreach (var campPos in _creepCampPositions)
{
Gizmos.DrawWireSphere(campPos, 8f);
}
}
#endif
}