第6章 群体智能与战术决策系统
群体智能与战术决策系统摘要 本章探讨了Unity中群体智能系统的实现技术,包括队形管理、协同战术和高级决策算法。群体智能通过模拟多个AI角色的协作行为,提升游戏真实感和挑战性。6.2节详细介绍了队形系统的设计与实现,包含队形类型枚举、位置分配算法和动态调整机制。核心实现采用FormationManager类管理队形锚点和单位位置,支持线形、楔形、V形等多种队形配置,并考虑适应性、稳定性和性能优化等
第6章 群体智能与战术决策系统
6.1 群体智能系统概述
在复杂的游戏环境中,单个AI角色的行为已经无法满足现代游戏对真实感的需求。群体智能系统通过模拟多个AI角色之间的协作与战术配合,能够创造出更加真实、更具挑战性的游戏体验。本章将深入探讨Unity引擎中实现群体智能与战术决策的各种技术,从基础的队形管理到高级的战术分析算法。
群体智能的核心思想是让多个AI角色像真实团队一样协作行动,而不是各自为战。这种协作可以体现在多个层面:基础层面的队形保持和移动同步,战术层面的协同攻击和防御,以及战略层面的资源分配和目标规划。在商业游戏项目中,一个优秀的群体智能系统能够显著提升游戏的可玩性和重玩价值。
Unity引擎提供了强大的AI工具集,但实现高级群体智能需要开发者结合多种技术。本章将介绍如何使用路径点系统、扩展的寻路算法、势力图分析和战斗循环设计等技术,构建一个完整的群体智能解决方案。我们将从基础概念开始,逐步深入到复杂的战术决策实现。
6.2 队形系统的设计与实现
队形系统是群体智能的基础,它决定了多个AI角色如何组织和移动。一个好的队形系统不仅需要保持队形的完整性,还要能够适应环境变化和战术需求。
6.2.1 队形系统基础理论
队形系统包含几个关键要素:队形类型、位置分配、动态调整和障碍回避。常见的队形类型包括线形、楔形、V形、方形和圆形等,每种队形都有其特定的战术优势。位置分配算法需要确保每个角色在队形中都有明确的位置,并且能够根据情况动态调整。
在实现队形系统时,需要考虑以下设计原则:
- 适应性:队形能够根据环境障碍和战术需求自动调整
- 稳定性:角色在队形中的位置变化应平滑自然
- 效率:队形计算不应过多消耗性能资源
- 可扩展性:支持不同规模和类型的队形
6.2.2 Unity中的队形系统实现
以下是一个完整的队形系统实现,展示了如何在Unity中创建和管理多种队形:
using UnityEngine;
using System.Collections.Generic;
namespace FormationSystem
{
// 队形类型枚举
public enum FormationType
{
Line, // 线形
Wedge, // 楔形
Vee, // V形
Square, // 方形
Circle, // 圆形
Column, // 纵队
Custom // 自定义
}
// 队形位置数据
public class FormationSlot
{
public Vector3 localPosition;
public Quaternion localRotation;
public int slotIndex;
public GameObject occupiedBy;
public float arrivalThreshold = 0.5f;
public FormationSlot(Vector3 position, Quaternion rotation, int index)
{
localPosition = position;
localRotation = rotation;
slotIndex = index;
occupiedBy = null;
}
public Vector3 GetWorldPosition(Transform formationAnchor)
{
return formationAnchor.TransformPoint(localPosition);
}
public Quaternion GetWorldRotation(Transform formationAnchor)
{
return formationAnchor.rotation * localRotation;
}
public bool IsOccupied()
{
return occupiedBy != null;
}
public void Occupy(GameObject unit)
{
occupiedBy = unit;
}
public void Vacate()
{
occupiedBy = null;
}
}
// 队形配置数据
[System.Serializable]
public class FormationConfiguration
{
public FormationType formationType;
public float spacing = 2.0f; // 单位间距
public float rowSpacing = 2.0f; // 行间距
public int unitsPerRow = 5; // 每行单位数
public float circleRadius = 5.0f; // 圆形队形半径
public float wedgeAngle = 45.0f; // 楔形角度
public bool dynamicSpacing = true; // 动态调整间距
public float maxSpacing = 4.0f; // 最大间距
public float minSpacing = 1.0f; // 最小间距
}
// 队形管理器
public class FormationManager : MonoBehaviour
{
[Header("队形配置")]
public FormationConfiguration formationConfig;
[Header("队形锚点")]
public Transform formationAnchor;
public bool autoCreateAnchor = true;
[Header("调试设置")]
public bool drawFormationGizmos = true;
public Color formationColor = Color.cyan;
// 队形数据
private List<FormationSlot> formationSlots;
private List<GameObject> registeredUnits;
private Dictionary<GameObject, FormationSlot> unitSlotMap;
// 队形状态
private bool isFormationActive = true;
private Vector3 formationForward;
private Vector3 lastAnchorPosition;
private Quaternion lastAnchorRotation;
private void Awake()
{
InitializeFormationSystem();
}
private void InitializeFormationSystem()
{
// 初始化数据结构
formationSlots = new List<FormationSlot>();
registeredUnits = new List<GameObject>();
unitSlotMap = new Dictionary<GameObject, FormationSlot>();
// 创建或获取队形锚点
if (formationAnchor == null && autoCreateAnchor)
{
GameObject anchorObject = new GameObject("FormationAnchor");
formationAnchor = anchorObject.transform;
formationAnchor.position = transform.position;
formationAnchor.rotation = transform.rotation;
}
// 设置初始朝向
formationForward = formationAnchor != null ?
formationAnchor.forward : Vector3.forward;
// 生成初始队形
GenerateFormationSlots();
Debug.Log("队形系统初始化完成");
}
private void Update()
{
if (!isFormationActive || formationAnchor == null)
{
return;
}
// 检测锚点变化
bool anchorMoved = formationAnchor.position != lastAnchorPosition;
bool anchorRotated = formationAnchor.rotation != lastAnchorRotation;
if (anchorMoved || anchorRotated)
{
UpdateFormationPositions();
lastAnchorPosition = formationAnchor.position;
lastAnchorRotation = formationAnchor.rotation;
}
// 更新单位位置
UpdateUnitsInFormation();
}
private void GenerateFormationSlots()
{
formationSlots.Clear();
// 根据队形类型生成位置
switch (formationConfig.formationType)
{
case FormationType.Line:
GenerateLineFormation();
break;
case FormationType.Wedge:
GenerateWedgeFormation();
break;
case FormationType.Vee:
GenerateVeeFormation();
break;
case FormationType.Square:
GenerateSquareFormation();
break;
case FormationType.Circle:
GenerateCircleFormation();
break;
case FormationType.Column:
GenerateColumnFormation();
break;
case FormationType.Custom:
// 自定义队形需要额外配置
break;
}
Debug.Log($"生成 {formationSlots.Count} 个队形位置");
}
private void GenerateLineFormation()
{
int unitCount = registeredUnits.Count;
if (unitCount == 0) unitCount = formationConfig.unitsPerRow;
float totalWidth = (unitCount - 1) * formationConfig.spacing;
float startX = -totalWidth / 2f;
for (int i = 0; i < unitCount; i++)
{
float xPos = startX + i * formationConfig.spacing;
Vector3 localPos = new Vector3(xPos, 0, 0);
Quaternion localRot = Quaternion.identity;
FormationSlot slot = new FormationSlot(localPos, localRot, i);
formationSlots.Add(slot);
}
}
private void GenerateWedgeFormation()
{
int unitCount = registeredUnits.Count;
if (unitCount == 0) unitCount = formationConfig.unitsPerRow * 2;
// 计算楔形布局
int rows = Mathf.CeilToInt(Mathf.Sqrt(unitCount));
int slotIndex = 0;
for (int row = 0; row < rows; row++)
{
int unitsInRow = row + 1;
float rowDepth = -row * formationConfig.rowSpacing;
float rowWidth = (unitsInRow - 1) * formationConfig.spacing;
float startX = -rowWidth / 2f;
for (int col = 0; col < unitsInRow; col++)
{
if (slotIndex >= unitCount) break;
float xPos = startX + col * formationConfig.spacing;
Vector3 localPos = new Vector3(xPos, 0, rowDepth);
// 面向中心
Vector3 directionToCenter = -localPos.normalized;
Quaternion localRot = Quaternion.LookRotation(directionToCenter);
FormationSlot slot = new FormationSlot(localPos, localRot, slotIndex);
formationSlots.Add(slot);
slotIndex++;
}
}
}
private void GenerateVeeFormation()
{
int unitCount = registeredUnits.Count;
if (unitCount == 0) unitCount = formationConfig.unitsPerRow * 2;
// V形队形是楔形的镜像
GenerateWedgeFormation();
// 复制并镜像队形
int currentCount = formationSlots.Count;
for (int i = 0; i < currentCount; i++)
{
FormationSlot originalSlot = formationSlots[i];
FormationSlot mirroredSlot = new FormationSlot(
new Vector3(-originalSlot.localPosition.x,
originalSlot.localPosition.y,
originalSlot.localPosition.z),
Quaternion.LookRotation(new Vector3(
-originalSlot.localRotation.x,
originalSlot.localRotation.y,
originalSlot.localRotation.z)),
formationSlots.Count
);
formationSlots.Add(mirroredSlot);
}
}
private void GenerateSquareFormation()
{
int unitCount = registeredUnits.Count;
if (unitCount == 0) unitCount = formationConfig.unitsPerRow * formationConfig.unitsPerRow;
int sideLength = Mathf.CeilToInt(Mathf.Sqrt(unitCount));
float halfSize = (sideLength - 1) * formationConfig.spacing / 2f;
int slotIndex = 0;
for (int x = 0; x < sideLength; x++)
{
for (int z = 0; z < sideLength; z++)
{
if (slotIndex >= unitCount) break;
float xPos = -halfSize + x * formationConfig.spacing;
float zPos = -halfSize + z * formationConfig.spacing;
Vector3 localPos = new Vector3(xPos, 0, zPos);
Quaternion localRot = Quaternion.identity;
FormationSlot slot = new FormationSlot(localPos, localRot, slotIndex);
formationSlots.Add(slot);
slotIndex++;
}
}
}
private void GenerateCircleFormation()
{
int unitCount = registeredUnits.Count;
if (unitCount == 0) unitCount = formationConfig.unitsPerRow;
float angleStep = 360f / unitCount;
for (int i = 0; i < unitCount; i++)
{
float angle = i * angleStep * Mathf.Deg2Rad;
float xPos = Mathf.Cos(angle) * formationConfig.circleRadius;
float zPos = Mathf.Sin(angle) * formationConfig.circleRadius;
Vector3 localPos = new Vector3(xPos, 0, zPos);
// 面向圆心
Vector3 directionToCenter = -localPos.normalized;
Quaternion localRot = Quaternion.LookRotation(directionToCenter);
FormationSlot slot = new FormationSlot(localPos, localRot, i);
formationSlots.Add(slot);
}
}
private void GenerateColumnFormation()
{
int unitCount = registeredUnits.Count;
if (unitCount == 0) unitCount = formationConfig.unitsPerRow * 3;
int columns = Mathf.Min(formationConfig.unitsPerRow, unitCount);
int rows = Mathf.CeilToInt((float)unitCount / columns);
float columnWidth = (columns - 1) * formationConfig.spacing;
float columnStartX = -columnWidth / 2f;
int slotIndex = 0;
for (int col = 0; col < columns; col++)
{
for (int row = 0; row < rows; row++)
{
if (slotIndex >= unitCount) break;
float xPos = columnStartX + col * formationConfig.spacing;
float zPos = -row * formationConfig.rowSpacing;
Vector3 localPos = new Vector3(xPos, 0, zPos);
Quaternion localRot = Quaternion.identity;
FormationSlot slot = new FormationSlot(localPos, localRot, slotIndex);
formationSlots.Add(slot);
slotIndex++;
}
}
}
private void UpdateFormationPositions()
{
// 根据动态间距调整队形
if (formationConfig.dynamicSpacing)
{
AdjustDynamicSpacing();
}
}
private void AdjustDynamicSpacing()
{
// 根据单位数量动态调整间距
int unitCount = registeredUnits.Count;
if (unitCount <= 1) return;
// 计算理想间距
float idealSpacing = Mathf.Lerp(
formationConfig.minSpacing,
formationConfig.maxSpacing,
Mathf.InverseLerp(1, 20, unitCount)
);
// 平滑过渡到新间距
formationConfig.spacing = Mathf.Lerp(
formationConfig.spacing,
idealSpacing,
Time.deltaTime * 2f
);
// 重新生成队形
GenerateFormationSlots();
ReassignUnitsToSlots();
}
public void RegisterUnit(GameObject unit)
{
if (registeredUnits.Contains(unit))
{
return;
}
registeredUnits.Add(unit);
// 分配队形位置
AssignUnitToSlot(unit);
// 重新生成队形以适应新单位
GenerateFormationSlots();
ReassignUnitsToSlots();
Debug.Log($"单位 {unit.name} 已注册到队形系统");
}
public void UnregisterUnit(GameObject unit)
{
if (!registeredUnits.Contains(unit))
{
return;
}
// 释放占用的位置
if (unitSlotMap.ContainsKey(unit))
{
FormationSlot slot = unitSlotMap[unit];
slot.Vacate();
unitSlotMap.Remove(unit);
}
registeredUnits.Remove(unit);
// 重新生成队形
GenerateFormationSlots();
ReassignUnitsToSlots();
Debug.Log($"单位 {unit.name} 已从队形系统注销");
}
private void AssignUnitToSlot(GameObject unit)
{
// 寻找最近的空位置
FormationSlot nearestSlot = null;
float nearestDistance = float.MaxValue;
Vector3 unitPosition = unit.transform.position;
foreach (FormationSlot slot in formationSlots)
{
if (slot.IsOccupied())
{
continue;
}
Vector3 slotWorldPos = slot.GetWorldPosition(formationAnchor);
float distance = Vector3.Distance(unitPosition, slotWorldPos);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestSlot = slot;
}
}
if (nearestSlot != null)
{
nearestSlot.Occupy(unit);
unitSlotMap[unit] = nearestSlot;
}
}
private void ReassignUnitsToSlots()
{
// 清空所有位置
foreach (FormationSlot slot in formationSlots)
{
slot.Vacate();
}
unitSlotMap.Clear();
// 重新分配所有单位
foreach (GameObject unit in registeredUnits)
{
AssignUnitToSlot(unit);
}
}
private void UpdateUnitsInFormation()
{
foreach (KeyValuePair<GameObject, FormationSlot> entry in unitSlotMap)
{
GameObject unit = entry.Key;
FormationSlot slot = entry.Value;
if (unit == null)
{
continue;
}
// 获取目标位置和旋转
Vector3 targetPosition = slot.GetWorldPosition(formationAnchor);
Quaternion targetRotation = slot.GetWorldRotation(formationAnchor);
// 计算移动方向
Vector3 moveDirection = targetPosition - unit.transform.position;
float distance = moveDirection.magnitude;
// 如果距离较远,则移动单位
if (distance > slot.arrivalThreshold)
{
// 获取单位的移动组件(这里简化处理)
// 实际项目中可能需要根据具体的移动系统调整
if (unit.TryGetComponent<UnityEngine.AI.NavMeshAgent>(out var navAgent))
{
navAgent.SetDestination(targetPosition);
navAgent.speed = CalculateFormationSpeed(distance);
}
else
{
// 简单的位置插值
unit.transform.position = Vector3.MoveTowards(
unit.transform.position,
targetPosition,
Time.deltaTime * 5f
);
}
// 平滑旋转
unit.transform.rotation = Quaternion.Slerp(
unit.transform.rotation,
targetRotation,
Time.deltaTime * 3f
);
}
}
}
private float CalculateFormationSpeed(float distanceToTarget)
{
// 根据距离调整速度,确保队形保持
if (distanceToTarget > 10f)
{
return 8f; // 快速接近
}
else if (distanceToTarget > 3f)
{
return 4f; // 中等速度
}
else
{
return 2f; // 慢速微调
}
}
public void ChangeFormationType(FormationType newType)
{
formationConfig.formationType = newType;
GenerateFormationSlots();
ReassignUnitsToSlots();
Debug.Log($"队形已切换为: {newType}");
}
public void SetFormationAnchor(Transform newAnchor)
{
formationAnchor = newAnchor;
UpdateFormationPositions();
}
public void SetFormationActive(bool active)
{
isFormationActive = active;
}
public Vector3 GetSlotPositionForUnit(GameObject unit)
{
if (unitSlotMap.ContainsKey(unit))
{
return unitSlotMap[unit].GetWorldPosition(formationAnchor);
}
return unit.transform.position;
}
private void OnDrawGizmos()
{
if (!drawFormationGizmos || formationSlots == null)
{
return;
}
// 绘制队形位置
Gizmos.color = formationColor;
foreach (FormationSlot slot in formationSlots)
{
Vector3 worldPos = formationAnchor != null ?
slot.GetWorldPosition(formationAnchor) :
transform.TransformPoint(slot.localPosition);
// 绘制位置点
Gizmos.DrawSphere(worldPos, 0.2f);
// 绘制朝向
Quaternion worldRot = formationAnchor != null ?
slot.GetWorldRotation(formationAnchor) :
transform.rotation * slot.localRotation;
Vector3 forward = worldRot * Vector3.forward;
Gizmos.DrawLine(worldPos, worldPos + forward * 0.5f);
// 绘制编号
#if UNITY_EDITOR
UnityEditor.Handles.Label(worldPos + Vector3.up * 0.3f,
slot.slotIndex.ToString());
#endif
}
// 绘制队形锚点
if (formationAnchor != null)
{
Gizmos.color = Color.yellow;
Gizmos.DrawSphere(formationAnchor.position, 0.3f);
Gizmos.DrawLine(formationAnchor.position,
formationAnchor.position + formationAnchor.forward);
}
}
}
// 队形单位控制器
public class FormationUnitController : MonoBehaviour
{
[Header("队形设置")]
public FormationManager formationManager;
public bool autoRegister = true;
[Header("移动设置")]
public float moveSpeed = 5f;
public float rotationSpeed = 120f;
public float formationTolerance = 1f;
// 状态变量
private bool isInFormation = false;
private Vector3 formationTarget;
private Quaternion formationRotation;
private void Start()
{
if (autoRegister && formationManager != null)
{
formationManager.RegisterUnit(gameObject);
isInFormation = true;
}
}
private void Update()
{
if (!isInFormation || formationManager == null)
{
return;
}
// 获取队形目标位置
formationTarget = formationManager.GetSlotPositionForUnit(gameObject);
// 计算移动方向
Vector3 toTarget = formationTarget - transform.position;
float distance = toTarget.magnitude;
// 如果在容差范围内,则停止移动
if (distance <= formationTolerance)
{
return;
}
// 移动和旋转
MoveToFormationPosition(toTarget, distance);
}
private void MoveToFormationPosition(Vector3 direction, float distance)
{
// 标准化方向
direction.Normalize();
// 计算移动量
float moveDistance = moveSpeed * Time.deltaTime;
if (moveDistance > distance)
{
moveDistance = distance;
}
// 应用移动
transform.position += direction * moveDistance;
// 平滑旋转到目标方向
if (direction != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
}
public void JoinFormation(FormationManager manager)
{
formationManager = manager;
formationManager.RegisterUnit(gameObject);
isInFormation = true;
}
public void LeaveFormation()
{
if (formationManager != null)
{
formationManager.UnregisterUnit(gameObject);
}
isInFormation = false;
}
private void OnDestroy()
{
if (formationManager != null)
{
formationManager.UnregisterUnit(gameObject);
}
}
}
}
6.2.3 队形系统的优化与扩展
在商业项目中,队形系统需要进一步优化以适应复杂场景。以下是一些优化策略的实现:
// 高级队形管理器
public class AdvancedFormationManager : FormationManager
{
[System.Serializable]
public class AdvancedFormationSettings
{
public bool useObstacleAvoidance = true;
public float obstacleDetectionRange = 5f;
public LayerMask obstacleMask = -1;
public bool useLaneSystem = true;
public int maxLanes = 3;
public bool adaptiveFormation = true;
public float adaptationSpeed = 2f;
}
public AdvancedFormationSettings advancedSettings;
private List<Vector3> formationLanes;
private Dictionary<GameObject, int> unitLaneMap;
protected override void InitializeFormationSystem()
{
base.InitializeFormationSystem();
// 初始化高级系统
formationLanes = new List<Vector3>();
unitLaneMap = new Dictionary<GameObject, int>();
// 生成车道系统
if (advancedSettings.useLaneSystem)
{
GenerateFormationLanes();
}
}
private void GenerateFormationLanes()
{
formationLanes.Clear();
// 根据队形类型生成车道
switch (formationConfig.formationType)
{
case FormationType.Line:
case FormationType.Column:
GenerateStraightLanes();
break;
case FormationType.Wedge:
case FormationType.Vee:
GenerateAngledLanes();
break;
default:
// 其他队形不使用车道系统
break;
}
}
private void GenerateStraightLanes()
{
int laneCount = Mathf.Min(advancedSettings.maxLanes, registeredUnits.Count);
float laneSpacing = formationConfig.spacing;
for (int i = 0; i < laneCount; i++)
{
float laneOffset = (i - (laneCount - 1) / 2f) * laneSpacing;
Vector3 laneDirection = formationAnchor.forward;
formationLanes.Add(laneDirection * laneOffset);
}
}
private void GenerateAngledLanes()
{
// 楔形和V形队形的车道生成逻辑
int laneCount = advancedSettings.maxLanes;
float angleStep = formationConfig.wedgeAngle / (laneCount - 1);
for (int i = 0; i < laneCount; i++)
{
float angle = (i - (laneCount - 1) / 2f) * angleStep;
Quaternion rotation = Quaternion.Euler(0, angle, 0);
Vector3 laneDirection = rotation * formationAnchor.forward;
formationLanes.Add(laneDirection);
}
}
protected override void UpdateUnitsInFormation()
{
if (advancedSettings.useObstacleAvoidance)
{
UpdateFormationWithObstacleAvoidance();
}
else
{
base.UpdateUnitsInFormation();
}
}
private void UpdateFormationWithObstacleAvoidance()
{
foreach (KeyValuePair<GameObject, FormationSlot> entry in unitSlotMap)
{
GameObject unit = entry.Key;
FormationSlot slot = entry.Value;
if (unit == null)
{
continue;
}
Vector3 targetPosition = slot.GetWorldPosition(formationAnchor);
// 检测障碍物
Vector3 avoidanceOffset = CalculateObstacleAvoidance(unit, targetPosition);
Vector3 adjustedTarget = targetPosition + avoidanceOffset;
// 更新单位位置
UpdateUnitPosition(unit, adjustedTarget);
}
}
private Vector3 CalculateObstacleAvoidance(GameObject unit, Vector3 targetPosition)
{
Vector3 avoidanceOffset = Vector3.zero;
// 射线检测前方障碍物
Vector3 direction = (targetPosition - unit.transform.position).normalized;
float detectionRange = advancedSettings.obstacleDetectionRange;
RaycastHit hit;
if (Physics.Raycast(unit.transform.position, direction, out hit,
detectionRange, advancedSettings.obstacleMask))
{
// 计算避障偏移
Vector3 hitNormal = hit.normal;
avoidanceOffset = hitNormal * detectionRange * 0.5f;
// 根据队形类型调整避障策略
if (advancedSettings.useLaneSystem && unitLaneMap.ContainsKey(unit))
{
avoidanceOffset = AdjustAvoidanceForLane(unit, avoidanceOffset);
}
}
return avoidanceOffset;
}
private Vector3 AdjustAvoidanceForLane(GameObject unit, Vector3 avoidanceOffset)
{
int laneIndex = unitLaneMap[unit];
// 尝试保持在同一车道上
Vector3 laneDirection = formationLanes[laneIndex];
Vector3 projectedOffset = Vector3.Project(avoidanceOffset, laneDirection);
// 限制偏移量,避免偏离车道太远
float maxLaneDeviation = formationConfig.spacing * 0.5f;
if (projectedOffset.magnitude > maxLaneDeviation)
{
projectedOffset = projectedOffset.normalized * maxLaneDeviation;
}
return projectedOffset;
}
public void AssignUnitToLane(GameObject unit, int laneIndex)
{
if (laneIndex >= 0 && laneIndex < formationLanes.Count)
{
unitLaneMap[unit] = laneIndex;
}
}
public void AutoAssignLanes()
{
if (!advancedSettings.useLaneSystem || formationLanes.Count == 0)
{
return;
}
// 根据单位位置自动分配车道
int unitsPerLane = Mathf.CeilToInt((float)registeredUnits.Count / formationLanes.Count);
int currentLane = 0;
int unitsInCurrentLane = 0;
foreach (GameObject unit in registeredUnits)
{
AssignUnitToLane(unit, currentLane);
unitsInCurrentLane++;
if (unitsInCurrentLane >= unitsPerLane)
{
currentLane++;
unitsInCurrentLane = 0;
if (currentLane >= formationLanes.Count)
{
currentLane = formationLanes.Count - 1;
}
}
}
}
private void UpdateUnitPosition(GameObject unit, Vector3 targetPosition)
{
// 简化的位置更新
// 实际项目中应使用单位的移动系统
float moveSpeed = 5f;
float arrivalThreshold = 0.5f;
Vector3 toTarget = targetPosition - unit.transform.position;
float distance = toTarget.magnitude;
if (distance > arrivalThreshold)
{
unit.transform.position = Vector3.MoveTowards(
unit.transform.position,
targetPosition,
moveSpeed * Time.deltaTime
);
}
}
}
6.3 协作寻路算法:扩展A*实现
传统的A算法适用于单个单位的路径规划,但在群体智能中,我们需要考虑多个单位的协作和战术需求。扩展的A算法能够为群体提供更加智能的路径选择。
6.3.1 A*算法基础与扩展
A*算法通过评估每个节点的代价函数(f(n) = g(n) + h(n))来寻找最优路径,其中g(n)是从起点到当前节点的实际代价,h(n)是从当前节点到目标的估计代价。在协作寻路中,我们需要扩展这个代价函数以考虑群体因素:
- 群体协调代价:考虑其他单位的位置和路径
- 战术优势代价:考虑路径的隐蔽性和安全性
- 队形保持代价:确保群体能够保持队形移动
- 资源消耗代价:考虑能量、弹药等资源消耗
6.3.2 Unity中的协作A*实现
以下是一个扩展的A*算法实现,专门为群体协作设计:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace CooperativePathfinding
{
// 协作路径节点
public class CooperativePathNode
{
public Vector3 position;
public float gCost; // 从起点到当前节点的实际代价
public float hCost; // 从当前节点到目标的估计代价
public float fCost { get { return gCost + hCost; } } // 总代价
public CooperativePathNode parent;
public bool isWalkable = true;
// 协作扩展字段
public float groupPenalty; // 群体惩罚(避免拥挤)
public float tacticalValue; // 战术价值(隐蔽性等)
public float formationCost; // 队形保持代价
public int occupancyCount; // 当前占用单位数
public CooperativePathNode(Vector3 pos)
{
position = pos;
gCost = float.MaxValue;
hCost = 0;
groupPenalty = 0;
tacticalValue = 1.0f; // 默认值
formationCost = 0;
occupancyCount = 0;
}
public float GetAdjustedFCost(float groupWeight, float tacticalWeight)
{
// 调整后的代价函数
float baseCost = gCost + hCost;
float adjustedCost = baseCost *
(1 + groupPenalty * groupWeight) *
tacticalValue * tacticalWeight;
return adjustedCost + formationCost;
}
}
// 协作A*路径规划器
public class CooperativeAStarPlanner : MonoBehaviour
{
[System.Serializable]
public class PlanningSettings
{
public float nodeSpacing = 1.0f;
public float maxSlopeAngle = 45.0f;
public LayerMask walkableMask = -1;
public LayerMask obstacleMask = -1;
// 代价权重
public float groupAvoidanceWeight = 0.5f;
public float tacticalAdvantageWeight = 0.3f;
public float formationWeight = 0.2f;
public float resourceWeight = 0.1f;
// 性能设置
public int maxIterations = 1000;
public bool useMultiThreading = false;
public float planningTimeout = 5.0f;
}
[Header("规划设置")]
public PlanningSettings settings;
[Header("调试")]
public bool drawGridGizmos = false;
public bool drawPathGizmos = true;
public Color walkableColor = Color.green;
public Color obstacleColor = Color.red;
// 网格数据
private Dictionary<Vector3Int, CooperativePathNode> gridNodes;
private Bounds gridBounds;
private float gridCellSize;
// 群体数据
private List<GameObject> groupMembers;
private Dictionary<GameObject, CooperativePathNode> memberPositions;
// 路径缓存
private Dictionary<string, List<Vector3>> pathCache;
private void Awake()
{
InitializePathfindingSystem();
}
private void InitializePathfindingSystem()
{
gridNodes = new Dictionary<Vector3Int, CooperativePathNode>();
groupMembers = new List<GameObject>();
memberPositions = new Dictionary<GameObject, CooperativePathNode>();
pathCache = new Dictionary<string, List<Vector3>>();
gridCellSize = settings.nodeSpacing;
Debug.Log("协作A*路径规划系统初始化完成");
}
public void RegisterGroupMember(GameObject member)
{
if (!groupMembers.Contains(member))
{
groupMembers.Add(member);
UpdateMemberPosition(member);
}
}
public void UnregisterGroupMember(GameObject member)
{
if (groupMembers.Contains(member))
{
groupMembers.Remove(member);
if (memberPositions.ContainsKey(member))
{
memberPositions.Remove(member);
}
}
}
private void UpdateMemberPosition(GameObject member)
{
Vector3 memberPos = member.transform.position;
CooperativePathNode node = GetOrCreateNodeAtPosition(memberPos);
if (node != null)
{
memberPositions[member] = node;
node.occupancyCount++;
}
}
private void Update()
{
// 更新所有成员位置
foreach (GameObject member in groupMembers)
{
UpdateMemberPosition(member);
}
// 更新网格节点代价
UpdateGridCosts();
}
private void UpdateGridCosts()
{
// 更新每个节点的群体惩罚
foreach (CooperativePathNode node in gridNodes.Values)
{
UpdateNodeGroupPenalty(node);
UpdateNodeTacticalValue(node);
UpdateNodeFormationCost(node);
}
}
private void UpdateNodeGroupPenalty(CooperativePathNode node)
{
// 根据附近单位的数量计算群体惩罚
int nearbyUnits = 0;
float maxRadius = settings.nodeSpacing * 3f;
foreach (GameObject member in groupMembers)
{
if (memberPositions.ContainsKey(member))
{
CooperativePathNode memberNode = memberPositions[member];
float distance = Vector3.Distance(node.position, memberNode.position);
if (distance <= maxRadius)
{
nearbyUnits++;
}
}
}
// 惩罚公式:指数增长避免过度拥挤
node.groupPenalty = Mathf.Pow(1.5f, nearbyUnits) - 1.0f;
}
private void UpdateNodeTacticalValue(CooperativePathNode node)
{
// 计算战术价值(隐蔽性、安全性等)
float visibility = CalculateVisibility(node.position);
float coverValue = CalculateCoverValue(node.position);
float elevationValue = CalculateElevationValue(node.position);
// 综合战术价值(值越低越好)
node.tacticalValue = 1.0f +
visibility * 0.5f -
coverValue * 0.3f +
elevationValue * 0.2f;
}
private float CalculateVisibility(Vector3 position)
{
// 简化的可见性计算
// 实际项目中需要更复杂的视线检测
int rayCount = 8;
float maxDistance = 20f;
float visibleCount = 0;
for (int i = 0; i < rayCount; i++)
{
float angle = i * (360f / rayCount) * Mathf.Deg2Rad;
Vector3 direction = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle));
RaycastHit hit;
if (Physics.Raycast(position, direction, out hit, maxDistance, settings.obstacleMask))
{
// 有障碍物遮挡,减少可见性
}
else
{
visibleCount++;
}
}
return visibleCount / rayCount;
}
private float CalculateCoverValue(Vector3 position)
{
// 计算掩体价值
float coverValue = 0f;
float checkRadius = 2f;
Collider[] nearbyColliders = Physics.OverlapSphere(position, checkRadius, settings.obstacleMask);
foreach (Collider collider in nearbyColliders)
{
// 简单的掩体价值评估
Vector3 toCover = collider.transform.position - position;
float distance = toCover.magnitude;
if (distance > 0)
{
coverValue += 1.0f / distance;
}
}
return Mathf.Clamp01(coverValue);
}
private float CalculateElevationValue(Vector3 position)
{
// 计算高度优势
// 实际项目中需要地形高度数据
return 0.1f; // 简化处理
}
private void UpdateNodeFormationCost(CooperativePathNode node)
{
// 计算队形保持代价
if (groupMembers.Count == 0)
{
node.formationCost = 0;
return;
}
// 计算到群体中心的距离
Vector3 groupCenter = CalculateGroupCenter();
float distanceToCenter = Vector3.Distance(node.position, groupCenter);
float maxFormationRadius = settings.nodeSpacing * Mathf.Sqrt(groupMembers.Count);
// 距离中心越远,代价越高
node.formationCost = Mathf.Clamp01(distanceToCenter / maxFormationRadius) * 2.0f;
}
private Vector3 CalculateGroupCenter()
{
if (groupMembers.Count == 0)
{
return Vector3.zero;
}
Vector3 sum = Vector3.zero;
foreach (GameObject member in groupMembers)
{
sum += member.transform.position;
}
return sum / groupMembers.Count;
}
public List<Vector3> FindCooperativePath(Vector3 start, Vector3 target, GameObject requester = null)
{
// 生成缓存键
string cacheKey = GenerateCacheKey(start, target, requester);
// 检查缓存
if (pathCache.ContainsKey(cacheKey))
{
return new List<Vector3>(pathCache[cacheKey]);
}
// 执行协作A*搜索
List<Vector3> path = PerformCooperativeAStar(start, target, requester);
// 缓存结果
if (path != null && path.Count > 0)
{
pathCache[cacheKey] = new List<Vector3>(path);
// 限制缓存大小
if (pathCache.Count > 100)
{
RemoveOldestCacheEntry();
}
}
return path;
}
private List<Vector3> PerformCooperativeAStar(Vector3 start, Vector3 target, GameObject requester)
{
System.Diagnostics.Stopwatch timer = System.Diagnostics.Stopwatch.StartNew();
// 获取起始和目标节点
CooperativePathNode startNode = GetOrCreateNodeAtPosition(start);
CooperativePathNode targetNode = GetOrCreateNodeAtPosition(target);
if (startNode == null || targetNode == null || !startNode.isWalkable || !targetNode.isWalkable)
{
Debug.LogWarning("起始点或目标点不可行走");
return null;
}
// 初始化开放和关闭列表
List<CooperativePathNode> openSet = new List<CooperativePathNode>();
HashSet<CooperativePathNode> closedSet = new HashSet<CooperativePathNode>();
startNode.gCost = 0;
startNode.hCost = CalculateHeuristic(startNode, targetNode);
openSet.Add(startNode);
int iterations = 0;
while (openSet.Count > 0 && iterations < settings.maxIterations)
{
iterations++;
// 获取当前代价最低的节点
CooperativePathNode currentNode = openSet[0];
for (int i = 1; i < openSet.Count; i++)
{
float currentFCost = currentNode.GetAdjustedFCost(
settings.groupAvoidanceWeight,
settings.tacticalAdvantageWeight
);
float compareFCost = openSet[i].GetAdjustedFCost(
settings.groupAvoidanceWeight,
settings.tacticalAdvantageWeight
);
if (compareFCost < currentFCost ||
(Mathf.Approximately(compareFCost, currentFCost) &&
openSet[i].hCost < currentNode.hCost))
{
currentNode = openSet[i];
}
}
// 检查是否到达目标
if (currentNode == targetNode)
{
timer.Stop();
Debug.Log($"路径找到,迭代次数: {iterations}, 耗时: {timer.ElapsedMilliseconds}ms");
return RetracePath(startNode, targetNode);
}
// 移动到关闭列表
openSet.Remove(currentNode);
closedSet.Add(currentNode);
// 检查邻居节点
List<CooperativePathNode> neighbors = GetNeighbors(currentNode);
foreach (CooperativePathNode neighbor in neighbors)
{
if (!neighbor.isWalkable || closedSet.Contains(neighbor))
{
continue;
}
// 计算新的gCost
float movementCost = currentNode.gCost +
GetDistance(currentNode, neighbor) *
GetTerrainCost(neighbor);
// 考虑请求者的特殊需求
if (requester != null && memberPositions.ContainsKey(requester))
{
movementCost += GetRequesterSpecificCost(requester, neighbor);
}
if (movementCost < neighbor.gCost || !openSet.Contains(neighbor))
{
neighbor.gCost = movementCost;
neighbor.hCost = CalculateHeuristic(neighbor, targetNode);
neighbor.parent = currentNode;
if (!openSet.Contains(neighbor))
{
openSet.Add(neighbor);
}
}
}
// 检查超时
if (timer.Elapsed.TotalSeconds > settings.planningTimeout)
{
Debug.LogWarning("路径规划超时");
break;
}
}
timer.Stop();
Debug.LogWarning($"路径未找到,迭代次数: {iterations}, 耗时: {timer.ElapsedMilliseconds}ms");
return null;
}
private float GetDistance(CooperativePathNode a, CooperativePathNode b)
{
// 欧几里得距离
return Vector3.Distance(a.position, b.position);
}
private float GetTerrainCost(CooperativePathNode node)
{
// 地形代价(坡度、类型等)
// 简化处理:检查坡度
float slopeCost = 1.0f;
// 检查节点周围的高度变化
Vector3[] checkDirections = {
Vector3.forward, Vector3.back,
Vector3.left, Vector3.right
};
foreach (Vector3 dir in checkDirections)
{
Vector3 checkPos = node.position + dir * gridCellSize;
CooperativePathNode neighbor = GetNodeAtPosition(checkPos);
if (neighbor != null)
{
float heightDiff = Mathf.Abs(node.position.y - neighbor.position.y);
float slope = Mathf.Atan(heightDiff / gridCellSize) * Mathf.Rad2Deg;
if (slope > settings.maxSlopeAngle)
{
return float.MaxValue; // 不可通过
}
slopeCost += heightDiff * 0.5f;
}
}
return slopeCost;
}
private float GetRequesterSpecificCost(GameObject requester, CooperativePathNode node)
{
// 根据请求者的特殊需求调整代价
float specificCost = 0;
// 示例:如果请求者是狙击手,偏好高处
if (requester.CompareTag("Sniper"))
{
// 高处有优势
specificCost -= node.position.y * 0.1f;
}
// 示例:如果请求者是坦克,偏好平坦地形
else if (requester.CompareTag("Tank"))
{
// 检查地形平坦度
float flatnessPenalty = CalculateTerrainRoughness(node.position);
specificCost += flatnessPenalty * 0.2f;
}
return specificCost;
}
private float CalculateTerrainRoughness(Vector3 position)
{
// 计算地形粗糙度
// 简化处理:随机值
return Random.Range(0f, 1f);
}
private float CalculateHeuristic(CooperativePathNode a, CooperativePathNode b)
{
// 使用对角线距离作为启发函数
float dx = Mathf.Abs(a.position.x - b.position.x);
float dz = Mathf.Abs(a.position.z - b.position.z);
return (dx + dz) + (Mathf.Sqrt(2) - 2) * Mathf.Min(dx, dz);
}
private List<CooperativePathNode> GetNeighbors(CooperativePathNode node)
{
List<CooperativePathNode> neighbors = new List<CooperativePathNode>();
// 8方向邻居
for (int x = -1; x <= 1; x++)
{
for (int z = -1; z <= 1; z++)
{
if (x == 0 && z == 0)
{
continue;
}
Vector3 neighborPos = node.position +
new Vector3(x * gridCellSize, 0, z * gridCellSize);
CooperativePathNode neighbor = GetOrCreateNodeAtPosition(neighborPos);
if (neighbor != null && neighbor.isWalkable)
{
neighbors.Add(neighbor);
}
}
}
return neighbors;
}
private List<Vector3> RetracePath(CooperativePathNode startNode, CooperativePathNode endNode)
{
List<Vector3> path = new List<Vector3>();
CooperativePathNode currentNode = endNode;
while (currentNode != startNode)
{
path.Add(currentNode.position);
currentNode = currentNode.parent;
if (currentNode == null)
{
break;
}
}
path.Reverse();
// 简化路径(移除不必要的中间点)
path = SimplifyPath(path);
return path;
}
private List<Vector3> SimplifyPath(List<Vector3> path)
{
if (path.Count <= 2)
{
return path;
}
List<Vector3> simplified = new List<Vector3>();
simplified.Add(path[0]);
for (int i = 1; i < path.Count - 1; i++)
{
Vector3 prevDir = (path[i] - simplified[simplified.Count - 1]).normalized;
Vector3 nextDir = (path[i + 1] - path[i]).normalized;
// 如果方向变化超过阈值,保留该点
float angle = Vector3.Angle(prevDir, nextDir);
if (angle > 5f) // 5度阈值
{
simplified.Add(path[i]);
}
}
simplified.Add(path[path.Count - 1]);
return simplified;
}
private CooperativePathNode GetOrCreateNodeAtPosition(Vector3 position)
{
Vector3Int gridCoord = WorldToGridCoordinate(position);
if (gridNodes.ContainsKey(gridCoord))
{
return gridNodes[gridCoord];
}
// 创建新节点
CooperativePathNode newNode = new CooperativePathNode(position);
// 检查是否可行走
newNode.isWalkable = IsPositionWalkable(position);
gridNodes[gridCoord] = newNode;
// 更新网格边界
UpdateGridBounds(position);
return newNode;
}
private CooperativePathNode GetNodeAtPosition(Vector3 position)
{
Vector3Int gridCoord = WorldToGridCoordinate(position);
if (gridNodes.ContainsKey(gridCoord))
{
return gridNodes[gridCoord];
}
return null;
}
private Vector3Int WorldToGridCoordinate(Vector3 worldPosition)
{
int x = Mathf.RoundToInt(worldPosition.x / gridCellSize);
int y = Mathf.RoundToInt(worldPosition.y / gridCellSize);
int z = Mathf.RoundToInt(worldPosition.z / gridCellSize);
return new Vector3Int(x, y, z);
}
private bool IsPositionWalkable(Vector3 position)
{
// 检查地面
float groundCheckDistance = 1.0f;
RaycastHit groundHit;
bool hasGround = Physics.Raycast(position + Vector3.up * 0.5f,
Vector3.down, out groundHit, groundCheckDistance, settings.walkableMask);
if (!hasGround)
{
return false;
}
// 检查障碍物
float obstacleCheckRadius = gridCellSize * 0.5f;
Collider[] obstacles = Physics.OverlapSphere(position,
obstacleCheckRadius, settings.obstacleMask);
return obstacles.Length == 0;
}
private void UpdateGridBounds(Vector3 position)
{
if (gridNodes.Count == 1)
{
gridBounds = new Bounds(position, Vector3.one * gridCellSize);
}
else
{
gridBounds.Encapsulate(position);
}
}
private string GenerateCacheKey(Vector3 start, Vector3 target, GameObject requester)
{
// 生成基于起点、目标和请求者的缓存键
Vector3Int startGrid = WorldToGridCoordinate(start);
Vector3Int targetGrid = WorldToGridCoordinate(target);
string requesterId = requester != null ? requester.GetInstanceID().ToString() : "null";
return $"{startGrid.x},{startGrid.z}|{targetGrid.x},{targetGrid.z}|{requesterId}";
}
private void RemoveOldestCacheEntry()
{
// 移除最旧的缓存条目(简化:移除第一个)
if (pathCache.Count > 0)
{
string firstKey = pathCache.Keys.First();
pathCache.Remove(firstKey);
}
}
private void OnDrawGizmos()
{
if (!drawGridGizmos || gridNodes == null)
{
return;
}
// 绘制网格
foreach (KeyValuePair<Vector3Int, CooperativePathNode> entry in gridNodes)
{
CooperativePathNode node = entry.Value;
Gizmos.color = node.isWalkable ? walkableColor : obstacleColor;
Gizmos.DrawWireCube(node.position, Vector3.one * gridCellSize * 0.8f);
// 绘制代价信息
#if UNITY_EDITOR
if (node.groupPenalty > 0)
{
UnityEditor.Handles.Label(node.position + Vector3.up * 0.2f,
$"P: {node.groupPenalty:F2}");
}
#endif
}
// 绘制网格边界
if (gridNodes.Count > 0)
{
Gizmos.color = Color.white;
Gizmos.DrawWireCube(gridBounds.center, gridBounds.size);
}
}
public void DrawPathGizmos(List<Vector3> path)
{
if (!drawPathGizmos || path == null || path.Count < 2)
{
return;
}
Gizmos.color = Color.yellow;
for (int i = 0; i < path.Count - 1; i++)
{
Gizmos.DrawLine(path[i], path[i + 1]);
Gizmos.DrawSphere(path[i], 0.2f);
}
if (path.Count > 0)
{
Gizmos.DrawSphere(path[path.Count - 1], 0.2f);
}
}
}
// 协作路径使用者
public class CooperativePathUser : MonoBehaviour
{
[Header("路径设置")]
public CooperativeAStarPlanner pathPlanner;
public float pathUpdateInterval = 1.0f;
public float waypointArrivalThreshold = 0.5f;
[Header("移动设置")]
public float moveSpeed = 5f;
public float rotationSpeed = 120f;
// 路径状态
private List<Vector3> currentPath;
private int currentWaypointIndex;
private float pathUpdateTimer;
private Vector3 currentTarget;
private void Start()
{
if (pathPlanner != null)
{
pathPlanner.RegisterGroupMember(gameObject);
}
pathUpdateTimer = pathUpdateInterval;
currentPath = new List<Vector3>();
}
private void Update()
{
if (pathPlanner == null || currentPath == null)
{
return;
}
// 更新路径计时器
pathUpdateTimer -= Time.deltaTime;
if (pathUpdateTimer <= 0)
{
UpdatePathToRandomTarget();
pathUpdateTimer = pathUpdateInterval;
}
// 跟随路径
FollowCurrentPath();
}
private void UpdatePathToRandomTarget()
{
// 生成随机目标
Vector3 randomTarget = transform.position +
new Vector3(
Random.Range(-20f, 20f),
0,
Random.Range(-20f, 20f)
);
// 请求协作路径
currentPath = pathPlanner.FindCooperativePath(transform.position, randomTarget, gameObject);
if (currentPath != null && currentPath.Count > 0)
{
currentWaypointIndex = 0;
currentTarget = currentPath[currentWaypointIndex];
Debug.Log($"新路径生成,包含 {currentPath.Count} 个航点");
}
else
{
Debug.LogWarning("无法生成路径");
}
}
private void FollowCurrentPath()
{
if (currentPath == null || currentPath.Count == 0 ||
currentWaypointIndex >= currentPath.Count)
{
return;
}
// 检查是否到达当前航点
Vector3 toTarget = currentTarget - transform.position;
float distance = toTarget.magnitude;
if (distance <= waypointArrivalThreshold)
{
// 移动到下一个航点
currentWaypointIndex++;
if (currentWaypointIndex < currentPath.Count)
{
currentTarget = currentPath[currentWaypointIndex];
}
else
{
// 到达路径终点
Debug.Log("到达路径终点");
currentPath.Clear();
return;
}
}
// 移动到当前航点
MoveToTarget(currentTarget, toTarget.normalized);
}
private void MoveToTarget(Vector3 target, Vector3 direction)
{
// 应用移动
transform.position = Vector3.MoveTowards(
transform.position,
target,
moveSpeed * Time.deltaTime
);
// 平滑旋转
if (direction != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
}
public void SetTarget(Vector3 target)
{
if (pathPlanner != null)
{
currentPath = pathPlanner.FindCooperativePath(transform.position, target, gameObject);
if (currentPath != null && currentPath.Count > 0)
{
currentWaypointIndex = 0;
currentTarget = currentPath[currentWaypointIndex];
}
}
}
private void OnDestroy()
{
if (pathPlanner != null)
{
pathPlanner.UnregisterGroupMember(gameObject);
}
}
private void OnDrawGizmosSelected()
{
if (currentPath != null && currentPath.Count > 0)
{
// 绘制当前路径
Gizmos.color = Color.cyan;
for (int i = 0; i < currentPath.Count - 1; i++)
{
Gizmos.DrawLine(currentPath[i], currentPath[i + 1]);
Gizmos.DrawSphere(currentPath[i], 0.2f);
}
Gizmos.DrawSphere(currentPath[currentPath.Count - 1], 0.2f);
// 绘制当前目标
if (currentWaypointIndex < currentPath.Count)
{
Gizmos.color = Color.red;
Gizmos.DrawSphere(currentTarget, 0.3f);
Gizmos.DrawLine(transform.position, currentTarget);
}
}
}
}
}
6.3.3 A*算法的战术扩展:伏击路径规划
伏击路径规划是协作A*算法的一个重要扩展,专门用于寻找适合伏击的路径:
// 伏击路径规划器
public class AmbushPathPlanner : CooperativeAStarPlanner
{
[System.Serializable]
public class AmbushSettings
{
public float ambushPreference = 0.7f; // 伏击偏好度 (0-1)
public float visibilityPenalty = 2.0f; // 可见性惩罚
public float coverBonus = 0.5f; // 掩体奖励
public float elevationBonus = 0.3f; // 高度奖励
public float flankingBonus = 0.8f; // 侧翼攻击奖励
public float surpriseBonus = 1.0f; // 突袭奖励
}
public AmbushSettings ambushSettings;
// 目标信息
private Vector3 enemyPosition;
private Vector3 enemyForward;
private bool hasEnemyTarget = false;
public void SetEnemyTarget(Vector3 position, Vector3 forward)
{
enemyPosition = position;
enemyForward = forward.normalized;
hasEnemyTarget = true;
}
protected override void UpdateNodeTacticalValue(CooperativePathNode node)
{
base.UpdateNodeTacticalValue(node);
if (!hasEnemyTarget)
{
return;
}
// 计算伏击相关的战术价值
float ambushValue = CalculateAmbushValue(node.position);
// 调整战术价值(值越低越好)
node.tacticalValue *= (1.0f + ambushValue * ambushSettings.ambushPreference);
}
private float CalculateAmbushValue(Vector3 position)
{
float totalValue = 0f;
// 1. 可见性惩罚(在敌人视线内不好)
if (IsVisibleFromEnemy(position))
{
totalValue += ambushSettings.visibilityPenalty;
}
// 2. 掩体奖励(有掩体好)
float coverValue = CalculateCoverFromEnemy(position);
totalValue -= coverValue * ambushSettings.coverBonus;
// 3. 高度奖励(高处有利)
float elevationAdvantage = CalculateElevationAdvantage(position);
totalValue -= elevationAdvantage * ambushSettings.elevationBonus;
// 4. 侧翼奖励(从侧面攻击有利)
float flankingValue = CalculateFlankingValue(position);
totalValue -= flankingValue * ambushSettings.flankingBonus;
// 5. 突袭奖励(从背后攻击有利)
float surpriseValue = CalculateSurpriseValue(position);
totalValue -= surpriseValue * ambushSettings.surpriseBonus;
return totalValue;
}
private bool IsVisibleFromEnemy(Vector3 position)
{
// 简化的可见性检查
Vector3 toPosition = position - enemyPosition;
float distance = toPosition.magnitude;
if (distance > 50f) // 最大可见距离
{
return false;
}
// 检查是否在敌人视野锥形内
float angle = Vector3.Angle(enemyForward, toPosition.normalized);
if (angle > 90f) // 90度视野
{
return false;
}
// 检查视线遮挡
RaycastHit hit;
if (Physics.Raycast(enemyPosition, toPosition.normalized, out hit, distance))
{
if (hit.point != position)
{
return false; // 有遮挡
}
}
return true;
}
private float CalculateCoverFromEnemy(Vector3 position)
{
// 计算从敌人方向看来的掩体保护
Vector3 fromEnemy = position - enemyPosition;
float distance = fromEnemy.magnitude;
if (distance < 1f)
{
return 0f;
}
Vector3 direction = fromEnemy.normalized;
float coverValue = 0f;
// 检查周围的掩体
Vector3[] checkOffsets = {
Vector3.up * 0.5f,
Vector3.right * 0.5f,
Vector3.left * 0.5f,
Vector3.forward * 0.5f,
Vector3.back * 0.5f
};
foreach (Vector3 offset in checkOffsets)
{
Vector3 checkPos = position + offset;
Vector3 toCheck = checkPos - enemyPosition;
RaycastHit hit;
if (Physics.Raycast(enemyPosition, toCheck.normalized, out hit, distance * 1.2f))
{
// 检查击中点是否接近检查位置
float hitDistance = Vector3.Distance(hit.point, checkPos);
if (hitDistance < 0.5f)
{
coverValue += 1.0f;
}
}
}
return Mathf.Clamp01(coverValue / checkOffsets.Length);
}
private float CalculateElevationAdvantage(Vector3 position)
{
// 计算相对于敌人的高度优势
float heightDifference = position.y - enemyPosition.y;
if (heightDifference > 0)
{
return Mathf.Clamp01(heightDifference / 10f); // 10米最大优势
}
return 0f;
}
private float CalculateFlankingValue(Vector3 position)
{
// 计算侧翼攻击价值
Vector3 toEnemy = enemyPosition - position;
Vector3 enemyRight = Vector3.Cross(enemyForward, Vector3.up);
// 计算位置相对于敌人侧面的角度
float rightDot = Vector3.Dot(toEnemy.normalized, enemyRight);
float flankAngle = Mathf.Abs(Mathf.Acos(rightDot) * Mathf.Rad2Deg - 90f);
// 角度越小(接近90度),侧翼价值越高
return Mathf.Clamp01(1.0f - flankAngle / 90f);
}
private float CalculateSurpriseValue(Vector3 position)
{
// 计算突袭价值(从背后攻击)
Vector3 toEnemy = enemyPosition - position;
float forwardDot = Vector3.Dot(toEnemy.normalized, enemyForward);
// 如果在敌人后方(forwardDot < 0),则有突袭价值
if (forwardDot < 0)
{
return Mathf.Clamp01(Mathf.Abs(forwardDot));
}
return 0f;
}
public List<Vector3> FindAmbushPath(Vector3 start, Vector3 enemyPos, Vector3 enemyFwd)
{
SetEnemyTarget(enemyPos, enemyFwd);
// 在敌人周围寻找最佳伏击位置
Vector3 ambushTarget = FindBestAmbushPosition(enemyPos);
// 寻找路径
return FindCooperativePath(start, ambushTarget);
}
private Vector3 FindBestAmbushPosition(Vector3 enemyPos)
{
// 在敌人周围搜索最佳伏击位置
float searchRadius = 30f;
int sampleCount = 20;
Vector3 bestPosition = enemyPos + Vector3.back * 10f; // 默认位置
float bestScore = float.MinValue;
for (int i = 0; i < sampleCount; i++)
{
// 在圆周上采样
float angle = (i * 360f / sampleCount) * Mathf.Deg2Rad;
float distance = searchRadius * Random.Range(0.7f, 1.3f);
Vector3 samplePos = enemyPos +
new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * distance;
// 确保位置可行走
if (!IsPositionWalkable(samplePos))
{
continue;
}
// 计算伏击分数
float score = CalculateAmbushPositionScore(samplePos);
if (score > bestScore)
{
bestScore = score;
bestPosition = samplePos;
}
}
return bestPosition;
}
private float CalculateAmbushPositionScore(Vector3 position)
{
if (!hasEnemyTarget)
{
return 0f;
}
float score = 0f;
// 负分项:可见性
if (IsVisibleFromEnemy(position))
{
score -= 10f;
}
// 正分项:掩体
float coverValue = CalculateCoverFromEnemy(position);
score += coverValue * 5f;
// 正分项:高度优势
float elevationAdvantage = CalculateElevationAdvantage(position);
score += elevationAdvantage * 3f;
// 正分项:侧翼位置
float flankingValue = CalculateFlankingValue(position);
score += flankingValue * 4f;
// 正分项:突袭位置
float surpriseValue = CalculateSurpriseValue(position);
score += surpriseValue * 6f;
// 距离惩罚(太近或太远都不好)
float distance = Vector3.Distance(position, enemyPosition);
float idealDistance = 15f;
float distancePenalty = Mathf.Abs(distance - idealDistance) / idealDistance;
score -= distancePenalty * 2f;
return score;
}
}
6.4 地形高度分析与路径点优化
地形高度对战术决策有重要影响。高处通常提供更好的视野和防御优势,而低处可能提供更好的隐蔽性。本节将实现一个高度分析系统,用于评估路径点的战术价值。
6.4.1 高度分析系统实现
// 地形高度分析器
public class TerrainHeightAnalyzer : MonoBehaviour
{
[System.Serializable]
public class HeightAnalysisSettings
{
public float analysisRadius = 50f;
public float gridResolution = 2f;
public LayerMask terrainMask = -1;
public AnimationCurve heightImportanceCurve = AnimationCurve.Linear(0, 0, 1, 1);
public float maxHeightDifference = 20f;
}
public HeightAnalysisSettings settings;
// 高度网格数据
private Dictionary<Vector2Int, float> heightGrid;
private Dictionary<Vector2Int, float> tacticalHeightValue;
private Bounds analysisBounds;
private void Start()
{
InitializeHeightGrid();
AnalyzeTerrainHeights();
}
private void InitializeHeightGrid()
{
heightGrid = new Dictionary<Vector2Int, float>();
tacticalHeightValue = new Dictionary<Vector2Int, float>();
Vector3 position = transform.position;
analysisBounds = new Bounds(position, Vector3.one * settings.analysisRadius * 2);
}
public void AnalyzeTerrainHeights()
{
// 在分析范围内采样高度
float halfSize = settings.analysisRadius;
int gridSize = Mathf.CeilToInt((halfSize * 2) / settings.gridResolution);
for (int x = 0; x < gridSize; x++)
{
for (int y = 0; y < gridSize; y++)
{
Vector3 worldPos = new Vector3(
transform.position.x - halfSize + x * settings.gridResolution,
100f, // 起始高度
transform.position.z - halfSize + y * settings.gridResolution
);
Vector2Int gridCoord = new Vector2Int(x, y);
// 射线检测获取地形高度
RaycastHit hit;
if (Physics.Raycast(worldPos, Vector3.down, out hit, 200f, settings.terrainMask))
{
heightGrid[gridCoord] = hit.point.y;
// 计算战术高度价值
tacticalHeightValue[gridCoord] = CalculateTacticalHeightValue(gridCoord);
}
else
{
heightGrid[gridCoord] = 0f;
tacticalHeightValue[gridCoord] = 0f;
}
}
}
Debug.Log($"地形高度分析完成,采样点: {heightGrid.Count}");
}
private float CalculateTacticalHeightValue(Vector2Int gridCoord)
{
float baseHeight = heightGrid[gridCoord];
float relativeHeight = 0f;
int sampleCount = 0;
// 采样周围点计算相对高度
for (int dx = -2; dx <= 2; dx++)
{
for (int dy = -2; dy <= 2; dy++)
{
if (dx == 0 && dy == 0) continue;
Vector2Int neighborCoord = new Vector2Int(gridCoord.x + dx, gridCoord.y + dy);
if (heightGrid.ContainsKey(neighborCoord))
{
float neighborHeight = heightGrid[neighborCoord];
relativeHeight += (baseHeight - neighborHeight);
sampleCount++;
}
}
}
if (sampleCount > 0)
{
relativeHeight /= sampleCount;
}
// 标准化并应用重要性曲线
float normalizedHeight = Mathf.Clamp01(relativeHeight / settings.maxHeightDifference);
float tacticalValue = settings.heightImportanceCurve.Evaluate(normalizedHeight);
return tacticalValue;
}
public float GetHeightAtPosition(Vector3 position)
{
Vector2Int gridCoord = WorldToGridCoordinate(position);
if (heightGrid.ContainsKey(gridCoord))
{
return heightGrid[gridCoord];
}
return position.y;
}
public float GetTacticalHeightValue(Vector3 position)
{
Vector2Int gridCoord = WorldToGridCoordinate(position);
if (tacticalHeightValue.ContainsKey(gridCoord))
{
return tacticalHeightValue[gridCoord];
}
return 0.5f; // 默认值
}
public Vector3 GetHighestPointInRadius(Vector3 center, float radius)
{
Vector3 highestPoint = center;
float highestHeight = GetHeightAtPosition(center);
int sampleCount = 20;
for (int i = 0; i < sampleCount; i++)
{
float angle = (i * 360f / sampleCount) * Mathf.Deg2Rad;
Vector3 samplePos = center +
new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * radius;
float sampleHeight = GetHeightAtPosition(samplePos);
if (sampleHeight > highestHeight)
{
highestHeight = sampleHeight;
highestPoint = samplePos;
highestPoint.y = sampleHeight;
}
}
return highestPoint;
}
public Vector3 GetBestHighGround(Vector3 center, float searchRadius, float minHeightAdvantage = 5f)
{
List<Vector3> highPoints = new List<Vector3>();
// 寻找所有足够高的点
int sampleCount = 30;
float centerHeight = GetHeightAtPosition(center);
for (int i = 0; i < sampleCount; i++)
{
float angle = (i * 360f / sampleCount) * Mathf.Deg2Rad;
float distance = searchRadius * Random.Range(0.5f, 1f);
Vector3 samplePos = center +
new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * distance;
float sampleHeight = GetHeightAtPosition(samplePos);
if (sampleHeight - centerHeight >= minHeightAdvantage)
{
samplePos.y = sampleHeight;
highPoints.Add(samplePos);
}
}
if (highPoints.Count == 0)
{
return center;
}
// 选择战术价值最高的点
Vector3 bestPoint = highPoints[0];
float bestValue = GetTacticalHeightValue(bestPoint);
for (int i = 1; i < highPoints.Count; i++)
{
float value = GetTacticalHeightValue(highPoints[i]);
if (value > bestValue)
{
bestValue = value;
bestPoint = highPoints[i];
}
}
return bestPoint;
}
private Vector2Int WorldToGridCoordinate(Vector3 worldPosition)
{
Vector3 localPos = worldPosition - (transform.position - Vector3.one * settings.analysisRadius);
int x = Mathf.FloorToInt(localPos.x / settings.gridResolution);
int y = Mathf.FloorToInt(localPos.z / settings.gridResolution);
return new Vector2Int(x, y);
}
private void OnDrawGizmosSelected()
{
if (heightGrid == null || heightGrid.Count == 0)
{
return;
}
// 绘制高度网格
foreach (KeyValuePair<Vector2Int, float> entry in heightGrid)
{
Vector3 worldPos = GridToWorldPosition(entry.Key);
worldPos.y = entry.Value;
// 根据高度值着色
float normalizedHeight = Mathf.Clamp01(entry.Value / 100f);
Color heightColor = Color.Lerp(Color.blue, Color.red, normalizedHeight);
Gizmos.color = heightColor;
Gizmos.DrawCube(worldPos, Vector3.one * settings.gridResolution * 0.8f);
}
// 绘制分析边界
Gizmos.color = Color.white;
Gizmos.DrawWireCube(analysisBounds.center, analysisBounds.size);
}
private Vector3 GridToWorldPosition(Vector2Int gridCoord)
{
return new Vector3(
transform.position.x - settings.analysisRadius + gridCoord.x * settings.gridResolution,
0,
transform.position.z - settings.analysisRadius + gridCoord.y * settings.gridResolution
);
}
}
6.4.2 高度感知路径点生成器
// 高度感知路径点生成器
public class HeightAwareWaypointGenerator : MonoBehaviour
{
[System.Serializable]
public class WaypointGenerationSettings
{
public float generationRadius = 100f;
public float waypointDensity = 0.1f; // 每平方米路径点数
public float minWaypointDistance = 2f;
public float maxWaypointDistance = 10f;
public LayerMask groundMask = -1;
public LayerMask obstacleMask = -1;
// 高度相关设置
public float preferredHeightRange = 10f;
public float heightVariation = 5f;
public bool prioritizeHighGround = true;
public float highGroundBonus = 2f;
}
public WaypointGenerationSettings settings;
public TerrainHeightAnalyzer heightAnalyzer;
private List<Vector3> generatedWaypoints;
private List<float> waypointTacticalValues;
private void Start()
{
generatedWaypoints = new List<Vector3>();
waypointTacticalValues = new List<float>();
if (heightAnalyzer == null)
{
heightAnalyzer = GetComponent<TerrainHeightAnalyzer>();
}
GenerateWaypoints();
}
private void GenerateWaypoints()
{
// 计算需要生成的路径点数量
float area = Mathf.PI * settings.generationRadius * settings.generationRadius;
int targetCount = Mathf.CeilToInt(area * settings.waypointDensity);
int attempts = 0;
int maxAttempts = targetCount * 10;
while (generatedWaypoints.Count < targetCount && attempts < maxAttempts)
{
attempts++;
// 生成随机位置
Vector3 randomPos = GenerateRandomPosition();
// 检查位置有效性
if (IsValidWaypointPosition(randomPos))
{
// 计算战术价值
float tacticalValue = CalculateWaypointTacticalValue(randomPos);
generatedWaypoints.Add(randomPos);
waypointTacticalValues.Add(tacticalValue);
}
}
// 优化路径点分布
OptimizeWaypointDistribution();
Debug.Log($"生成 {generatedWaypoints.Count} 个路径点");
}
private Vector3 GenerateRandomPosition()
{
// 在圆形区域内生成随机位置
float angle = Random.Range(0f, 360f) * Mathf.Deg2Rad;
float distance = Random.Range(0f, settings.generationRadius);
Vector3 position = transform.position +
new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * distance;
// 获取地形高度
position.y = GetGroundHeight(position);
return position;
}
private float GetGroundHeight(Vector3 position)
{
RaycastHit hit;
if (Physics.Raycast(position + Vector3.up * 100f, Vector3.down, out hit, 200f, settings.groundMask))
{
return hit.point.y;
}
return position.y;
}
private bool IsValidWaypointPosition(Vector3 position)
{
// 检查是否在地面上
if (Mathf.Abs(position.y - GetGroundHeight(position)) > 0.1f)
{
return false;
}
// 检查障碍物
Collider[] obstacles = Physics.OverlapSphere(position, 0.5f, settings.obstacleMask);
if (obstacles.Length > 0)
{
return false;
}
// 检查与其他路径点的最小距离
foreach (Vector3 existingWaypoint in generatedWaypoints)
{
if (Vector3.Distance(position, existingWaypoint) < settings.minWaypointDistance)
{
return false;
}
}
// 检查坡度(简化)
float maxSlope = 45f;
if (CheckSlopeTooSteep(position, maxSlope))
{
return false;
}
return true;
}
private bool CheckSlopeTooSteep(Vector3 position, float maxSlopeDegrees)
{
// 简化的坡度检查
float checkDistance = 1f;
Vector3[] checkDirections = {
Vector3.forward, Vector3.back,
Vector3.left, Vector3.right
};
foreach (Vector3 dir in checkDirections)
{
Vector3 checkPos = position + dir * checkDistance;
float checkHeight = GetGroundHeight(checkPos);
float heightDiff = Mathf.Abs(checkHeight - position.y);
float slope = Mathf.Atan(heightDiff / checkDistance) * Mathf.Rad2Deg;
if (slope > maxSlopeDegrees)
{
return true;
}
}
return false;
}
private float CalculateWaypointTacticalValue(Vector3 position)
{
float value = 1.0f;
// 高度价值
if (heightAnalyzer != null)
{
float heightValue = heightAnalyzer.GetTacticalHeightValue(position);
value *= heightValue;
if (settings.prioritizeHighGround)
{
value += heightValue * settings.highGroundBonus;
}
}
// 掩体价值
float coverValue = CalculateCoverValue(position);
value *= (1.0f + coverValue);
// 可见性价值(越低越好)
float visibility = CalculateVisibility(position);
value /= (1.0f + visibility * 0.5f);
return value;
}
private float CalculateCoverValue(Vector3 position)
{
// 简化的掩体价值计算
float coverValue = 0f;
float checkRadius = 3f;
Collider[] nearbyObjects = Physics.OverlapSphere(position, checkRadius);
foreach (Collider collider in nearbyObjects)
{
// 检查是否可以作为掩体
if (collider.gameObject.CompareTag("Cover"))
{
float distance = Vector3.Distance(position, collider.transform.position);
coverValue += 1.0f / (distance + 0.1f);
}
}
return Mathf.Clamp01(coverValue);
}
private float CalculateVisibility(Vector3 position)
{
// 简化的可见性计算
int rayCount = 12;
float maxDistance = 20f;
float visibleCount = 0;
for (int i = 0; i < rayCount; i++)
{
float angle = i * (360f / rayCount) * Mathf.Deg2Rad;
Vector3 direction = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle));
RaycastHit hit;
if (!Physics.Raycast(position, direction, out hit, maxDistance, settings.obstacleMask))
{
visibleCount++;
}
}
return visibleCount / rayCount;
}
private void OptimizeWaypointDistribution()
{
// 移除过于密集的路径点
List<int> indicesToRemove = new List<int>();
for (int i = 0; i < generatedWaypoints.Count; i++)
{
for (int j = i + 1; j < generatedWaypoints.Count; j++)
{
float distance = Vector3.Distance(generatedWaypoints[i], generatedWaypoints[j]);
if (distance < settings.minWaypointDistance)
{
// 保留战术价值更高的路径点
if (waypointTacticalValues[i] < waypointTacticalValues[j])
{
indicesToRemove.Add(i);
}
else
{
indicesToRemove.Add(j);
}
}
}
}
// 移除标记的路径点
indicesToRemove.Sort();
indicesToRemove.Reverse();
foreach (int index in indicesToRemove)
{
if (index < generatedWaypoints.Count)
{
generatedWaypoints.RemoveAt(index);
waypointTacticalValues.RemoveAt(index);
}
}
}
public Vector3 GetBestWaypointNear(Vector3 position, float searchRadius)
{
Vector3 bestWaypoint = position;
float bestValue = -1f;
for (int i = 0; i < generatedWaypoints.Count; i++)
{
float distance = Vector3.Distance(position, generatedWaypoints[i]);
if (distance <= searchRadius)
{
// 考虑距离的战术价值
float distanceFactor = 1.0f - (distance / searchRadius);
float combinedValue = waypointTacticalValues[i] * (1.0f + distanceFactor * 0.5f);
if (combinedValue > bestValue)
{
bestValue = combinedValue;
bestWaypoint = generatedWaypoints[i];
}
}
}
return bestWaypoint;
}
public List<Vector3> GetWaypointsInArea(Vector3 center, float radius)
{
List<Vector3> waypointsInArea = new List<Vector3>();
foreach (Vector3 waypoint in generatedWaypoints)
{
if (Vector3.Distance(center, waypoint) <= radius)
{
waypointsInArea.Add(waypoint);
}
}
return waypointsInArea;
}
public void AddDynamicWaypoint(Vector3 position)
{
if (IsValidWaypointPosition(position))
{
float tacticalValue = CalculateWaypointTacticalValue(position);
generatedWaypoints.Add(position);
waypointTacticalValues.Add(tacticalValue);
}
}
public void RemoveWaypoint(Vector3 position, float tolerance = 0.5f)
{
for (int i = generatedWaypoints.Count - 1; i >= 0; i--)
{
if (Vector3.Distance(generatedWaypoints[i], position) <= tolerance)
{
generatedWaypoints.RemoveAt(i);
waypointTacticalValues.RemoveAt(i);
break;
}
}
}
private void OnDrawGizmos()
{
if (generatedWaypoints == null)
{
return;
}
// 绘制路径点
Gizmos.color = Color.cyan;
foreach (Vector3 waypoint in generatedWaypoints)
{
Gizmos.DrawSphere(waypoint, 0.3f);
}
// 绘制生成区域
Gizmos.color = Color.white;
Gizmos.DrawWireSphere(transform.position, settings.generationRadius);
}
}
6.5 掩体与可见性分析系统
在战术决策中,掩体利用和可见性管理至关重要。一个完善的掩体分析系统能够帮助AI角色选择最佳的防御位置和攻击角度。
6.5.1 掩体分析系统实现
// 掩体分析系统
public class CoverAnalysisSystem : MonoBehaviour
{
[System.Serializable]
public class CoverAnalysisSettings
{
public float analysisRange = 50f;
public float raycastHeight = 1f; // 角色眼睛高度
public LayerMask coverMask = -1;
public LayerMask obstacleMask = -1;
public int raysPerDirection = 3;
public float coverQualityThreshold = 0.7f;
}
public CoverAnalysisSettings settings;
// 掩体数据
private Dictionary<Vector3, CoverPoint> coverPoints;
private Dictionary<GameObject, List<CoverPoint>> objectCoverMap;
// 掩体点定义
public class CoverPoint
{
public Vector3 position;
public Vector3 normal; // 掩体法线方向
public float quality; // 掩体质量 (0-1)
public CoverType type;
public GameObject sourceObject;
public CoverPoint(Vector3 pos, Vector3 norm, float qual, CoverType t, GameObject source)
{
position = pos;
normal = norm;
quality = qual;
type = t;
sourceObject = source;
}
public bool ProvidesCoverFrom(Vector3 threatPosition)
{
Vector3 toThreat = threatPosition - position;
float angle = Vector3.Angle(normal, toThreat.normalized);
// 如果威胁在掩体后方,则提供保护
return angle < 90f;
}
public float GetCoverEffectiveness(Vector3 threatPosition)
{
if (!ProvidesCoverFrom(threatPosition))
{
return 0f;
}
Vector3 toThreat = threatPosition - position;
float distance = toThreat.magnitude;
float angle = Vector3.Angle(normal, toThreat.normalized);
// 角度越小,掩体越有效
float angleEffectiveness = 1.0f - (angle / 90f);
// 距离越远,掩体越有效
float distanceEffectiveness = Mathf.Clamp01(1.0f - (distance / 100f));
return quality * angleEffectiveness * distanceEffectiveness;
}
}
public enum CoverType
{
Full, // 完全掩体
Half, // 半掩体
Low, // 低掩体
Partial // 部分掩体
}
private void Start()
{
coverPoints = new Dictionary<Vector3, CoverPoint>();
objectCoverMap = new Dictionary<GameObject, List<CoverPoint>>();
AnalyzeEnvironmentCover();
}
private void AnalyzeEnvironmentCover()
{
// 查找所有可能提供掩体的对象
Collider[] potentialCoverObjects = Physics.OverlapSphere(
transform.position,
settings.analysisRange,
settings.coverMask
);
foreach (Collider collider in potentialCoverObjects)
{
AnalyzeObjectForCover(collider.gameObject);
}
Debug.Log($"发现 {coverPoints.Count} 个掩体点");
}
private void AnalyzeObjectForCover(GameObject coverObject)
{
List<CoverPoint> objectCoverPoints = new List<CoverPoint>();
// 获取对象边界
Renderer renderer = coverObject.GetComponent<Renderer>();
if (renderer == null)
{
return;
}
Bounds bounds = renderer.bounds;
// 在对象周围采样点
Vector3[] samplePoints = GenerateSamplePointsAroundBounds(bounds);
foreach (Vector3 samplePoint in samplePoints)
{
CoverPoint coverPoint = AnalyzeCoverAtPoint(samplePoint, coverObject);
if (coverPoint != null && coverPoint.quality >= settings.coverQualityThreshold)
{
// 添加到全局掩体点
if (!coverPoints.ContainsKey(coverPoint.position))
{
coverPoints[coverPoint.position] = coverPoint;
}
// 添加到对象掩体点列表
objectCoverPoints.Add(coverPoint);
}
}
if (objectCoverPoints.Count > 0)
{
objectCoverMap[coverObject] = objectCoverPoints;
}
}
private Vector3[] GenerateSamplePointsAroundBounds(Bounds bounds)
{
List<Vector3> samplePoints = new List<Vector3>();
// 在边界盒的每个面生成采样点
Vector3 center = bounds.center;
Vector3 size = bounds.size;
// 前面和后面
for (int i = 0; i <= settings.raysPerDirection; i++)
{
for (int j = 0; j <= settings.raysPerDirection; j++)
{
float x = Mathf.Lerp(-size.x / 2, size.x / 2, i / (float)settings.raysPerDirection);
float y = Mathf.Lerp(-size.y / 2, size.y / 2, j / (float)settings.raysPerDirection);
// 前面
samplePoints.Add(center + new Vector3(x, y, size.z / 2));
// 后面
samplePoints.Add(center + new Vector3(x, y, -size.z / 2));
}
}
// 左面和右面
for (int i = 0; i <= settings.raysPerDirection; i++)
{
for (int j = 0; j <= settings.raysPerDirection; j++)
{
float z = Mathf.Lerp(-size.z / 2, size.z / 2, i / (float)settings.raysPerDirection);
float y = Mathf.Lerp(-size.y / 2, size.y / 2, j / (float)settings.raysPerDirection);
// 左面
samplePoints.Add(center + new Vector3(-size.x / 2, y, z));
// 右面
samplePoints.Add(center + new Vector3(size.x / 2, y, z));
}
}
return samplePoints.ToArray();
}
private CoverPoint AnalyzeCoverAtPoint(Vector3 point, GameObject sourceObject)
{
// 调整点到角色眼睛高度
Vector3 analysisPoint = point + Vector3.up * settings.raycastHeight;
// 向多个方向发射射线检查掩体质量
int coverHits = 0;
int totalRays = 8; // 8个方向
Vector3 averageNormal = Vector3.zero;
for (int i = 0; i < totalRays; i++)
{
float angle = i * (360f / totalRays) * Mathf.Deg2Rad;
Vector3 direction = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle));
RaycastHit hit;
if (Physics.Raycast(analysisPoint, direction, out hit, 5f, settings.coverMask))
{
if (hit.collider.gameObject == sourceObject)
{
coverHits++;
averageNormal += hit.normal;
}
}
}
if (coverHits == 0)
{
return null;
}
// 计算掩体质量
float coverQuality = coverHits / (float)totalRays;
averageNormal = (averageNormal / coverHits).normalized;
// 确定掩体类型
CoverType coverType = DetermineCoverType(coverQuality, analysisPoint);
return new CoverPoint(point, averageNormal, coverQuality, coverType, sourceObject);
}
private CoverType DetermineCoverType(float quality, Vector3 position)
{
// 根据掩体质量和位置确定类型
if (quality > 0.9f)
{
return CoverType.Full;
}
else if (quality > 0.7f)
{
// 检查是否为低掩体
RaycastHit hit;
if (Physics.Raycast(position + Vector3.up * 0.5f, Vector3.down, out hit, 2f))
{
if (hit.point.y < position.y + 1f)
{
return CoverType.Low;
}
}
return CoverType.Half;
}
else
{
return CoverType.Partial;
}
}
public CoverPoint FindBestCoverFromThreat(Vector3 unitPosition, Vector3 threatPosition, float searchRadius)
{
CoverPoint bestCover = null;
float bestScore = -1f;
foreach (CoverPoint cover in coverPoints.Values)
{
float distance = Vector3.Distance(unitPosition, cover.position);
if (distance <= searchRadius)
{
// 计算掩体有效性
float effectiveness = cover.GetCoverEffectiveness(threatPosition);
if (effectiveness > 0)
{
// 综合评分:有效性 * 距离因子
float distanceFactor = 1.0f - (distance / searchRadius);
float score = effectiveness * (1.0f + distanceFactor * 0.5f);
if (score > bestScore)
{
bestScore = score;
bestCover = cover;
}
}
}
}
return bestCover;
}
public List<CoverPoint> FindCoverInDirection(Vector3 position, Vector3 direction, float maxAngle = 45f)
{
List<CoverPoint> suitableCover = new List<CoverPoint>();
foreach (CoverPoint cover in coverPoints.Values)
{
Vector3 toCover = cover.position - position;
float angle = Vector3.Angle(direction, toCover.normalized);
if (angle <= maxAngle)
{
suitableCover.Add(cover);
}
}
// 按距离排序
suitableCover.Sort((a, b) =>
Vector3.Distance(position, a.position).CompareTo(
Vector3.Distance(position, b.position)
)
);
return suitableCover;
}
public bool IsPositionInCover(Vector3 position, Vector3 threatPosition)
{
// 检查位置是否有掩体保护
foreach (CoverPoint cover in coverPoints.Values)
{
float distance = Vector3.Distance(position, cover.position);
if (distance < 2f && cover.ProvidesCoverFrom(threatPosition))
{
return true;
}
}
return false;
}
public float CalculateCoverQualityAtPosition(Vector3 position, Vector3 threatPosition)
{
float bestQuality = 0f;
foreach (CoverPoint cover in coverPoints.Values)
{
float distance = Vector3.Distance(position, cover.position);
if (distance < 3f)
{
float effectiveness = cover.GetCoverEffectiveness(threatPosition);
if (effectiveness > bestQuality)
{
bestQuality = effectiveness;
}
}
}
return bestQuality;
}
public void UpdateDynamicCover(GameObject newCoverObject)
{
// 动态添加新的掩体
AnalyzeObjectForCover(newCoverObject);
}
public void RemoveCoverObject(GameObject coverObject)
{
// 移除掩体对象及其所有掩体点
if (objectCoverMap.ContainsKey(coverObject))
{
foreach (CoverPoint coverPoint in objectCoverMap[coverObject])
{
coverPoints.Remove(coverPoint.position);
}
objectCoverMap.Remove(coverObject);
}
}
private void OnDrawGizmosSelected()
{
if (coverPoints == null)
{
return;
}
// 绘制掩体点
foreach (CoverPoint cover in coverPoints.Values)
{
// 根据掩体质量着色
Color coverColor = Color.Lerp(Color.yellow, Color.green, cover.quality);
Gizmos.color = coverColor;
// 绘制掩体点
Gizmos.DrawSphere(cover.position, 0.2f);
// 绘制掩体法线
Gizmos.DrawRay(cover.position, cover.normal);
// 绘制掩体类型标识
switch (cover.type)
{
case CoverType.Full:
Gizmos.color = Color.green;
break;
case CoverType.Half:
Gizmos.color = Color.yellow;
break;
case CoverType.Low:
Gizmos.color = Color.blue;
break;
case CoverType.Partial:
Gizmos.color = Color.red;
break;
}
Gizmos.DrawWireCube(cover.position, Vector3.one * 0.3f);
}
// 绘制分析范围
Gizmos.color = Color.white;
Gizmos.DrawWireSphere(transform.position, settings.analysisRange);
}
}
总结
本章详细探讨了Unity中群体智能与战术决策系统的实现。从基础的队形管理到高级的协作寻路算法,从地形高度分析到掩体可见性评估,我们构建了一个完整的战术AI框架。
关键要点总结:
-
队形系统:提供了灵活的队形管理,支持多种队形类型和动态调整,确保群体移动的协调性和战术优势。
-
协作寻路:扩展的A*算法不仅考虑路径长度,还整合了群体协调、战术价值和队形保持等多种因素,为群体提供智能路径规划。
-
伏击路径规划:专门针对战术伏击设计的路径规划系统,能够识别最佳的伏击位置和路径。
-
地形分析:高度感知的路径点生成和战术位置评估,帮助AI利用地形优势。
-
掩体系统:完整的掩体分析和利用系统,使AI能够智能地利用环境进行防御和攻击。
在实际商业项目中,这些系统可以组合使用,创建出具有高度智能的AI敌人。例如,在战术射击游戏中,AI小队可以使用队形系统保持阵型,利用协作寻路找到最佳进攻路线,通过高度分析占领制高点,最后利用掩体系统进行智能攻防。
实现这些系统时需要注意性能优化,特别是在大型开放世界游戏中。可以通过空间分区、LOD系统、异步计算和智能缓存等技术来确保系统的运行效率。
通过本章介绍的技术,开发者可以为游戏创建出具有真实战术行为的AI,显著提升游戏的挑战性和沉浸感。这些系统不仅适用于军事题材游戏,也可以经过调整应用于RTS、MOBA甚至角色扮演游戏中,为玩家提供更加丰富和智能的游戏体验。
更多推荐
所有评论(0)