1045 lines
38 KiB
C#
1045 lines
38 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;
|
|
[Tooltip("생성된 오브젝트를 부모로 그룹화")]
|
|
[SerializeField] private bool groupUnderParent = 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 _initialResourcePosition;
|
|
private Vector2 _corePosition;
|
|
private Vector2 _barracksPosition;
|
|
|
|
[Header("Resource Generation")]
|
|
[Tooltip("자원 프리팹")]
|
|
public GameObject resourcePrefab;
|
|
[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, 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;
|
|
|
|
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]
|
|
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 = FindObjectOfType<Core>();
|
|
if (core != null)
|
|
{
|
|
_corePosition = new Vector2(core.transform.position.x, core.transform.position.z);
|
|
Debug.Log($"[MapGenerator] Found Core at {_corePosition}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[MapGenerator] Core not found in scene!");
|
|
_corePosition = Vector2.zero;
|
|
}
|
|
|
|
Resource[] resources = FindObjectsOfType<Resource>();
|
|
if (resources.Length > 0)
|
|
{
|
|
_initialResourcePosition = new Vector2(resources[0].transform.position.x, resources[0].transform.position.z);
|
|
Debug.Log($"[MapGenerator] Found initial Resource at {_initialResourcePosition}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[MapGenerator] No Resource found in scene!");
|
|
_initialResourcePosition = Vector2.zero;
|
|
}
|
|
|
|
GameObject barracks = GameObject.Find("Worker Hall");
|
|
if (barracks != null)
|
|
{
|
|
_barracksPosition = new Vector2(barracks.transform.position.x, barracks.transform.position.z);
|
|
Debug.Log($"[MapGenerator] Found Worker Hall at {_barracksPosition}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[MapGenerator] Worker Hall not found in scene!");
|
|
_barracksPosition = Vector2.zero;
|
|
}
|
|
}
|
|
|
|
private void GenerateMap()
|
|
{
|
|
if (groupUnderParent)
|
|
{
|
|
_objectsParent = new GameObject("Generated Map Objects").transform;
|
|
_objectsParent.SetParent(transform);
|
|
|
|
NetworkObject parentNetworkObj = _objectsParent.gameObject.GetComponent<NetworkObject>();
|
|
if (parentNetworkObj == null)
|
|
{
|
|
parentNetworkObj = _objectsParent.gameObject.AddComponent<NetworkObject>();
|
|
}
|
|
parentNetworkObj.Spawn();
|
|
}
|
|
|
|
_spawnedPositions.Clear();
|
|
_creepCampPositions.Clear();
|
|
|
|
if (generateResourcesFirst)
|
|
{
|
|
GenerateResources();
|
|
GenerateObstacles();
|
|
}
|
|
else
|
|
{
|
|
GenerateObstacles();
|
|
GenerateResources();
|
|
}
|
|
|
|
GenerateCreepCamps();
|
|
|
|
Debug.Log($"[MapGenerator] Map generation complete!");
|
|
}
|
|
|
|
#region Resource Generation
|
|
|
|
private void GenerateResources()
|
|
{
|
|
if (resourcePrefab == null)
|
|
{
|
|
Debug.LogError("[MapGenerator] Resource prefab not assigned!");
|
|
return;
|
|
}
|
|
|
|
bool success = false;
|
|
|
|
for (int attempt = 0; attempt < maxResourceGenerationAttempts; attempt++)
|
|
{
|
|
if (TryGenerateResources(out var resources))
|
|
{
|
|
_generatedResources = resources;
|
|
success = true;
|
|
Debug.Log($"[MapGenerator] Successfully generated resources on attempt {attempt + 1}");
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"[MapGenerator] Resource generation attempt {attempt + 1} failed validation");
|
|
}
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
Debug.LogWarning("[MapGenerator] All resource generation attempts failed, using fallback layout");
|
|
GenerateResourceFallbackLayout();
|
|
}
|
|
|
|
SpawnResources();
|
|
}
|
|
|
|
private bool TryGenerateResources(out ResourceData[] resources)
|
|
{
|
|
resources = null;
|
|
int additionalResourceCount = Random.Range(minResourceCount, maxResourceCount + 1);
|
|
ResourceData[] tempResources = new ResourceData[additionalResourceCount];
|
|
|
|
for (int i = 0; i < additionalResourceCount; 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;
|
|
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 = initialResourceProduction;
|
|
|
|
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 = new Vector2(30, 100), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
|
|
new ResourceData { position = new Vector2(50, 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(60, 400), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
|
|
new ResourceData { position = new Vector2(35, 500), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
|
|
new ResourceData { position = new Vector2(55, 600), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
|
|
new ResourceData { position = new Vector2(25, 700), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
|
|
new ResourceData { position = new Vector2(45, 150), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction },
|
|
new ResourceData { position = new Vector2(65, 250), baseProduction = additionalResourceBaseProduction, qualityModifier = 0f, finalProduction = additionalResourceBaseProduction }
|
|
};
|
|
|
|
float totalProduction = initialResourceProduction;
|
|
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;
|
|
}
|
|
|
|
Debug.Log($"[MapGenerator] Resource fallback layout generated. Global multiplier: {_globalProductionMultiplier:F2}");
|
|
}
|
|
|
|
private void SpawnResources()
|
|
{
|
|
float totalProduction = initialResourceProduction;
|
|
|
|
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)
|
|
{
|
|
Debug.LogError($"[MapGenerator] Resource prefab at index {i} doesn't have Resource component!");
|
|
Destroy(resourceObj);
|
|
continue;
|
|
}
|
|
|
|
resource.InitializeQuality(_generatedResources[i].qualityModifier);
|
|
Debug.Log($"[MapGenerator] Spawned resource at {_generatedResources[i].position} with quality: {_generatedResources[i].qualityModifier:F1}% (Actual Max: {resource.ActualMaxResources}, Actual Recharge: {resource.ActualRechargeAmount})");
|
|
|
|
NetworkObject networkObj = resourceObj.GetComponent<NetworkObject>();
|
|
if (networkObj != null)
|
|
{
|
|
networkObj.Spawn();
|
|
if (groupUnderParent && _objectsParent != null)
|
|
{
|
|
resourceObj.transform.SetParent(_objectsParent);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[MapGenerator] Resource prefab at index {i} doesn't have NetworkObject component!");
|
|
Destroy(resourceObj);
|
|
}
|
|
|
|
_spawnedPositions.Add(new Vector3(_generatedResources[i].position.x, 1f, _generatedResources[i].position.y));
|
|
totalProduction += _generatedResources[i].finalProduction;
|
|
}
|
|
|
|
Debug.Log($"[MapGenerator] Spawned {_generatedResources.Length} additional resources (plus initial at {_initialResourcePosition}). Total production: {totalProduction:F2} mana/min. Global multiplier: {_globalProductionMultiplier:F2}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Obstacle Generation
|
|
|
|
private void GenerateObstacles()
|
|
{
|
|
if (obstacles.Count == 0)
|
|
{
|
|
Debug.Log("[MapGenerator] No obstacles configured, skipping obstacle generation.");
|
|
return;
|
|
}
|
|
|
|
int totalSpawned = 0;
|
|
int targetCount = Mathf.RoundToInt(maxTotalObstacles * obstacleDensity);
|
|
|
|
Debug.Log($"[MapGenerator] Starting obstacle generation. Target: {targetCount}, Density: {obstacleDensity}");
|
|
|
|
foreach (var obstacle in obstacles)
|
|
{
|
|
if (obstacle.prefab == null)
|
|
{
|
|
Debug.LogWarning($"[MapGenerator] Obstacle prefab is null!");
|
|
continue;
|
|
}
|
|
|
|
int minRequired = obstacle.minCount;
|
|
for (int i = 0; i < minRequired && totalSpawned < maxTotalObstacles; i++)
|
|
{
|
|
if (TrySpawnObstacle(obstacle))
|
|
{
|
|
totalSpawned++;
|
|
Debug.Log($"[MapGenerator] Spawned min required obstacle: {obstacle.prefab.name}");
|
|
}
|
|
}
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
Debug.Log($"[MapGenerator] Spawned {totalSpawned} obstacles (target: {targetCount}). Consecutive failures: {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 (groupUnderParent && _objectsParent != null)
|
|
{
|
|
obstacle.transform.SetParent(_objectsParent);
|
|
}
|
|
|
|
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()
|
|
{
|
|
Vector3 center = transform.position;
|
|
float x = Random.Range(-playableAreaWidth / 2f, playableAreaWidth / 2f);
|
|
float z = Random.Range(startZ, endZ);
|
|
|
|
return new Vector3(x, center.y, z);
|
|
}
|
|
|
|
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)
|
|
{
|
|
int count = 0;
|
|
if (_objectsParent != null)
|
|
{
|
|
foreach (Transform child in _objectsParent)
|
|
{
|
|
if (child.name.StartsWith(prefab.name))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
#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()
|
|
{
|
|
if (_generatedResources == null)
|
|
return initialResourceProduction;
|
|
|
|
float total = initialResourceProduction;
|
|
for (int i = 0; i < _generatedResources.Length; i++)
|
|
{
|
|
total += _generatedResources[i].finalProduction;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
public void ClearGeneratedObjects()
|
|
{
|
|
if (_objectsParent != null)
|
|
{
|
|
NetworkObject parentNetworkObj = _objectsParent.GetComponent<NetworkObject>();
|
|
if (parentNetworkObj != null && parentNetworkObj.IsSpawned)
|
|
{
|
|
parentNetworkObj.Despawn(true);
|
|
}
|
|
|
|
foreach (Transform child in _objectsParent)
|
|
{
|
|
NetworkObject networkObj = child.GetComponent<NetworkObject>();
|
|
if (networkObj != null && networkObj.IsSpawned)
|
|
{
|
|
networkObj.Despawn(true);
|
|
}
|
|
}
|
|
|
|
if (Application.isPlaying)
|
|
{
|
|
Destroy(_objectsParent.gameObject);
|
|
}
|
|
else
|
|
{
|
|
DestroyImmediate(_objectsParent.gameObject);
|
|
}
|
|
_objectsParent = null;
|
|
}
|
|
|
|
_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;
|
|
}
|
|
}
|