Files
Northbound/Assets/Scripts/MapGenerator.cs

934 lines
32 KiB
C#

using Unity.Netcode;
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Northbound
{
public class MapGenerator : NetworkBehaviour
{
public static MapGenerator Instance { get; private set; }
[Header("Common Settings")]
[Tooltip("맵 생성 시작 시 자동 생성")]
[SerializeField] private bool generateOnSpawn = true;
[Header("Map Boundaries")]
[Tooltip("X 범위 (-width/2 ~ +width/2)")]
public float playableAreaWidth = 200f;
[Tooltip("Z 시작 위치")]
public float startZ = 25f;
[Tooltip("Z 끝 위치")]
public float endZ = 700f;
private Vector2 _corePosition;
private Vector2 _barracksPosition;
[Header("Resource Generation")]
[Tooltip("자원 프리팹")]
public GameObject resourcePrefab;
[Tooltip("기본 자원 위치 (초기 스폰 지점)")]
public Vector2 initialResourcePosition = new Vector2(0f, 5f);
[Tooltip("최소 자원 개수 (초기 자원 제외)")]
[Range(8, 12)]
public int minResourceCount = 8;
[Tooltip("최대 자원 개수 (초기 자원 제외)")]
[Range(8, 12)]
public int maxResourceCount = 12;
[Tooltip("자원 간 최소 거리")]
public float minDistanceBetweenResources = 80f;
[Tooltip("코어와의 최소 거리")]
public float minDistanceFromCore = 50f;
[Tooltip("막사와의 최소 거리")]
public float minDistanceFromBarracks = 50f;
[Tooltip("초기 자원 생산량 (분당)")]
public float initialResourceProduction = 50f;
[Tooltip("추가 자원 기본 생산량 (분당)")]
public float additionalResourceBaseProduction = 25f;
[Tooltip("목표 총 생산량 (분당)")]
public float targetTotalProduction = 300f;
[Tooltip("생산량 허용 오차")]
[Range(0f, 0.2f)]
public float targetProductionTolerance = 0.05f;
[Tooltip("최소 품질 보정")]
public float minQualityModifier = -30f;
[Tooltip("최대 품질 보정")]
public float maxQualityModifier = 30f;
[Tooltip("최대 생성 시도 횟수")]
public int maxResourceGenerationAttempts = 10;
[Header("Obstacle Settings")]
[Tooltip("배치할 장애물 목록")]
[SerializeField] private List<ObstacleEntry> obstacles = new List<ObstacleEntry>();
[Tooltip("장애물 밀도")]
[Range(0f, 1f)]
[SerializeField] private float obstacleDensity = 0.5f;
[Tooltip("최대 장애물 개수")]
[SerializeField] private int maxTotalObstacles = 100;
[Tooltip("장애물 간 최소 거리")]
[SerializeField] private float minDistanceBetweenObstacles = 2f;
[Tooltip("배치 전 충돌 체크")]
[SerializeField] private bool checkCollision = true;
[Tooltip("충돌 체크 레이어")]
[SerializeField] private LayerMask collisionLayers = -1;
[Tooltip("충돌 체크 반경")]
[SerializeField] private float collisionCheckRadius = 1f;
[Tooltip("지형에 맞춰 배치")]
[SerializeField] private bool alignToTerrain = true;
[Tooltip("Y축 랜덤 회전 적용")]
[SerializeField] private bool randomRotation = true;
[Tooltip("크기 랜덤 변화 범위")]
[Range(0f, 0.5f)]
[SerializeField] private float scaleVariation = 0.1f;
[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, 1000)]
[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;
[Tooltip("크립 스폰 비용 예산")]
[SerializeField] private float campCostBudget = 10f;
[Tooltip("스폰 반경 (캠프 주변)")]
[SerializeField] private float spawnRadius = 5f;
[Tooltip("최대 크립 스폰 시도 횟수")]
[SerializeField] private int maxSpawnAttempts = 50;
[Header("Generation Order")]
[Tooltip("자원 먼저 생성 후 장애물 생성 (true) 또는 그 반대 (false)")]
[SerializeField] private bool generateResourcesFirst = true;
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>();
[System.Serializable]
public class ObstacleEntry
{
[Tooltip("배치할 장애물 프리팹")]
public GameObject prefab;
[Tooltip("이 장애물의 스폰 가중치")]
[Range(1, 100)]
public int spawnWeight = 50;
[Tooltip("최소 스폰 개수")]
public int minCount = 0;
[Tooltip("최대 스폰 개수")]
public int maxCount = 10;
}
public override void OnNetworkSpawn()
{
if (IsServer && generateOnSpawn)
{
Instance = this;
FindImportantPositions();
GenerateMap();
}
}
private void FindImportantPositions()
{
Core core = FindFirstObjectByType<Core>();
if (core != null)
{
_corePosition = new Vector2(core.transform.position.x, core.transform.position.z);
}
else
{
_corePosition = Vector2.zero;
}
GameObject barracks = GameObject.Find("Worker Hall");
if (barracks != null)
{
_barracksPosition = new Vector2(barracks.transform.position.x, barracks.transform.position.z);
}
else
{
_barracksPosition = Vector2.zero;
}
}
private void GenerateMap()
{
_spawnedPositions.Clear();
_creepCampPositions.Clear();
if (generateResourcesFirst)
{
GenerateResources();
GenerateObstacles();
}
else
{
GenerateObstacles();
GenerateResources();
}
GenerateCreepCamps();
}
#region Resource Generation
private void GenerateResources()
{
if (resourcePrefab == null)
{
return;
}
bool success = false;
for (int attempt = 0; attempt < maxResourceGenerationAttempts; attempt++)
{
if (TryGenerateResources(out var resources))
{
_generatedResources = resources;
success = true;
break;
}
}
if (!success)
{
GenerateResourceFallbackLayout();
}
SpawnResources();
}
private bool TryGenerateResources(out ResourceData[] resources)
{
resources = null;
int additionalResourceCount = Random.Range(minResourceCount, maxResourceCount + 1);
ResourceData[] tempResources = new ResourceData[additionalResourceCount + 1];
// Add initial resource at index 0
tempResources[0] = new ResourceData
{
position = initialResourcePosition,
baseProduction = initialResourceProduction,
qualityModifier = 0f,
finalProduction = initialResourceProduction
};
// Generate additional resources starting from index 1
for (int i = 1; i < tempResources.Length; i++)
{
Vector2 position;
float qualityModifier;
if (!TryFindValidResourcePosition(tempResources, i, out position))
{
return false;
}
qualityModifier = Random.Range(minQualityModifier, maxQualityModifier);
float qualityMultiplier = 1f + (qualityModifier / 100f);
float finalProduction = additionalResourceBaseProduction * qualityMultiplier;
tempResources[i] = new ResourceData
{
position = position,
baseProduction = additionalResourceBaseProduction,
qualityModifier = qualityModifier,
finalProduction = finalProduction
};
}
if (!ValidateProductionRate(tempResources, out float globalMultiplier))
{
return false;
}
_globalProductionMultiplier = globalMultiplier;
for (int i = 0; i < tempResources.Length; i++)
{
tempResources[i].finalProduction *= _globalProductionMultiplier;
}
resources = tempResources;
return true;
}
private bool TryFindValidResourcePosition(ResourceData[] existingResources, int currentIndex, out Vector2 position)
{
position = Vector2.zero;
// Initial resource uses fixed position
if (currentIndex == 0)
{
position = initialResourcePosition;
return true;
}
int maxAttempts = 100;
for (int attempt = 0; attempt < maxAttempts; attempt++)
{
float x = Random.Range(-playableAreaWidth / 2f, playableAreaWidth / 2f);
float y = Random.Range(startZ, endZ);
Vector2 candidatePosition = new Vector2(x, y);
if (IsValidResourcePosition(candidatePosition, existingResources, currentIndex))
{
position = candidatePosition;
return true;
}
}
return false;
}
private bool IsValidResourcePosition(Vector2 position, ResourceData[] existingResources, int currentIndex)
{
if (Vector2.Distance(position, initialResourcePosition) < minDistanceBetweenResources)
{
return false;
}
if (Vector2.Distance(position, _corePosition) < minDistanceFromCore)
{
return false;
}
if (Vector2.Distance(position, _barracksPosition) < minDistanceFromBarracks)
{
return false;
}
for (int i = 0; i < currentIndex; i++)
{
if (Vector2.Distance(position, existingResources[i].position) < minDistanceBetweenResources)
{
return false;
}
}
return true;
}
private bool ValidateProductionRate(ResourceData[] additionalResources, out float globalMultiplier)
{
globalMultiplier = 1f;
float totalProduction = 0f;
for (int i = 0; i < additionalResources.Length; i++)
{
totalProduction += additionalResources[i].finalProduction;
}
float minTarget = targetTotalProduction * (1f - targetProductionTolerance);
float maxTarget = targetTotalProduction * (1f + targetProductionTolerance);
if (totalProduction >= minTarget && totalProduction <= maxTarget)
{
globalMultiplier = 1f;
return true;
}
globalMultiplier = targetTotalProduction / totalProduction;
float adjustedTotal = totalProduction * globalMultiplier;
if (adjustedTotal >= minTarget && adjustedTotal <= maxTarget)
{
return true;
}
return false;
}
private void GenerateResourceFallbackLayout()
{
_generatedResources = new ResourceData[]
{
new ResourceData { position = initialResourcePosition, baseProduction = initialResourceProduction, qualityModifier = 0f, finalProduction = initialResourceProduction },
new ResourceData { position = new Vector2(-30, 100), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(30, 200), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(-20, 300), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(20, 400), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(0, 500), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(-35, 600), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(35, 700), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(-15, 150), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
new ResourceData { position = new Vector2(15, 250), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction }
};
float totalProduction = 0f;
for (int i = 0; i < _generatedResources.Length; i++)
{
totalProduction += _generatedResources[i].finalProduction;
}
_globalProductionMultiplier = targetTotalProduction / totalProduction;
for (int i = 0; i < _generatedResources.Length; i++)
{
_generatedResources[i].finalProduction *= _globalProductionMultiplier;
}
}
private void SpawnResources()
{
for (int i = 0; i < _generatedResources.Length; i++)
{
GameObject resourceObj = Instantiate(resourcePrefab, new Vector3(_generatedResources[i].position.x, 1f, _generatedResources[i].position.y), Quaternion.identity);
Resource resource = resourceObj.GetComponent<Resource>();
if (resource == null)
{
Destroy(resourceObj);
continue;
}
NetworkObject networkObj = resourceObj.GetComponent<NetworkObject>();
if (networkObj != null)
{
networkObj.SpawnWithOwnership(NetworkManager.ServerClientId);
// 스폰 후에 InitializeQuality 호출 (OnNetworkSpawn 이후에 호출해야 IsServer 체크가 작동함)
resource.InitializeQuality(_generatedResources[i].qualityModifier);
_spawnedPositions.Add(new Vector3(_generatedResources[i].position.x, 1f, _generatedResources[i].position.y));
}
else
{
Destroy(resourceObj);
}
}
}
#endregion
#region Obstacle Generation
private void GenerateObstacles()
{
if (obstacles.Count == 0)
{
return;
}
int totalSpawned = 0;
int targetCount = Mathf.RoundToInt(maxTotalObstacles * obstacleDensity);
foreach (var obstacle in obstacles)
{
if (obstacle.prefab == null)
{
continue;
}
int minRequired = obstacle.minCount;
for (int i = 0; i < minRequired && totalSpawned < maxTotalObstacles; i++)
{
if (TrySpawnObstacle(obstacle))
{
totalSpawned++;
}
}
}
int consecutiveFailures = 0;
int maxConsecutiveFailures = 50;
while (totalSpawned < targetCount && consecutiveFailures < maxConsecutiveFailures)
{
ObstacleEntry selectedObstacle = SelectRandomObstacle();
if (selectedObstacle == null || selectedObstacle.prefab == null)
{
consecutiveFailures++;
continue;
}
int currentCount = CountObstacleType(selectedObstacle.prefab);
if (currentCount >= selectedObstacle.maxCount)
{
consecutiveFailures++;
if (AllObstaclesAtMax())
{
break;
}
continue;
}
if (TrySpawnObstacle(selectedObstacle))
{
totalSpawned++;
consecutiveFailures = 0;
}
else
{
consecutiveFailures++;
}
}
}
private bool TrySpawnObstacle(ObstacleEntry obstacleEntry)
{
for (int attempt = 0; attempt < maxObstacleSpawnAttempts; attempt++)
{
Vector3 randomPos = GetRandomPositionInPlayableArea();
if (!IsValidObstaclePosition(randomPos))
{
continue;
}
if (checkCollision && Physics.CheckSphere(randomPos, collisionCheckRadius, collisionLayers))
{
continue;
}
if (alignToTerrain)
{
if (Physics.Raycast(randomPos + Vector3.up * 100f, Vector3.down, out RaycastHit hit, 200f))
{
randomPos = hit.point;
}
}
randomPos.y = 1f;
Quaternion rotation = randomRotation
? Quaternion.Euler(0, Random.Range(0f, 360f), 0)
: Quaternion.identity;
GameObject obstacle = Instantiate(obstacleEntry.prefab, randomPos, rotation);
if (scaleVariation > 0)
{
float scale = 1f + Random.Range(-scaleVariation, scaleVariation);
obstacle.transform.localScale *= scale;
}
NetworkObject networkObj = obstacle.GetComponent<NetworkObject>();
if (networkObj == null)
{
networkObj = obstacle.AddComponent<NetworkObject>();
}
networkObj.Spawn();
if (obstacle.GetComponent<FogOfWarVisibility>() == null)
{
var visibility = obstacle.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = false;
visibility.updateInterval = 0.2f;
}
_spawnedPositions.Add(randomPos);
return true;
}
return false;
}
private Vector3 GetRandomPositionInPlayableArea()
{
float x = Random.Range(-playableAreaWidth / 2f, playableAreaWidth / 2f);
float z = Random.Range(startZ, endZ);
Vector3 result = new Vector3(x, 1, z);
return result;
}
private bool IsValidObstaclePosition(Vector3 position)
{
foreach (var spawnedPos in _spawnedPositions)
{
if (Vector3.Distance(position, spawnedPos) < minDistanceBetweenObstacles)
{
return false;
}
}
if (Vector2.Distance(new Vector2(position.x, position.z), _corePosition) < minDistanceFromCore)
{
return false;
}
if (Vector2.Distance(new Vector2(position.x, position.z), _barracksPosition) < minDistanceFromBarracks)
{
return false;
}
return true;
}
private ObstacleEntry SelectRandomObstacle()
{
if (obstacles.Count == 0) return null;
int totalWeight = 0;
foreach (var obstacle in obstacles)
{
if (obstacle.prefab != null)
{
totalWeight += obstacle.spawnWeight;
}
}
if (totalWeight == 0) return null;
int randomValue = Random.Range(0, totalWeight);
int currentWeight = 0;
foreach (var obstacle in obstacles)
{
if (obstacle.prefab == null) continue;
currentWeight += obstacle.spawnWeight;
if (randomValue < currentWeight)
{
return obstacle;
}
}
return obstacles[0];
}
private bool AllObstaclesAtMax()
{
foreach (var obstacle in obstacles)
{
if (obstacle.prefab == null) continue;
int currentCount = CountObstacleType(obstacle.prefab);
if (currentCount < obstacle.maxCount)
{
return false;
}
}
return true;
}
private int CountObstacleType(GameObject prefab)
{
return 0;
}
#endregion
#region Creep Camp Generation
private void GenerateCreepCamps()
{
if (creepCampPrefab == null)
{
return;
}
LoadCreepPrefabs();
if (_creepPrefabs.Count == 0)
{
return;
}
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);
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);
SpawnCreepCamp(campPosition, strength);
_creepCampPositions.Add(campPosition);
additionalSpawned++;
}
}
}
private void LoadCreepPrefabs()
{
if (_creepPrefabs.Count > 0)
{
return;
}
#if UNITY_EDITOR
string[] prefabGuids = UnityEditor.AssetDatabase.FindAssets("t:Prefab", new[] { "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);
}
}
#else
// Creep prefabs not available in build - please assign manually
#endif
}
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;
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;
}
}
}
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);
if (campObj.GetComponent<FogOfWarVisibility>() == null)
{
var visibility = campObj.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = false;
visibility.updateInterval = 0.2f;
}
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, campCostBudget, spawnRadius, maxSpawnAttempts);
creepCamp.SetCreepPrefabs(_creepPrefabs);
NetworkObject networkObj = campObj.GetComponent<NetworkObject>();
if (networkObj == null)
{
networkObj = campObj.AddComponent<NetworkObject>();
}
networkObj.Spawn();
}
#endregion
#region Public Methods
public float GetTotalProduction()
{
if (_generatedResources == null)
return 0f;
float total = 0f;
for (int i = 0; i < _generatedResources.Length; i++)
{
total += _generatedResources[i].finalProduction;
}
return total;
}
public void ClearGeneratedObjects()
{
_spawnedPositions.Clear();
}
public void RegenerateMap()
{
ClearGeneratedObjects();
GenerateMap();
}
#endregion
#region Editor
private void OnDrawGizmos()
{
Vector3 center = transform.position;
center.x = 0f;
center.z = (startZ + endZ) / 2f;
float height = endZ - startZ;
Gizmos.color = new Color(0, 1, 0, 0.3f);
Gizmos.DrawCube(center, new Vector3(playableAreaWidth, 0.1f, height));
Gizmos.color = Color.green;
Gizmos.DrawWireCube(center, new Vector3(playableAreaWidth, 0.1f, height));
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(new Vector3(_corePosition.x, 0, _corePosition.y), minDistanceFromCore);
Gizmos.color = Color.magenta;
Gizmos.DrawWireSphere(new Vector3(_barracksPosition.x, 0, _barracksPosition.y), minDistanceFromBarracks);
}
private void OnDrawGizmosSelected()
{
#if UNITY_EDITOR
Gizmos.color = Color.yellow;
foreach (var pos in _spawnedPositions)
{
Gizmos.DrawWireSphere(pos, minDistanceBetweenObstacles / 2f);
}
if (_generatedResources != null)
{
Gizmos.color = Color.blue;
foreach (var resource in _generatedResources)
{
Gizmos.DrawWireSphere(new Vector3(resource.position.x, 0, resource.position.y), 5f);
}
}
if (resourcePrefab != null)
{
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
}
#endregion
}
public struct ResourceData
{
public Vector2 position;
public float baseProduction;
public float qualityModifier;
public float finalProduction;
}
}