Files
Cielonos/Assets/Scripts/MainGame/AttackArea/AttackAreaDistributionHelper.cs
SoulliesOfficial 9a9e48f8a5
2026-06-27 12:52:03 -04:00

150 lines
6.1 KiB
C#

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