2025-04-09 16:40:11 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using Framework;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using Gameplay.Unit;
|
|
|
|
|
|
using Gameplay.Area;
|
|
|
|
|
|
using Gameplay.Level;
|
|
|
|
|
|
using cfg.CharacterCfg;
|
|
|
|
|
|
using Gameplay.Character;
|
|
|
|
|
|
using Gameplay.Pool;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Gameplay.AI
|
|
|
|
|
|
{
|
|
|
|
|
|
public static partial class AIUtils
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 对象池 - 用于缓存单位距离字典
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static readonly Dictionary<GameUnit, int> UnitsDistanceCache = new (64);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 临时列表,用于避免在 GetNearestUnit/GetRandomUnit 中创建新列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static readonly List<KeyValuePair<GameUnit, int>> TempPairList = new (64);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过GUID获取单位
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static GameUnit FindUnit(uint id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (GameUnitManager.instance.deadUnits.Contains(id))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
return GameUnitManager.instance.infightUnits.Contains(id)
|
|
|
|
|
|
? GameUnitManager.instance.infightUnits.Search(id)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取符合条件的单位及距离
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="owner">自身</param>
|
|
|
|
|
|
/// <param name="candidates">需要筛选的对象</param>
|
|
|
|
|
|
/// <param name="filter">条件</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
private static Dictionary<GameUnit, int> FindUnits(GameUnit owner, List<GameUnit> candidates,
|
|
|
|
|
|
IAITargetFilter filter)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 清空临时字典以便重用
|
|
|
|
|
|
UnitsDistanceCache.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
var ownerCell = owner.transData?.cellIndex;
|
|
|
|
|
|
if (!ownerCell.HasValue) return UnitsDistanceCache;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var unit in candidates)
|
|
|
|
|
|
{
|
|
|
|
|
|
var cell = unit.transData?.cellIndex;
|
|
|
|
|
|
if (!cell.HasValue) continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (filter.IsValid(unit))
|
|
|
|
|
|
{
|
|
|
|
|
|
int distance = GetDistance(ownerCell.Value, cell.Value);
|
|
|
|
|
|
UnitsDistanceCache[unit] = distance;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return UnitsDistanceCache;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region 寻找敌人
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 寻找视野内的最近敌人
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="owner">自身</param>
|
|
|
|
|
|
/// <param name="range">指定范围 -1则为索敌范围</param>
|
|
|
|
|
|
/// <param name="job">指定职业</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static GameUnit FindNearestTarget(GameUnit owner, int range = -1, Ejob job = Ejob.Job_Other)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var inSightDistanceDic = FindEnemiesInRange(owner, range, job);
|
|
|
|
|
|
return GetNearestUnit(inSightDistanceDic);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
DebugUtil.LogError("AIUtils.FindNearestTarget error: {0}", e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 寻找范围内符合条件的所有敌人
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static Dictionary<GameUnit, int> FindEnemiesInRange(GameUnit owner, int range = -1,
|
|
|
|
|
|
Ejob job = Ejob.Job_Other, uint targetID = 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (owner == null || range == 0) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var enemies = GameUnitManager.instance.SearchEnemies(owner);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用对象池获取EnemyFilter
|
|
|
|
|
|
var filter = ObjPoolManager.instance.Get<EnemyFilter>();
|
|
|
|
|
|
filter.Init(owner, range, job, targetID);
|
|
|
|
|
|
|
|
|
|
|
|
var result = FindUnits(owner, enemies, filter);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用完毕后返回对象池
|
|
|
|
|
|
ObjPoolManager.instance.Return(filter);
|
|
|
|
|
|
|
|
|
|
|
|
// 归还临时列表
|
|
|
|
|
|
GameUnitManager.instance.ReturnTempList(enemies);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取最近的单位
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static GameUnit GetNearestUnit(Dictionary<GameUnit, int> units)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (units == null || units.Count == 0)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
var minDistance = float.MaxValue;
|
|
|
|
|
|
GameUnit nearestUnit = null;
|
|
|
|
|
|
foreach (var pair in units)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (minDistance > pair.Value)
|
|
|
|
|
|
{
|
|
|
|
|
|
minDistance = pair.Value;
|
|
|
|
|
|
nearestUnit = pair.Key;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nearestUnit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取更近的攻击来源单位
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static GameUnit GetNearestAttackGameUnit(GameUnit owner, GameUnit newUnit, GameUnit unit)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (owner == null || newUnit == null || unit == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var map = LevelManager.Instance.CurrentLevel.Map;
|
|
|
|
|
|
if (map == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var ownerCellIndex = owner.transData?.cellIndex;
|
|
|
|
|
|
var unitCellIndex = unit.transData?.cellIndex;
|
|
|
|
|
|
var newUnitCellIndex = newUnit.transData?.cellIndex;
|
|
|
|
|
|
if (!ownerCellIndex.HasValue || !unitCellIndex.HasValue || !newUnitCellIndex.HasValue) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var distance = MapUtils.Distance(map, ownerCellIndex.Value, unitCellIndex.Value);
|
|
|
|
|
|
var newDistance = MapUtils.Distance(map, ownerCellIndex.Value, newUnitCellIndex.Value);
|
|
|
|
|
|
return distance <= newDistance ? unit : newUnit;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
DebugUtil.LogError("AIUtils.GetNearestGameUnit error: {0}", e);
|
|
|
|
|
|
// 异常返回默认单位
|
|
|
|
|
|
return unit;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 寻找友军
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 通过MonsterID寻找友军 只返回第一个Monster
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static GameUnit FindFriendByMonsterID(int id)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var unit in GameUnitManager.instance.totalUnits.unitList)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (unit.unitLevelData != null && !unit.statusData.isDead && unit.unitLevelData.monsterId == id)
|
|
|
|
|
|
{
|
|
|
|
|
|
return unit;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 寻找视野内的最近友军
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="owner">自身</param>
|
|
|
|
|
|
/// <param name="range">指定距离 -1为锁敌距离</param>
|
|
|
|
|
|
/// <param name="job">指定职业</param>
|
|
|
|
|
|
public static GameUnit FindNearestFriend(GameUnit owner, int range = -1, Ejob job = Ejob.Job_Other)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var inSightDistanceDic = FindFriendsWithDistance(owner, range, job);
|
|
|
|
|
|
return GetNearestUnit(inSightDistanceDic);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
DebugUtil.LogError("AIUtils.FindNearestFriend error: {0}", e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 寻找锁敌范围内的友军列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static List<GameUnit> FindFriendsInSightDistance(GameUnit owner)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var friends = FindFriendsWithDistance(owner);
|
|
|
|
|
|
if (friends == null || friends.Count == 0) return new List<GameUnit>();
|
|
|
|
|
|
|
|
|
|
|
|
// 避免创建新列表,使用单位字典的键集合
|
|
|
|
|
|
var result = new List<GameUnit>(friends.Keys);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
DebugUtil.LogError("AIUtils.FindFriendsInSightDistance error: {0}", e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 寻找符合条件的友军及距离字典
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static Dictionary<GameUnit, int> FindFriendsWithDistance(GameUnit owner, int range = -1,
|
|
|
|
|
|
Ejob job = Ejob.Job_Other)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (owner == null || range == 0) return null;
|
|
|
|
|
|
|
|
|
|
|
|
var friends = GameUnitManager.instance.SearchFriends(owner);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用对象池获取FriendFilter
|
|
|
|
|
|
var filter = ObjPoolManager.instance.Get<FriendFilter>();
|
|
|
|
|
|
filter.Init(owner, range, job);
|
|
|
|
|
|
|
|
|
|
|
|
var result = FindUnits(owner, friends, filter);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用完毕后返回对象池
|
|
|
|
|
|
ObjPoolManager.instance.Return(filter);
|
|
|
|
|
|
|
|
|
|
|
|
// 归还临时列表
|
|
|
|
|
|
GameUnitManager.instance.ReturnTempList(friends);
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检测某个地格是否有友军单位
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static GameUnit FindFriendByCellIndex(int cellIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
var units = AreaManager.instance.GetUnitsByCellIndex(cellIndex);
|
|
|
|
|
|
foreach (var unit in units)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (unit.commonData.troopId == ETroopsId.Enemy)
|
|
|
|
|
|
{
|
|
|
|
|
|
return unit;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 寻找随机目标
|
|
|
|
|
|
|
|
|
|
|
|
public static int FindRandomCell(GameUnit owner, int maxStep = 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var map = LevelManager.Instance.CurrentLevel?.Map;
|
|
|
|
|
|
if (map == null || owner == null) return -1;
|
|
|
|
|
|
var cellIndex = owner.transData.cellIndex;
|
|
|
|
|
|
if (!AreaManager.instance.CheckContainsMoveUnit(cellIndex))
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
var randomCells = new List<int>();
|
|
|
|
|
|
for (var i = 1; i <= maxStep; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var minSteps = i - 1;
|
|
|
|
|
|
var maxSteps = i;
|
|
|
|
|
|
var cells = map.TerrainGridSystem.CellGetNeighboursWithinRange(cellIndex, minSteps, maxSteps,
|
|
|
|
|
|
canCrossCheckType: TGS.CanCrossCheckType.IgnoreCanCrossCheckOnAllCells);
|
|
|
|
|
|
foreach (var cell in cells)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!CheckCellCanSelect(cell)) continue;
|
|
|
|
|
|
if (!PathFinder.CanBasePass(cell, owner)) continue;
|
|
|
|
|
|
if (AreaManager.instance.CheckContainsMoveUnit(cell)) continue;
|
|
|
|
|
|
if (maxSteps > 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (CheckPathExits(owner, cell))
|
|
|
|
|
|
{
|
|
|
|
|
|
randomCells.Add(cell);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
randomCells.Add(cell);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (randomCells.Count == 0)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
var randomIndex = UnityEngine.Random.Range(0, randomCells.Count);
|
|
|
|
|
|
return randomCells[randomIndex];
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
DebugUtil.LogError("AIUtils.FindRandomCell error: {0}", e);
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 寻找随机单位
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static GameUnit FindRandomTarget(GameUnit owner, uint enemy, int range = -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var targetID = enemy;
|
|
|
|
|
|
var inSightDistanceDic = FindEnemiesInRange(owner, range, targetID: targetID);
|
|
|
|
|
|
|
|
|
|
|
|
return GetRandomUnit(inSightDistanceDic);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
|
|
|
|
|
DebugUtil.LogError("AIUtils.FindRandomTarget error: {0}", e);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取随机单位
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static GameUnit GetRandomUnit(Dictionary<GameUnit, int> units)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (units == null || units.Count == 0)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用复用的临时列表避免创建新列表
|
|
|
|
|
|
TempPairList.Clear();
|
|
|
|
|
|
foreach (var pair in units)
|
|
|
|
|
|
{
|
|
|
|
|
|
TempPairList.Add(new KeyValuePair<GameUnit, int>(pair.Key, pair.Value));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (TempPairList.Count == 0)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
var randomIndex = UnityEngine.Random.Range(0, TempPairList.Count);
|
|
|
|
|
|
return TempPairList[randomIndex].Key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 组内单位 寻找 判断
|
|
|
|
|
|
|
|
|
|
|
|
public static bool GroupTargetInRange(GameUnit owner, List<int> group)
|
|
|
|
|
|
{
|
|
|
|
|
|
var inRange = false;
|
|
|
|
|
|
var groupFriends = FindGroupUnits(owner, group);
|
|
|
|
|
|
foreach (var groupFriend in groupFriends)
|
|
|
|
|
|
{
|
|
|
|
|
|
var targetID = groupFriend.unitLevelData.AIBattleInfo.Value.targetID;
|
|
|
|
|
|
if (targetID == Constants.INVALID_UINT_ID) continue;
|
|
|
|
|
|
var target = GameUnitManager.instance.infightUnits.Search(targetID);
|
|
|
|
|
|
var distance = GetDistance(owner.transData.cellIndex, target.transData.cellIndex);
|
|
|
|
|
|
if (distance <= groupFriend.fightData.trackRange)
|
|
|
|
|
|
{
|
|
|
|
|
|
inRange = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return inRange;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static List<GameUnit> FindGroupUnits(GameUnit owner, List<int> group)
|
|
|
|
|
|
{
|
|
|
|
|
|
var groupUnits = new List<GameUnit>();
|
|
|
|
|
|
if (owner == null || group == null || group.Count == 0)
|
|
|
|
|
|
return groupUnits;
|
|
|
|
|
|
|
|
|
|
|
|
var friends = GameUnitManager.instance.SearchFriends(owner);
|
|
|
|
|
|
foreach (var friend in friends)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (friend.unitLevelData == null || friend.unitLevelData.AIBattleInfo == null) continue;
|
|
|
|
|
|
var unitGroups = friend.unitLevelData.AIBattleInfo.Value.groupInfo;
|
|
|
|
|
|
if (unitGroups == null || unitGroups.Count <= 0) continue;
|
|
|
|
|
|
// 检查当前单位的组ID列表与传入的组ID列表是否有交集
|
|
|
|
|
|
foreach (var groupId in unitGroups)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (group.Contains(groupId))
|
|
|
|
|
|
{
|
|
|
|
|
|
groupUnits.Add(friend);
|
|
|
|
|
|
break; // 找到一个匹配就可以添加该单位,无需继续检查其他组ID
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return groupUnits;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
2025-03-28 16:44:08 +08:00
|
|
|
|
}
|