651 lines
23 KiB
C#
651 lines
23 KiB
C#
using Opsive.BehaviorDesigner.AddOns.MovementPack.Runtime.Tasks;
|
||
using Opsive.BehaviorDesigner.AddOns.Shared.Runtime.Pathfinding;
|
||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||
using Opsive.GraphDesigner.Runtime;
|
||
using Opsive.GraphDesigner.Runtime.Variables;
|
||
using Opsive.Shared.Utility;
|
||
using UnityEngine;
|
||
using UnityEngine.AI;
|
||
|
||
namespace Cielonos.MainGame.Characters.AI
|
||
{
|
||
/// <summary>
|
||
/// 对峙与游走 (Standoff)
|
||
/// 敌人面朝玩家,在指定对峙距离带 (MinDistance ~ MaxDistance) 上
|
||
/// 进行横向绕圈、前后踱步和短暂停滞的随机组合移动。
|
||
/// 通过运动模式权重可塑造不同敌人的"性格"(激进型 / 谨慎型)。
|
||
/// </summary>
|
||
[Description("对峙与游走。敌人面朝玩家,在攻击距离边缘进行横向绕圈与随机踱步。")]
|
||
[NodeIcon("f900ccca7c66371459b52036efeb8778", "cfd0e78235c50db46bc12d1751492ecf")]
|
||
[Category("Cielonos/Movement")]
|
||
public class Standoff : MovementBase
|
||
{
|
||
/// <summary>
|
||
/// 运动模式枚举。
|
||
/// </summary>
|
||
public enum StandoffMovementMode : byte
|
||
{
|
||
/// <summary>绕着目标横向移动(左右绕圈)。</summary>
|
||
Strafe,
|
||
/// <summary>小幅度前进或后退。</summary>
|
||
AdvanceRetreat,
|
||
/// <summary>原地短暂停滞(蓄势待发)。</summary>
|
||
Pause,
|
||
}
|
||
|
||
#region Parameters
|
||
|
||
[Header("Target & Distance")]
|
||
[Tooltip("对峙的目标对象。")]
|
||
[SerializeField] protected SharedVariable<GameObject> m_Target;
|
||
[Tooltip("与目标保持的最小距离 (米)。敌人不会比这更近。")]
|
||
[SerializeField] protected SharedVariable<float> m_MinDistance = 4f;
|
||
[Tooltip("与目标保持的最大距离 (米)。敌人不会比这更远。")]
|
||
[SerializeField] protected SharedVariable<float> m_MaxDistance = 7f;
|
||
|
||
[Header("Movement")]
|
||
[Tooltip("横向移动时的速度。")]
|
||
[SerializeField] protected SharedVariable<float> m_StrafeSpeed = 2.5f;
|
||
[Tooltip("前后踱步时的速度。")]
|
||
[SerializeField] protected SharedVariable<float> m_AdvanceRetreatSpeed = 1.5f;
|
||
[Tooltip("面朝目标的旋转角速度 (度/秒)。")]
|
||
[SerializeField] protected SharedVariable<float> m_RotationSpeed = 360f;
|
||
|
||
[Header("Strafe")]
|
||
[Tooltip("横向绕圈时,每段圆弧的最小角度 (度)。")]
|
||
[SerializeField] protected SharedVariable<float> m_StrafeArcMin = 30f;
|
||
[Tooltip("横向绕圈时,每段圆弧的最大角度 (度)。")]
|
||
[SerializeField] protected SharedVariable<float> m_StrafeArcMax = 90f;
|
||
|
||
[Header("Advance / Retreat")]
|
||
[Tooltip("前后踱步的最小距离 (米)。")]
|
||
[SerializeField] protected SharedVariable<float> m_AdvanceRetreatDistanceMin = 1f;
|
||
[Tooltip("前后踱步的最大距离 (米)。")]
|
||
[SerializeField] protected SharedVariable<float> m_AdvanceRetreatDistanceMax = 3f;
|
||
|
||
[Header("Mode Weights & Duration")]
|
||
[Tooltip("横向绕圈的权重。")]
|
||
[SerializeField] protected SharedVariable<float> m_StrafeWeight = 5f;
|
||
[Tooltip("前后踱步的权重。")]
|
||
[SerializeField] protected SharedVariable<float> m_AdvanceRetreatWeight = 3f;
|
||
[Tooltip("短暂停滞的权重。")]
|
||
[SerializeField] protected SharedVariable<float> m_PauseWeight = 2f;
|
||
[Tooltip("每个运动模式持续的最短时间 (秒)。")]
|
||
[SerializeField] protected SharedVariable<float> m_ModeDurationMin = 1f;
|
||
[Tooltip("每个运动模式持续的最长时间 (秒)。")]
|
||
[SerializeField] protected SharedVariable<float> m_ModeDurationMax = 3f;
|
||
[Tooltip("模式切换之间 / 到达航点后的短暂停顿时间 (秒)。设为 0 则无停顿。")]
|
||
[SerializeField] protected SharedVariable<float> m_TransitionPause = 0.2f;
|
||
|
||
[Header("Task Duration")]
|
||
[Tooltip("是否无限期运行 (仅靠 Conditional Abort 打断)。")]
|
||
[SerializeField] protected SharedVariable<bool> m_IsInfinite = false;
|
||
[Tooltip("如果非无限, 最长持续时间 (秒)。到期后返回 Success。")]
|
||
[SerializeField] protected SharedVariable<float> m_MaxDuration = 8f;
|
||
|
||
#endregion
|
||
|
||
#region Private State
|
||
|
||
private NavMeshAgentPathfinder _navPathfinder;
|
||
private NavMeshAgent _agent;
|
||
private AutomataLandMovementSubcontroller _movementSc;
|
||
private float _originalMaxSpeed;
|
||
private bool _originalUpdateRotation;
|
||
private bool _original8WayMovement;
|
||
|
||
// 任务计时
|
||
private float _taskStartTime;
|
||
|
||
// 运动模式状态机
|
||
private StandoffMovementMode _currentMode;
|
||
private float _modeStartTime;
|
||
private float _modeDuration;
|
||
private bool _isInTransitionPause;
|
||
private float _transitionPauseEndTime;
|
||
|
||
// 当前运动参数
|
||
private int _strafeDirection; // +1 = 右绕, -1 = 左绕
|
||
private bool _hasValidDestination;
|
||
|
||
// NavMesh 采样
|
||
private const int k_MaxSampleRetries = 5;
|
||
|
||
// 安全距离余量: 航点不会正好落在边界上,留出这个余量
|
||
private const float k_DistanceMargin = 0.5f;
|
||
|
||
// 最小有效航点距离: 航点与当前位置之间低于此距离视为"太近",不生成
|
||
private const float k_MinMeaningfulDistance = 0.8f;
|
||
|
||
#endregion
|
||
|
||
#region Convenience
|
||
|
||
/// <summary>安全最小距离: 确保至少为 0.5m。</summary>
|
||
private float SafeMinDistance => Mathf.Max(m_MinDistance.Value, 0.5f);
|
||
|
||
/// <summary>安全最大距离: 确保至少比最小距离大 0.5m。</summary>
|
||
private float SafeMaxDistance => Mathf.Max(m_MaxDistance.Value, SafeMinDistance + 0.5f);
|
||
|
||
/// <summary>距离带中心半径。</summary>
|
||
private float CenterRadius => (SafeMinDistance + SafeMaxDistance) * 0.5f;
|
||
|
||
/// <summary>用于航点钳位的有效最小距离 (含安全余量)。</summary>
|
||
private float ClampMinDistance => SafeMinDistance + k_DistanceMargin;
|
||
|
||
/// <summary>用于航点钳位的有效最大距离 (含安全余量)。</summary>
|
||
private float ClampMaxDistance => Mathf.Max(SafeMaxDistance - k_DistanceMargin, ClampMinDistance + 0.1f);
|
||
|
||
private float GetDistanceToTarget()
|
||
{
|
||
Vector3 diff = transform.position - m_Target.Value.transform.position;
|
||
diff.y = 0f;
|
||
return diff.magnitude;
|
||
}
|
||
|
||
private Vector3 GetFlatOffsetFromTarget()
|
||
{
|
||
Vector3 diff = transform.position - m_Target.Value.transform.position;
|
||
diff.y = 0f;
|
||
return diff;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Lifecycle
|
||
|
||
public override void OnAwake()
|
||
{
|
||
base.OnAwake();
|
||
|
||
_navPathfinder = m_Pathfinder as NavMeshAgentPathfinder;
|
||
if (_navPathfinder == null)
|
||
{
|
||
Debug.LogError("[Standoff] Requires NavMeshAgentPathfinder.");
|
||
return;
|
||
}
|
||
_agent = _navPathfinder.m_NavMeshAgent;
|
||
|
||
var automata = gameObject.GetComponent<Automata>();
|
||
if (automata != null)
|
||
{
|
||
_movementSc = automata.movementSc as AutomataLandMovementSubcontroller;
|
||
}
|
||
}
|
||
|
||
public override void OnStart()
|
||
{
|
||
base.OnStart();
|
||
|
||
if (m_Target == null || m_Target.Value == null)
|
||
{
|
||
Debug.LogError("[Standoff] A target must be set.");
|
||
return;
|
||
}
|
||
|
||
if (_agent == null) return;
|
||
|
||
_originalMaxSpeed = _navPathfinder.Speed;
|
||
_originalUpdateRotation = _agent.updateRotation;
|
||
_agent.updateRotation = false;
|
||
_agent.autoBraking = true;
|
||
|
||
if (_movementSc != null)
|
||
{
|
||
_original8WayMovement = _movementSc.is8WayMovement;
|
||
_movementSc.is8WayMovement = true;
|
||
}
|
||
|
||
_taskStartTime = Time.time;
|
||
_isInTransitionPause = false;
|
||
_hasValidDestination = false;
|
||
|
||
SelectNewMode();
|
||
}
|
||
|
||
public override TaskStatus OnUpdate()
|
||
{
|
||
if (m_Target == null || m_Target.Value == null)
|
||
return TaskStatus.Failure;
|
||
|
||
if (_agent == null)
|
||
return TaskStatus.Failure;
|
||
|
||
// 1. 任务总时长
|
||
if (!m_IsInfinite.Value && Time.time - _taskStartTime >= m_MaxDuration.Value)
|
||
return TaskStatus.Success;
|
||
|
||
// 2. 始终面朝目标
|
||
RotateTowardsTarget();
|
||
|
||
// 3. 过渡停顿
|
||
if (_isInTransitionPause)
|
||
{
|
||
StopAgent();
|
||
|
||
if (Time.time >= _transitionPauseEndTime)
|
||
{
|
||
_isInTransitionPause = false;
|
||
|
||
if (Time.time - _modeStartTime >= _modeDuration)
|
||
{
|
||
SelectNewMode();
|
||
}
|
||
else
|
||
{
|
||
GenerateWaypoint();
|
||
}
|
||
}
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
// 4. 模式到期
|
||
if (Time.time - _modeStartTime >= _modeDuration)
|
||
{
|
||
EnterTransitionPause();
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
// 5. 执行当前模式
|
||
if (_currentMode == StandoffMovementMode.Pause)
|
||
{
|
||
StopAgent();
|
||
}
|
||
else
|
||
{
|
||
if (_hasValidDestination && HasArrived())
|
||
{
|
||
EnterTransitionPause();
|
||
}
|
||
else if (!_hasValidDestination)
|
||
{
|
||
GenerateWaypoint();
|
||
}
|
||
}
|
||
|
||
return TaskStatus.Running;
|
||
}
|
||
|
||
public override void OnEnd()
|
||
{
|
||
base.OnEnd();
|
||
|
||
if (_agent != null && _agent.isOnNavMesh && _agent.isActiveAndEnabled)
|
||
{
|
||
_agent.isStopped = true;
|
||
_agent.velocity = Vector3.zero;
|
||
_agent.ResetPath();
|
||
}
|
||
|
||
if (_agent != null)
|
||
{
|
||
_agent.updateRotation = _originalUpdateRotation;
|
||
_agent.autoBraking = true;
|
||
_agent.speed = _originalMaxSpeed;
|
||
}
|
||
|
||
if (_movementSc != null)
|
||
{
|
||
_movementSc.is8WayMovement = _original8WayMovement;
|
||
}
|
||
}
|
||
|
||
public override void OnBehaviorTreeStopped(bool paused)
|
||
{
|
||
base.OnBehaviorTreeStopped(paused);
|
||
|
||
if (_agent != null && _agent.updateRotation != _originalUpdateRotation)
|
||
_agent.updateRotation = _originalUpdateRotation;
|
||
|
||
if (_movementSc != null && _movementSc.is8WayMovement != _original8WayMovement)
|
||
_movementSc.is8WayMovement = _original8WayMovement;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Rotation
|
||
|
||
private void RotateTowardsTarget()
|
||
{
|
||
Vector3 direction = m_Target.Value.transform.position - transform.position;
|
||
direction.y = 0f;
|
||
|
||
if (direction.sqrMagnitude < 0.001f) return;
|
||
|
||
Quaternion targetRotation = Quaternion.LookRotation(direction);
|
||
transform.rotation = Quaternion.RotateTowards(
|
||
transform.rotation,
|
||
targetRotation,
|
||
m_RotationSpeed.Value * Time.deltaTime
|
||
);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Mode Selection
|
||
|
||
private void EnterTransitionPause()
|
||
{
|
||
_isInTransitionPause = true;
|
||
_hasValidDestination = false;
|
||
_transitionPauseEndTime = Time.time + m_TransitionPause.Value;
|
||
}
|
||
|
||
private void SelectNewMode()
|
||
{
|
||
_modeStartTime = Time.time;
|
||
_modeDuration = Random.Range(m_ModeDurationMin.Value, m_ModeDurationMax.Value);
|
||
_hasValidDestination = false;
|
||
|
||
float totalWeight = m_StrafeWeight.Value + m_AdvanceRetreatWeight.Value + m_PauseWeight.Value;
|
||
if (totalWeight <= 0f)
|
||
{
|
||
_currentMode = StandoffMovementMode.Pause;
|
||
return;
|
||
}
|
||
|
||
float roll = Random.Range(0f, totalWeight);
|
||
|
||
if (roll < m_StrafeWeight.Value)
|
||
{
|
||
_currentMode = StandoffMovementMode.Strafe;
|
||
_strafeDirection = Random.value > 0.5f ? 1 : -1;
|
||
_agent.speed = m_StrafeSpeed.Value;
|
||
}
|
||
else if (roll < m_StrafeWeight.Value + m_AdvanceRetreatWeight.Value)
|
||
{
|
||
_currentMode = StandoffMovementMode.AdvanceRetreat;
|
||
_agent.speed = m_AdvanceRetreatSpeed.Value;
|
||
}
|
||
else
|
||
{
|
||
_currentMode = StandoffMovementMode.Pause;
|
||
_agent.speed = 0f;
|
||
return;
|
||
}
|
||
|
||
GenerateWaypoint();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Waypoint Generation
|
||
|
||
private void GenerateWaypoint()
|
||
{
|
||
bool success = false;
|
||
|
||
switch (_currentMode)
|
||
{
|
||
case StandoffMovementMode.Strafe:
|
||
success = GenerateStrafeWaypoint();
|
||
break;
|
||
case StandoffMovementMode.AdvanceRetreat:
|
||
success = GenerateAdvanceRetreatWaypoint();
|
||
break;
|
||
}
|
||
|
||
// 如果航点生成失败 (NavMesh 采样失败、空间不足等),
|
||
// 跳过当前模式,立刻进入过渡并重选
|
||
if (!success)
|
||
{
|
||
EnterTransitionPause();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 横向绕圈航点: 在以目标为圆心的圆弧上取一个安全的远航点。
|
||
/// 每次生成航点时随机取一个距离带内的半径,让路径更有机感。
|
||
/// </summary>
|
||
private bool GenerateStrafeWaypoint()
|
||
{
|
||
Vector3 targetPos = m_Target.Value.transform.position;
|
||
Vector3 toSelf = GetFlatOffsetFromTarget();
|
||
float currentDist = toSelf.magnitude;
|
||
|
||
// 如果太近,先撤到中心半径
|
||
if (currentDist < SafeMinDistance)
|
||
{
|
||
Vector3 retreatDir = currentDist > 0.01f ? toSelf.normalized : -transform.forward;
|
||
Vector3 safePos = targetPos + retreatDir * CenterRadius;
|
||
return TrySetValidDestination(safePos);
|
||
}
|
||
|
||
float currentAngle = Mathf.Atan2(toSelf.x, toSelf.z) * Mathf.Rad2Deg;
|
||
float arcAngle = Random.Range(m_StrafeArcMin.Value, m_StrafeArcMax.Value);
|
||
float newAngle = currentAngle + arcAngle * _strafeDirection;
|
||
|
||
// 每次航点使用距离带内的随机半径,产生自然波动
|
||
float radius = Random.Range(ClampMinDistance, ClampMaxDistance);
|
||
|
||
float rad = newAngle * Mathf.Deg2Rad;
|
||
Vector3 destination = targetPos + new Vector3(Mathf.Sin(rad), 0f, Mathf.Cos(rad)) * radius;
|
||
|
||
destination = ClampDestinationToSafeBand(targetPos, destination);
|
||
|
||
return TrySetValidDestination(destination);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 前后踱步航点: 每次生成航点时实时判断前进/后退方向。
|
||
/// 按距离与距离带中线的关系决定:
|
||
/// - 距离 ≤ 中线: 偏向后退
|
||
/// - 距离 > 中线: 偏向前进
|
||
/// 确保航点不会卡在边界上。
|
||
/// </summary>
|
||
private bool GenerateAdvanceRetreatWaypoint()
|
||
{
|
||
Vector3 targetPos = m_Target.Value.transform.position;
|
||
Vector3 toSelf = GetFlatOffsetFromTarget();
|
||
float currentDist = toSelf.magnitude;
|
||
|
||
// 如果太近,强制后退到中心半径
|
||
if (currentDist < SafeMinDistance)
|
||
{
|
||
Vector3 retreatDir = currentDist > 0.01f ? toSelf.normalized : -transform.forward;
|
||
Vector3 safePos = targetPos + retreatDir * CenterRadius;
|
||
return TrySetValidDestination(safePos);
|
||
}
|
||
|
||
// --- 实时决定方向 ---
|
||
float center = CenterRadius;
|
||
float advanceRetreatSign;
|
||
|
||
// 距离中线的偏移量: 正 = 偏远, 负 = 偏近
|
||
float offsetFromCenter = currentDist - center;
|
||
float bandHalfWidth = (SafeMaxDistance - SafeMinDistance) * 0.5f;
|
||
|
||
if (bandHalfWidth < 0.1f)
|
||
{
|
||
// 距离带极窄, 强制后退
|
||
advanceRetreatSign = -1f;
|
||
}
|
||
else
|
||
{
|
||
// 偏离中线越远, 回到中线方向的概率越大
|
||
// normalizedOffset: -1(在最小距离处) ~ +1(在最大距离处)
|
||
float normalizedOffset = Mathf.Clamp(offsetFromCenter / bandHalfWidth, -1f, 1f);
|
||
|
||
// 基础概率: 前进概率 = 0.5 + normalizedOffset * 0.4
|
||
// 在最小距离: 前进概率 10% (几乎只后退)
|
||
// 在中线: 前进概率 50% (五五开)
|
||
// 在最大距离: 前进概率 90% (几乎只前进)
|
||
float advanceProbability = 0.5f + normalizedOffset * 0.4f;
|
||
advanceRetreatSign = Random.value < advanceProbability ? 1f : -1f;
|
||
}
|
||
|
||
// --- 计算目标距离 ---
|
||
Vector3 radialDir = toSelf.normalized;
|
||
float stepDistance = Random.Range(m_AdvanceRetreatDistanceMin.Value, m_AdvanceRetreatDistanceMax.Value);
|
||
|
||
// 靠近: dist 减小, 远离: dist 增大
|
||
float newDist = currentDist - stepDistance * advanceRetreatSign;
|
||
|
||
// 钳位到含余量的安全范围
|
||
newDist = Mathf.Clamp(newDist, ClampMinDistance, ClampMaxDistance);
|
||
|
||
// --- 航点太近检查 ---
|
||
float actualMoveDist = Mathf.Abs(newDist - currentDist);
|
||
if (actualMoveDist < k_MinMeaningfulDistance)
|
||
{
|
||
// 航点距当前位置太近,翻转方向重新计算
|
||
advanceRetreatSign = -advanceRetreatSign;
|
||
newDist = currentDist - stepDistance * advanceRetreatSign;
|
||
newDist = Mathf.Clamp(newDist, ClampMinDistance, ClampMaxDistance);
|
||
|
||
actualMoveDist = Mathf.Abs(newDist - currentDist);
|
||
if (actualMoveDist < k_MinMeaningfulDistance)
|
||
{
|
||
// 两个方向都没有足够空间,放弃此模式
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Vector3 destination = targetPos + radialDir * newDist;
|
||
|
||
return TrySetValidDestination(destination);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将目标点钳位到含余量的安全距离带内。
|
||
/// </summary>
|
||
private Vector3 ClampDestinationToSafeBand(Vector3 targetPos, Vector3 destination)
|
||
{
|
||
Vector3 fromTarget = destination - targetPos;
|
||
fromTarget.y = 0f;
|
||
float dist = fromTarget.magnitude;
|
||
|
||
if (dist < 0.01f)
|
||
{
|
||
return targetPos + (-transform.forward) * CenterRadius;
|
||
}
|
||
|
||
float clampedDist = Mathf.Clamp(dist, ClampMinDistance, ClampMaxDistance);
|
||
|
||
if (Mathf.Abs(clampedDist - dist) > 0.01f)
|
||
{
|
||
return targetPos + fromTarget.normalized * clampedDist;
|
||
}
|
||
|
||
return destination;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试设置有效航点。成功时标记 _hasValidDestination 并启动 Agent。
|
||
/// </summary>
|
||
private bool TrySetValidDestination(Vector3 destination)
|
||
{
|
||
if (TrySampleAndSetDestination(destination))
|
||
{
|
||
_hasValidDestination = true;
|
||
EnsureAgentMoving();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region NavMesh Utility
|
||
|
||
private void StopAgent()
|
||
{
|
||
if (_agent.isOnNavMesh && !_agent.isStopped)
|
||
{
|
||
_agent.isStopped = true;
|
||
_agent.velocity = Vector3.zero;
|
||
}
|
||
}
|
||
|
||
private void EnsureAgentMoving()
|
||
{
|
||
if (_agent.isOnNavMesh && _agent.isStopped)
|
||
{
|
||
_agent.isStopped = false;
|
||
}
|
||
}
|
||
|
||
private bool TrySampleAndSetDestination(Vector3 destination)
|
||
{
|
||
Vector3 sampledPos = destination;
|
||
if (SamplePosition(ref sampledPos))
|
||
{
|
||
sampledPos = ClampDestinationToSafeBand(m_Target.Value.transform.position, sampledPos);
|
||
SetDestination(sampledPos);
|
||
return true;
|
||
}
|
||
|
||
Vector3 targetPos = m_Target.Value.transform.position;
|
||
for (int i = 1; i <= k_MaxSampleRetries; i++)
|
||
{
|
||
Vector3 safeCenter = targetPos + (destination - targetPos).normalized * CenterRadius;
|
||
Vector3 retryPos = Vector3.Lerp(destination, safeCenter, i * 0.2f);
|
||
if (SamplePosition(ref retryPos))
|
||
{
|
||
retryPos = ClampDestinationToSafeBand(targetPos, retryPos);
|
||
SetDestination(retryPos);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Reset & Debug
|
||
|
||
public override void Reset()
|
||
{
|
||
base.Reset();
|
||
|
||
m_Target = null;
|
||
m_MinDistance = 4f;
|
||
m_MaxDistance = 7f;
|
||
m_StrafeSpeed = 2.5f;
|
||
m_AdvanceRetreatSpeed = 1.5f;
|
||
m_RotationSpeed = 360f;
|
||
m_StrafeArcMin = 30f;
|
||
m_StrafeArcMax = 90f;
|
||
m_AdvanceRetreatDistanceMin = 1f;
|
||
m_AdvanceRetreatDistanceMax = 3f;
|
||
m_StrafeWeight = 5f;
|
||
m_AdvanceRetreatWeight = 3f;
|
||
m_PauseWeight = 2f;
|
||
m_ModeDurationMin = 1f;
|
||
m_ModeDurationMax = 3f;
|
||
m_TransitionPause = 0.2f;
|
||
m_IsInfinite = false;
|
||
m_MaxDuration = 8f;
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
protected override void OnDrawGizmosSelected()
|
||
{
|
||
if (m_Target == null || m_Target.Value == null) return;
|
||
|
||
Vector3 targetPos = m_Target.Value.transform.position;
|
||
|
||
// 最小距离 (红色 — 不可进入)
|
||
UnityEditor.Handles.color = new Color(1f, 0.3f, 0.3f, 0.3f);
|
||
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, m_MinDistance.Value);
|
||
|
||
// 有效活动范围 (黄色实线)
|
||
UnityEditor.Handles.color = new Color(1f, 0.8f, 0f, 0.25f);
|
||
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, m_MinDistance.Value + k_DistanceMargin);
|
||
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, Mathf.Max(m_MaxDistance.Value - k_DistanceMargin, m_MinDistance.Value + k_DistanceMargin + 0.1f));
|
||
|
||
// 最大距离 (蓝色)
|
||
UnityEditor.Handles.color = new Color(0.3f, 0.6f, 1f, 0.3f);
|
||
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, m_MaxDistance.Value);
|
||
|
||
// 中心半径 (青色)
|
||
UnityEditor.Handles.color = new Color(0f, 1f, 1f, 0.1f);
|
||
UnityEditor.Handles.DrawWireDisc(targetPos, Vector3.up, (m_MinDistance.Value + m_MaxDistance.Value) * 0.5f);
|
||
}
|
||
#endif
|
||
|
||
#endregion
|
||
}
|
||
}
|