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;
}
}
}