using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; namespace Cielonos.MainGame.AttackArea { public static class AttackAreaDistributionHelper { /// /// 在圆形范围内生成不重叠的随机位置列表,并自动吸附到 NavMesh 上。 /// /// 生成范围的中心点 /// 允许生成的最大范围半径 /// 两个点之间的最小安全距离(防止重叠) /// 需要生成的点位数量 /// 已经预先放置好的点位(新生成的点位会避开这些点) /// 每个点位的最大尝试次数(防止死循环) /// NavMesh 投影搜索的最大距离 /// 成功生成的随机点位列表 public static List GenerateNonOverlappingInCircle( Vector3 center, float spawnRadius, float exclusionDistance, int count, List prePlacedPositions = null, int maxAttemptsPerPoint = 30, float navMeshSampleDistance = 10f) { List results = new List(); List occupiedPositions = new List(); if (prePlacedPositions != null) { occupiedPositions.AddRange(prePlacedPositions); } float minSqrDistance = exclusionDistance * exclusionDistance; for (int i = 0; i < count; i++) { Vector3 bestPos = Vector3.zero; bool found = false; for (int attempt = 0; attempt < maxAttemptsPerPoint; attempt++) { Vector2 randomOffset = Random.insideUnitCircle * spawnRadius; Vector3 candidatePos = center + new Vector3(randomOffset.x, 0f, randomOffset.y); // NavMesh projection if (NavMesh.SamplePosition(candidatePos, out NavMeshHit hit, navMeshSampleDistance, NavMesh.AllAreas)) { candidatePos = hit.position; } if (IsValidPosition(candidatePos, occupiedPositions, minSqrDistance)) { bestPos = candidatePos; found = true; break; } } if (found) { results.Add(bestPos); occupiedPositions.Add(bestPos); } } return results; } /// /// 在矩形范围内生成不重叠的随机位置列表,并自动吸附到 NavMesh 上。 /// /// 生成范围的中心点 /// 矩形的半长宽 (x对应X轴, y对应Z轴) /// 两个点之间的最小安全距离(防止重叠) /// 需要生成的点位数量 /// 已经预先放置好的点位(新生成的点位会避开这些点) /// 每个点位的最大尝试次数(防止死循环) /// NavMesh 投影搜索的最大距离 /// 成功生成的随机点位列表 public static List GenerateNonOverlappingInRect( Vector3 center, Vector2 extents, float exclusionDistance, int count, List prePlacedPositions = null, int maxAttemptsPerPoint = 30, float navMeshSampleDistance = 10f) { List results = new List(); List occupiedPositions = new List(); if (prePlacedPositions != null) { occupiedPositions.AddRange(prePlacedPositions); } float minSqrDistance = exclusionDistance * exclusionDistance; for (int i = 0; i < count; i++) { Vector3 bestPos = Vector3.zero; bool found = false; for (int attempt = 0; attempt < maxAttemptsPerPoint; attempt++) { float randomX = Random.Range(-extents.x, extents.x); float randomZ = Random.Range(-extents.y, extents.y); Vector3 candidatePos = center + new Vector3(randomX, 0f, randomZ); // NavMesh projection if (NavMesh.SamplePosition(candidatePos, out NavMeshHit hit, navMeshSampleDistance, NavMesh.AllAreas)) { candidatePos = hit.position; } if (IsValidPosition(candidatePos, occupiedPositions, minSqrDistance)) { bestPos = candidatePos; found = true; break; } } if (found) { results.Add(bestPos); occupiedPositions.Add(bestPos); } } return results; } private static bool IsValidPosition(Vector3 candidatePos, List occupiedPositions, float minSqrDistance) { foreach (var pos in occupiedPositions) { // 为了防止在楼上楼下的重叠,我们使用 3D 距离,但在大多数平面场景下也等同于 2D 距离 if ((candidatePos - pos).sqrMagnitude < minSqrDistance) { return false; } } return true; } } }