2025-08-27 19:56:49 +08:00
|
|
|
|
using Data;
|
|
|
|
|
using Managers;
|
2025-07-21 13:58:58 +08:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace AI
|
|
|
|
|
{
|
|
|
|
|
public abstract class JobBase
|
|
|
|
|
{
|
|
|
|
|
public Entity.Entity entity;
|
2025-09-06 12:25:55 +08:00
|
|
|
|
protected int timeoutTicks = 150;
|
2025-08-07 16:44:43 +08:00
|
|
|
|
public bool Running => timeoutTicks > 0;
|
2025-07-21 13:58:58 +08:00
|
|
|
|
|
2025-08-07 16:44:43 +08:00
|
|
|
|
protected abstract void UpdateJob();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void StartJob(Entity.Entity target)
|
2025-07-21 13:58:58 +08:00
|
|
|
|
{
|
|
|
|
|
entity = target;
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
2025-07-21 13:58:58 +08:00
|
|
|
|
public bool Update()
|
|
|
|
|
{
|
2025-08-07 16:44:43 +08:00
|
|
|
|
if (!Running)
|
2025-07-21 13:58:58 +08:00
|
|
|
|
return false;
|
|
|
|
|
UpdateJob();
|
|
|
|
|
timeoutTicks--;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
2025-07-21 13:58:58 +08:00
|
|
|
|
public virtual void StopJob()
|
|
|
|
|
{
|
|
|
|
|
timeoutTicks = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
2025-07-21 13:58:58 +08:00
|
|
|
|
public class WanderJob : JobBase
|
|
|
|
|
{
|
|
|
|
|
public override void StartJob(Entity.Entity target)
|
|
|
|
|
{
|
|
|
|
|
base.StartJob(target);
|
2025-08-07 16:44:43 +08:00
|
|
|
|
Vector3 move = new(Random.Range(-10, 10), Random.Range(-10, 10));
|
|
|
|
|
var targetPosition = entity.transform.position + move;
|
2025-07-21 13:58:58 +08:00
|
|
|
|
entity.SetTarget(targetPosition);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void UpdateJob()
|
|
|
|
|
{
|
|
|
|
|
entity.TryMove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override public void StopJob()
|
|
|
|
|
{
|
|
|
|
|
base.StopJob();
|
|
|
|
|
}
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-07-21 13:58:58 +08:00
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
2025-08-07 16:44:43 +08:00
|
|
|
|
public class IdleJob : JobBase
|
2025-07-25 19:16:58 +08:00
|
|
|
|
{
|
|
|
|
|
override public void StartJob(Entity.Entity target)
|
|
|
|
|
{
|
|
|
|
|
base.StartJob(target);
|
|
|
|
|
timeoutTicks = 500;
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
2025-07-25 19:16:58 +08:00
|
|
|
|
protected override void UpdateJob()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
2025-07-25 19:16:58 +08:00
|
|
|
|
public class MoveJob : JobBase
|
|
|
|
|
{
|
|
|
|
|
protected override void UpdateJob()
|
|
|
|
|
{
|
|
|
|
|
entity.TryMove();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
public class AttackJob : JobBase
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
private Entity.Entity attackTarget;
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// StartJob 方法:用于初始化任务,寻找初始攻击目标
|
|
|
|
|
override public void StartJob(Entity.Entity performerEntityContext) // 参数名更明确,通常是发起任务的实体
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
base.StartJob(performerEntityContext);
|
|
|
|
|
// 1. 任务执行者自身有效性检查
|
2025-09-06 12:25:55 +08:00
|
|
|
|
if (!entity)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
StopJob(); // 调用StopJob来结束任务
|
|
|
|
|
return;
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
attackTarget = FindNewHostileTarget();
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
if (!attackTarget)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
StopJob(); // 调用StopJob来结束任务
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
protected override void UpdateJob()
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// 1. 任务执行者的基本检查
|
2025-09-06 12:25:55 +08:00
|
|
|
|
if (!entity || entity.IsDead)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
|
|
|
|
StopJob();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
if (!attackTarget || attackTarget.IsDead)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
attackTarget = FindNewHostileTarget(); // 尝试寻找新的攻击目标
|
2025-09-06 12:25:55 +08:00
|
|
|
|
if (!attackTarget)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
StopJob();
|
|
|
|
|
return;
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
var weapon = entity.GetCurrentWeapon();
|
|
|
|
|
var attackRange = 0f;
|
|
|
|
|
if (weapon != null)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
attackRange = weapon.Attributes.attackRange;
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
var distanceSq = (entity.Position - attackTarget.Position).sqrMagnitude;
|
|
|
|
|
var effectiveAttackRangeSq = attackRange * attackRange; // 将攻击范围也平方
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
entity.SetTarget(attackTarget.Position);
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
if (weapon != null && distanceSq <= effectiveAttackRangeSq)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
entity.TryAttack();
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
entity.TryMove();
|
|
|
|
|
}
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 查找执行实体最近的敌对目标。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>找到的敌对实体,如果没有则返回null。</returns>
|
|
|
|
|
private Entity.Entity FindNewHostileTarget()
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
if (!entity) return null;
|
|
|
|
|
return EntityManage.Instance.FindNearestEntityByRelation(
|
|
|
|
|
entity.currentDimensionId, // 搜索维度ID
|
|
|
|
|
entity.entityPrefab, // 执行实体的Prefab ID,用于关系判断
|
|
|
|
|
Relation.Hostile)?.entity; // 寻找敌对关系的目标
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
|
|
|
|
public class AdvancedAttackJob : JobBase
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
private Entity.Entity attackTarget;
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// 常量:用于配置远程AI行为的风筝参数
|
|
|
|
|
private const float KITING_THRESHOLD_MULTIPLIER = 0.5f; // 当目标距离小于 (攻击范围 * 此乘数) 时,远程单位开始尝试远离
|
|
|
|
|
|
|
|
|
|
private const float KITING_BUFFER_DISTANCE = 5.0f; // 当远程单位远离时,目标点会是当前位置向反方向偏移此距离
|
|
|
|
|
|
|
|
|
|
// StartJob 方法:用于初始化任务,寻找初始攻击目标
|
|
|
|
|
override public void StartJob(Entity.Entity performerEntityContext)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
base.StartJob(performerEntityContext);
|
|
|
|
|
if (entity == null)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
StopJob();
|
2025-08-07 16:44:43 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
attackTarget = FindNewHostileTarget();
|
|
|
|
|
if (attackTarget == null)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
StopJob();
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
protected override void UpdateJob()
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// 1. 任务执行者的基本检查
|
|
|
|
|
if (entity == null || entity.IsDead)
|
|
|
|
|
{
|
|
|
|
|
StopJob();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// 2. 攻击目标检查
|
|
|
|
|
if (attackTarget == null || attackTarget.IsDead)
|
|
|
|
|
{
|
|
|
|
|
attackTarget = FindNewHostileTarget(); // 尝试寻找新的攻击目标
|
|
|
|
|
if (attackTarget == null)
|
|
|
|
|
{
|
|
|
|
|
StopJob();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// 获取武器和其属性
|
|
|
|
|
var weapon = entity.GetCurrentWeapon();
|
|
|
|
|
var attackRange = 0f;
|
|
|
|
|
var isRangedWeapon = false; // 标识是否为远程武器
|
|
|
|
|
if (weapon != null)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
if (weapon.Attributes != null)
|
|
|
|
|
{
|
|
|
|
|
attackRange = weapon.Attributes.attackRange;
|
|
|
|
|
}
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// 使用 WeaponType 来判断武器类型,更明确
|
|
|
|
|
isRangedWeapon = weapon.Type == WeaponType.Ranged;
|
|
|
|
|
}
|
2025-08-07 16:44:43 +08:00
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
var distanceSq = (entity.Position - attackTarget.Position).sqrMagnitude;
|
|
|
|
|
var effectiveAttackRangeSq = attackRange * attackRange; // 将攻击范围平方
|
|
|
|
|
// ---- 核心AI行为决策 ( AdvancedAttackJob 的智能之处) ----
|
|
|
|
|
if (isRangedWeapon)
|
|
|
|
|
{
|
|
|
|
|
// 远程单位的风筝(Kiting)逻辑
|
|
|
|
|
var kitingDistance = attackRange * KITING_THRESHOLD_MULTIPLIER;
|
|
|
|
|
var kitingThresholdSq = kitingDistance * kitingDistance; // 过近距离的平方
|
|
|
|
|
// 1. 如果目标过于接近 (小于风筝阈值),尝试远离
|
|
|
|
|
if (distanceSq < kitingThresholdSq)
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
// 计算一个远离目标的点作为移动目标
|
|
|
|
|
var directionAway = (entity.Position - attackTarget.Position).normalized;
|
|
|
|
|
var fleePosition = entity.Position + directionAway * KITING_BUFFER_DISTANCE;
|
|
|
|
|
entity.SetTarget(fleePosition); // 设置远离点为新的移动目标
|
|
|
|
|
entity.TryMove(); // 优先执行移动操作以拉开距离
|
|
|
|
|
// 在此状态下不进行攻击,专注于 reposition
|
|
|
|
|
}
|
|
|
|
|
// 2. 如果目标在最佳攻击范围内 (即在风筝阈值和有效攻击范围之间),则停止移动并攻击
|
|
|
|
|
else if (distanceSq <= effectiveAttackRangeSq)
|
|
|
|
|
{
|
|
|
|
|
entity.SetTarget(entity.Position); // 设定目标为当前位置,使其停止移动,专注于攻击
|
|
|
|
|
entity.TryAttack();
|
|
|
|
|
}
|
|
|
|
|
// 3. 如果目标太远 (超出有效攻击范围),则移动靠近目标
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
entity.SetTarget(attackTarget.Position); // 设置目标位置为移动目标
|
|
|
|
|
entity.TryMove();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else // 近战单位或没有武器的单位
|
|
|
|
|
{
|
|
|
|
|
entity.SetTarget(attackTarget.Position);
|
|
|
|
|
if (weapon != null && distanceSq <= effectiveAttackRangeSq)
|
|
|
|
|
{
|
|
|
|
|
entity.TryAttack();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
entity.TryMove();
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 19:56:49 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// 查找执行实体最近的敌对目标。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>找到的敌对实体,如果没有则返回null。</returns>
|
|
|
|
|
private Entity.Entity FindNewHostileTarget()
|
2025-08-07 16:44:43 +08:00
|
|
|
|
{
|
2025-08-27 19:56:49 +08:00
|
|
|
|
if (!entity) return null;
|
|
|
|
|
return EntityManage.Instance.FindNearestEntityByRelation(
|
|
|
|
|
entity.currentDimensionId,
|
|
|
|
|
entity.entityPrefab,
|
|
|
|
|
Relation.Hostile)?.entity;
|
2025-08-07 16:44:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-27 19:56:49 +08:00
|
|
|
|
|
2025-09-06 12:25:55 +08:00
|
|
|
|
public class FleeJob : JobBase
|
|
|
|
|
{
|
|
|
|
|
// 逃跑时,在远离方向上设置的目标点偏移距离。让AI尝试跑向更远的点。
|
|
|
|
|
private const float FLEE_BUFFER_DISTANCE = 10.0f;
|
|
|
|
|
|
|
|
|
|
// 远程单位在逃跑时,如果敌人距离已超出其攻击范围的多少倍,可以认为已经足够安全。
|
|
|
|
|
// 此时实体将停止紧急移动,可以等待timeoutTicks结束或进行其他决策。
|
|
|
|
|
private const float RANGED_SAFE_DISTANCE_MULTIPLIER = 2.0f;
|
|
|
|
|
|
|
|
|
|
// 对于近战或无武器单位,敌人必须在这个距离内,才触发紧急逃跑。
|
|
|
|
|
// 超过这个距离,实体可能认为已经足够安全,可以停止紧急移动。
|
|
|
|
|
private const float MELEE_PERIL_DISTANCE = 15.0f;
|
|
|
|
|
|
|
|
|
|
private const float MELEE_PERIL_DISTANCE_SQUARED = MELEE_PERIL_DISTANCE * MELEE_PERIL_DISTANCE;
|
|
|
|
|
|
|
|
|
|
// **新增**:目标刷新间隔 (以Ticks计)
|
|
|
|
|
private const int TARGET_REFRESH_INTERVAL_TICKS = 30; // 每30帧(Ticks)刷新一次目标
|
|
|
|
|
|
|
|
|
|
// **新增**:缓存的敌对目标,避免频繁调用FindNearestHostileTarget
|
|
|
|
|
private Entity.Entity _cachedHostileTarget;
|
|
|
|
|
|
|
|
|
|
// **新增**:目标刷新计时器
|
|
|
|
|
private int _targetRefreshTimer;
|
|
|
|
|
|
|
|
|
|
// **新增**:侧走参数 (用于躲避远程攻击)
|
|
|
|
|
private const float EVASION_SIDE_STEP_STRENGTH = 0.5f; // 侧走分量强度,与主方向叠加,需要调整
|
|
|
|
|
private int _sideStepDirection = 1; // 1 for right, -1 for left
|
|
|
|
|
private int _sideStepTimer;
|
|
|
|
|
private const int EVASION_SIDE_STEP_INTERVAL_TICKS = 20; // 每20帧切换一次侧走方向
|
|
|
|
|
|
|
|
|
|
public FleeJob(int initialTimeoutTicks = 150)
|
|
|
|
|
{
|
|
|
|
|
timeoutTicks = initialTimeoutTicks;
|
|
|
|
|
_targetRefreshTimer = 0; // 初始化计时器,让第一次UpdateJob立即刷新目标
|
|
|
|
|
_sideStepTimer = 0;
|
|
|
|
|
_sideStepDirection = Random.Range(0, 2) * 2 - 1; // 随机初始化左右方向
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void StartJob(Entity.Entity performerEntityContext)
|
|
|
|
|
{
|
|
|
|
|
base.StartJob(performerEntityContext);
|
|
|
|
|
_cachedHostileTarget = null; // 任务开始时清空缓存,确保第一次更新会查找新目标
|
|
|
|
|
_targetRefreshTimer = 0; // 确保立即查找目标
|
|
|
|
|
_sideStepTimer = 0;
|
|
|
|
|
_sideStepDirection = Random.Range(0, 2) * 2 - 1; // 随机初始化左右方向
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void UpdateJob()
|
|
|
|
|
{
|
|
|
|
|
// 1. 任务执行者的基本检查
|
|
|
|
|
if (!entity || entity.IsDead)
|
|
|
|
|
{
|
|
|
|
|
StopJob();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 目标刷新逻辑 (避免每帧都调用FindNearestHostileTarget)
|
|
|
|
|
_targetRefreshTimer--;
|
|
|
|
|
if (!_cachedHostileTarget || _cachedHostileTarget.IsDead || _targetRefreshTimer <= 0)
|
|
|
|
|
{
|
|
|
|
|
_cachedHostileTarget = FindNearestHostileTarget();
|
|
|
|
|
_targetRefreshTimer = TARGET_REFRESH_INTERVAL_TICKS; // 重置计时器
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用缓存的目标
|
|
|
|
|
var hostileTarget = _cachedHostileTarget;
|
|
|
|
|
if (!hostileTarget)
|
|
|
|
|
{
|
|
|
|
|
// 如果没有敌对目标可逃跑,则停止此工作。
|
|
|
|
|
StopJob();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 计算与敌对目标的距离
|
|
|
|
|
var distanceSq = (entity.Position - hostileTarget.Position).sqrMagnitude;
|
|
|
|
|
// 4. 获取敌方目标武器信息,判断敌方是否为远程单位
|
|
|
|
|
var enemyWeapon = hostileTarget.GetCurrentWeapon();
|
|
|
|
|
var isEnemyRanged = enemyWeapon is { Type: WeaponType.Ranged };
|
|
|
|
|
|
|
|
|
|
// 获取自己的武器信息 (虽然逃跑主要看敌方武器,但自身安全距离判断需要)
|
|
|
|
|
var selfWeapon = entity.GetCurrentWeapon();
|
|
|
|
|
var isSelfRanged = selfWeapon is { Type: WeaponType.Ranged };
|
|
|
|
|
// 5. 计算一个远离目标的基础方向
|
|
|
|
|
var directionAway = (entity.Position - hostileTarget.Position).normalized;
|
|
|
|
|
var moveTargetDirection = directionAway; // 默认是纯远离方向
|
|
|
|
|
// 6. 如果敌方是远程单位,叠加侧走逻辑
|
|
|
|
|
if (isEnemyRanged)
|
|
|
|
|
{
|
|
|
|
|
_sideStepTimer--;
|
|
|
|
|
if (_sideStepTimer <= 0)
|
|
|
|
|
{
|
|
|
|
|
_sideStepDirection *= -1; // 切换侧走方向 (左或右)
|
|
|
|
|
_sideStepTimer = EVASION_SIDE_STEP_INTERVAL_TICKS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算侧走方向 (在X-Z平面上垂直于远离方向)
|
|
|
|
|
var perpendicularDirection = GetPerpendicularVectorXZ(directionAway) * _sideStepDirection;
|
|
|
|
|
// 侧走策略:在远离方向上叠加一个侧走分量
|
|
|
|
|
// 让侧走分量与远离分量结合,形成一个弧线或斜向移动
|
|
|
|
|
moveTargetDirection = (directionAway * (1 - EVASION_SIDE_STEP_STRENGTH) +
|
|
|
|
|
perpendicularDirection * EVASION_SIDE_STEP_STRENGTH).normalized;
|
|
|
|
|
// 或者更简单地:moveTargetDirection = (directionAway + perpendicularDirection * EVASION_SIDE_STEP_STRENGTH).normalized;
|
|
|
|
|
// 需要根据实际效果调整EVASION_SIDE_STEP_STRENGTH的数值和结合方式。
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7. 确定最终移动目标点
|
|
|
|
|
var fleePosition = entity.Position + moveTargetDirection * FLEE_BUFFER_DISTANCE;
|
|
|
|
|
// 8. 结合自己的武器类型,决定何时停止紧急移动 (和之前的逻辑类似)
|
|
|
|
|
if (isSelfRanged)
|
|
|
|
|
{
|
|
|
|
|
var selfAttackRange = 0f;
|
|
|
|
|
if (selfWeapon != null && selfWeapon.Attributes != null)
|
|
|
|
|
{
|
|
|
|
|
selfAttackRange = selfWeapon.Attributes.attackRange;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var safeDistanceSq = (selfAttackRange * RANGED_SAFE_DISTANCE_MULTIPLIER) *
|
|
|
|
|
(selfAttackRange * RANGED_SAFE_DISTANCE_MULTIPLIER);
|
|
|
|
|
// 如果目标在“安全距离”内,则实体需要继续远离
|
|
|
|
|
if (distanceSq < safeDistanceSq)
|
|
|
|
|
{
|
|
|
|
|
entity.SetTarget(fleePosition);
|
|
|
|
|
entity.TryMove();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 已经达到足够的安全距离,此时实体可以停止紧急移动。
|
|
|
|
|
// FleeJob会继续运行,直到timeoutTicks耗尽,给予一定的缓冲时间。
|
|
|
|
|
entity.SetTarget(entity.Position); // 设定目标为当前位置,使其停止移动
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else // 自己是近战单位或没有武器
|
|
|
|
|
{
|
|
|
|
|
// 只要目标在“危险距离”内,就一直向外逃跑
|
|
|
|
|
if (distanceSq < MELEE_PERIL_DISTANCE_SQUARED)
|
|
|
|
|
{
|
|
|
|
|
entity.SetTarget(fleePosition);
|
|
|
|
|
entity.TryMove();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// 已经拉开足够远的距离,停止紧急逃跑.
|
|
|
|
|
// 同样,FleeJob会继续运行,直到timeoutTicks耗尽。
|
|
|
|
|
entity.SetTarget(entity.Position); // 设定目标为当前位置,使其停止移动
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override public void StopJob()
|
|
|
|
|
{
|
|
|
|
|
base.StopJob();
|
|
|
|
|
if (entity != null)
|
|
|
|
|
{
|
|
|
|
|
entity.SetTarget(entity.Position); // 确保实体停止任何正在进行的逃跑移动
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_cachedHostileTarget = null; // 清除缓存目标,以便下次StartJob时重新查找
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 查找执行实体最近的敌对目标。此方法可能耗时,应避免频繁调用。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>找到的敌对实体,如果没有则返回null。</returns>
|
|
|
|
|
private Entity.Entity FindNearestHostileTarget()
|
|
|
|
|
{
|
|
|
|
|
if (entity == null) return null;
|
|
|
|
|
return EntityManage.Instance.FindNearestEntityByRelation(
|
|
|
|
|
entity.currentDimensionId, // 搜索维度ID
|
|
|
|
|
entity.entityPrefab, // 执行实体的Prefab ID,用于关系判断
|
|
|
|
|
Relation.Hostile)?.entity; // 寻找敌对关系的目标
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 获取给定向量在X-Z平面上的垂直向量。
|
|
|
|
|
/// 假定Y轴是世界向上方向。
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="direction">原始方向向量。</param>
|
|
|
|
|
/// <returns>在X-Z平面上垂直于原始向量的向量(顺时针旋转90度)。</returns>
|
|
|
|
|
private Vector3 GetPerpendicularVectorXZ(Vector3 direction)
|
|
|
|
|
{
|
|
|
|
|
// 创建一个只保留XZ分量的向量
|
|
|
|
|
var flatDirection = new Vector3(direction.x, 0, direction.z).normalized;
|
|
|
|
|
// 顺时针旋转90度 (x,z) => (z,-x)
|
|
|
|
|
return new Vector3(flatDirection.z, 0, -flatDirection.x);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-21 13:58:58 +08:00
|
|
|
|
}
|