多轮审查和修复
This commit is contained in:
27
Assets/Scripts/Enemies/AI/BD_CanAttack.cs
Normal file
27
Assets/Scripts/Enemies/AI/BD_CanAttack.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Conditional:攻击冷却是否完毕(可以发动攻击)。
|
||||
/// </summary>
|
||||
public class BD_CanAttack : Conditional
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
return _enemy.CanAttack() ? TaskStatus.Success : TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_CanAttack.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_CanAttack.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 795ff4e1aa5e05b4b86fe8604822e2c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
31
Assets/Scripts/Enemies/AI/BD_EnterPhase.cs
Normal file
31
Assets/Scripts/Enemies/AI/BD_EnterPhase.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:Boss 切换阶段(Phase)。
|
||||
/// 调用 BossBase.EnterPhase(PhaseIndex),单帧完成,返回 Success。
|
||||
/// </summary>
|
||||
public class BD_EnterPhase : Action
|
||||
{
|
||||
[UnityEngine.SerializeField] private int m_PhaseIndex = 1;
|
||||
|
||||
private BossBase _boss;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_boss = GetComponent<BossBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_boss == null) return TaskStatus.Failure;
|
||||
_boss.EnterPhase(m_PhaseIndex);
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_EnterPhase.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_EnterPhase.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b532ff86847a744c801b1b6c4bae0c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/Scripts/Enemies/AI/BD_FaceTarget.cs
Normal file
32
Assets/Scripts/Enemies/AI/BD_FaceTarget.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:立即朝向玩家,单帧完成,返回 Success。
|
||||
/// </summary>
|
||||
public class BD_FaceTarget : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
private Transform _player;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
var go = GameObject.FindWithTag("Player");
|
||||
_player = go != null ? go.transform : null;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null || _player == null) return TaskStatus.Failure;
|
||||
_enemy.FacePlayer();
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_FaceTarget.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_FaceTarget.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ed2d3259efee514b8e17bc22ca021e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/Scripts/Enemies/AI/BD_IsGrounded.cs
Normal file
27
Assets/Scripts/Enemies/AI/BD_IsGrounded.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Conditional:敌人是否处于地面。
|
||||
/// </summary>
|
||||
public class BD_IsGrounded : Conditional
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null || _enemy.Movement == null) return TaskStatus.Failure;
|
||||
return _enemy.Movement.IsGrounded ? TaskStatus.Success : TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_IsGrounded.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_IsGrounded.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3047372de5d51a4f98aee8505490bf1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/Scripts/Enemies/AI/BD_IsHPBelow.cs
Normal file
32
Assets/Scripts/Enemies/AI/BD_IsHPBelow.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Conditional:Boss HP 是否低于阈值(用于触发阶段切换)。
|
||||
/// HPThreshold 为 0~1 归一化比例(如 0.5 = HP ≤ 50%)。
|
||||
/// </summary>
|
||||
public class BD_IsHPBelow : Conditional
|
||||
{
|
||||
[UnityEngine.SerializeField] private float m_HPThreshold = 0.5f;
|
||||
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null || _enemy.Stats == null) return TaskStatus.Failure;
|
||||
|
||||
float ratio = (float)_enemy.Stats.CurrentHP / _enemy.Stats.MaxHP;
|
||||
return ratio <= m_HPThreshold ? TaskStatus.Success : TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_IsHPBelow.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_IsHPBelow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1b683b9405d60c4daacd09719b186b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/Scripts/Enemies/AI/BD_IsNearEdge.cs
Normal file
28
Assets/Scripts/Enemies/AI/BD_IsNearEdge.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Conditional:敌人是否靠近平台边缘。
|
||||
/// 通过 EnemyNavAgent.IsNearEdge() 检测(双射线检测脚下/前方地面)。
|
||||
/// </summary>
|
||||
public class BD_IsNearEdge : Conditional
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null || _enemy.Nav == null) return TaskStatus.Failure;
|
||||
return _enemy.Nav.IsNearEdge() ? TaskStatus.Success : TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_IsNearEdge.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_IsNearEdge.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 008a71992a8cb644a94c331e06cd3955
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/Scripts/Enemies/AI/BD_IsPlayerVisible.cs
Normal file
28
Assets/Scripts/Enemies/AI/BD_IsPlayerVisible.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Conditional:玩家是否可见(LOS 检测)。
|
||||
/// 读取 EnemyBase._losResult 字段(由 BatchLOSSystem 每帧写入,或降级 3 帧节流 Raycast)。
|
||||
/// </summary>
|
||||
public class BD_IsPlayerVisible : Conditional
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
return _enemy.IsPlayerVisible() ? TaskStatus.Success : TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_IsPlayerVisible.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_IsPlayerVisible.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f8a58f1dbebf644999205d1a2febdc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
Assets/Scripts/Enemies/AI/BD_IsStateMatch.cs
Normal file
43
Assets/Scripts/Enemies/AI/BD_IsStateMatch.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Conditionals;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Conditional:敌人当前 EnemyStateType 是否与目标状态名称匹配。
|
||||
/// TargetStateName 直接输入枚举名称字符串(Controlled / Hurt / Stagger / Dead),
|
||||
/// 枚举值顺序变化时 BD 图不会静默失效。
|
||||
/// </summary>
|
||||
public class BD_IsStateMatch : Conditional
|
||||
{
|
||||
/// <summary>目标状态名称(Controlled / Hurt / Stagger / Dead)。</summary>
|
||||
[SerializeField] private string m_TargetStateName = "Controlled";
|
||||
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
|
||||
if (!System.Enum.TryParse<EnemyStateType>(m_TargetStateName, out var target))
|
||||
{
|
||||
Debug.LogError($"[BD_IsStateMatch] 未知状态名: '{m_TargetStateName}'," +
|
||||
"有效值为 Controlled / Hurt / Stagger / Dead", gameObject);
|
||||
return TaskStatus.Failure;
|
||||
}
|
||||
|
||||
return _enemy.CurrentState == target
|
||||
? TaskStatus.Success
|
||||
: TaskStatus.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_IsStateMatch.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_IsStateMatch.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 058372e0cdf7c5b40b53838cf89b8daf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
45
Assets/Scripts/Enemies/AI/BD_JumpTo.cs
Normal file
45
Assets/Scripts/Enemies/AI/BD_JumpTo.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:跳跃至目标坐标(抛物线跳跃)。
|
||||
/// 调用 EnemyBase.JumpTo,待落地(IsGrounded)后返回 Success。
|
||||
/// </summary>
|
||||
public class BD_JumpTo : Action
|
||||
{
|
||||
[SerializeField] private Vector2 m_Target;
|
||||
|
||||
private EnemyBase _enemy;
|
||||
private bool _jumped;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
_jumped = false;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
|
||||
if (!_jumped)
|
||||
{
|
||||
_enemy.JumpTo(m_Target);
|
||||
_jumped = true;
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
// 等待落地
|
||||
if (_enemy.Movement != null && _enemy.Movement.IsGrounded)
|
||||
return TaskStatus.Success;
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_JumpTo.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_JumpTo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7907401e39b5fdf409449af475640396
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
Assets/Scripts/Enemies/AI/BD_MoveTo.cs
Normal file
42
Assets/Scripts/Enemies/AI/BD_MoveTo.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:移动到指定世界坐标 Target。
|
||||
/// 到达目标(IsAtDestination)后返回 Success。
|
||||
/// </summary>
|
||||
public class BD_MoveTo : Action
|
||||
{
|
||||
[SerializeField] private Vector2 m_Target;
|
||||
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
|
||||
_enemy.MoveTo(m_Target);
|
||||
|
||||
if (_enemy.Nav != null && _enemy.Nav.IsAtDestination())
|
||||
return TaskStatus.Success;
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
_enemy?.StopMovement();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_MoveTo.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_MoveTo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 460aa03baecb5b547980f7bab7cfee22
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,4 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
@@ -8,26 +7,24 @@ namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:移向玩家。
|
||||
/// OnStart 缓存玩家 Transform,OnUpdate 每帧更新导航目标。
|
||||
/// OnStart 从 EnemyBase.PlayerTransform 获取玩家引用(由 _onPlayerSpawned 事件缓存,
|
||||
/// 避免 FindWithTag 全场景扫描)。OnUpdate 每帧更新导航目标。
|
||||
/// </summary>
|
||||
public class BD_MoveToPlayer : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
private Transform _playerTransform;
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
var playerGO = GameObject.FindWithTag("Player");
|
||||
if (playerGO != null) _playerTransform = playerGO.transform;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
if (_playerTransform == null) return TaskStatus.Failure;
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
if (_enemy.PlayerTransform == null) return TaskStatus.Failure;
|
||||
|
||||
_enemy.MoveTo(_playerTransform.position);
|
||||
_enemy.MoveTo(_enemy.PlayerTransform.position);
|
||||
_enemy.FacePlayer();
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,25 @@
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:敌人巡逻行为(Phase 1 简单实现)。
|
||||
/// 挂在 BT 节点上,持续令敌人向当前朝向移动。
|
||||
/// BD Action:敌人巡逻行为。
|
||||
/// 持续令敌人向当前朝向移动,遇墙/边缘时自动转向。
|
||||
/// </summary>
|
||||
public class BD_Patrol : Action
|
||||
{
|
||||
[Tooltip("检测地面边缘的向下射线长度")]
|
||||
public float edgeCheckLength = 1.2f;
|
||||
[Tooltip("检测障碍物的水平射线长度")]
|
||||
public float wallCheckLength = 0.4f;
|
||||
[Tooltip("地面/墙壁 LayerMask")]
|
||||
public LayerMask groundLayer;
|
||||
|
||||
private EnemyBase _enemy;
|
||||
private float _dir = 1f;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
@@ -21,8 +30,11 @@ namespace BaseGames.Enemies.AI
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
// Phase 1:固定向右巡逻;Phase 2 改为往返检测
|
||||
_enemy.MoveInDirection(1f);
|
||||
|
||||
if (ShouldFlip())
|
||||
_dir = -_dir;
|
||||
|
||||
_enemy.MoveInDirection(_dir);
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
|
||||
@@ -30,6 +42,23 @@ namespace BaseGames.Enemies.AI
|
||||
{
|
||||
_enemy?.StopMovement();
|
||||
}
|
||||
|
||||
private bool ShouldFlip()
|
||||
{
|
||||
Transform t = _enemy.transform;
|
||||
Vector2 pos = t.position;
|
||||
|
||||
// 前方边缘检测:在脚前方向下射线,若无地面则转向
|
||||
Vector2 edgeOrigin = pos + Vector2.right * (_dir * 0.3f) + Vector2.down * 0.1f;
|
||||
bool hasGround = Physics2D.Raycast(edgeOrigin, Vector2.down, edgeCheckLength, groundLayer);
|
||||
if (!hasGround) return true;
|
||||
|
||||
// 前方障碍检测:水平射线,若撞墙则转向
|
||||
bool hitWall = Physics2D.Raycast(pos, Vector2.right * _dir, wallCheckLength, groundLayer);
|
||||
if (hitWall) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
37
Assets/Scripts/Enemies/AI/BD_PlayAnimation.cs
Normal file
37
Assets/Scripts/Enemies/AI/BD_PlayAnimation.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:通过 AnimationClip 名称播放 Animancer 动画,立即返回 Success。
|
||||
/// ClipName 需与 EnemyAnimationConfigSO 中字段名一致。
|
||||
/// </summary>
|
||||
public class BD_PlayAnimation : Action
|
||||
{
|
||||
/// <summary>EnemyAnimationConfigSO 中的 AnimationClip 字段名(如 "Attack_Melee")。</summary>
|
||||
[SerializeField] private string m_ClipName = "Idle";
|
||||
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null || _enemy.AnimConfig == null) return TaskStatus.Failure;
|
||||
|
||||
var clip = _enemy.AnimConfig.GetClipByName(m_ClipName);
|
||||
if (clip != null)
|
||||
_enemy.Animancer?.Play(clip);
|
||||
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_PlayAnimation.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_PlayAnimation.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3247bf3e6a748dc408bbf0d6ac7e1680
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
29
Assets/Scripts/Enemies/AI/BD_SetAlert.cs
Normal file
29
Assets/Scripts/Enemies/AI/BD_SetAlert.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:设置警觉状态,并通知 EnemyBase 调整 BT Tick 频率。
|
||||
/// </summary>
|
||||
public class BD_SetAlert : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
|
||||
_enemy.SetAggroTickRate(true);
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_SetAlert.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_SetAlert.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f8deae75c691904192374ccab313a2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
Assets/Scripts/Enemies/AI/BD_SpawnProjectile.cs
Normal file
39
Assets/Scripts/Enemies/AI/BD_SpawnProjectile.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Pool;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:在敌人当前位置生成弹射物。
|
||||
/// ProjectileKey 为 Addressable 键,通过 GlobalObjectPool 实例化。
|
||||
/// </summary>
|
||||
public class BD_SpawnProjectile : Action
|
||||
{
|
||||
[SerializeField] private Vector2 m_Direction = Vector2.right;
|
||||
[SerializeField] private string m_ProjectileKey = "";
|
||||
[SerializeField] private float m_Speed = 8f;
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_ProjectileKey)) return TaskStatus.Failure;
|
||||
|
||||
var pool = ServiceLocator.GetOrDefault<IObjectPoolService>();
|
||||
if (pool == null) return TaskStatus.Failure;
|
||||
|
||||
var go = pool.Spawn(m_ProjectileKey, transform.position, Quaternion.identity);
|
||||
if (go != null)
|
||||
{
|
||||
var rb = go.GetComponent<Rigidbody2D>();
|
||||
if (rb != null)
|
||||
rb.velocity = m_Direction.normalized * m_Speed;
|
||||
}
|
||||
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_SpawnProjectile.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_SpawnProjectile.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85547509455668b43971ceccfd36d609
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/Scripts/Enemies/AI/BD_StopMovement.cs
Normal file
28
Assets/Scripts/Enemies/AI/BD_StopMovement.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:立即停止移动,单帧完成,返回 Success。
|
||||
/// </summary>
|
||||
public class BD_StopMovement : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
_enemy.StopMovement();
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_StopMovement.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_StopMovement.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ff201baacc362345a79874bc2a56b74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
Assets/Scripts/Enemies/AI/BD_SummonMinions.cs
Normal file
43
Assets/Scripts/Enemies/AI/BD_SummonMinions.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Pool;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:Boss 专用召唤小兵。
|
||||
/// 通过 GlobalObjectPool 在 Boss 周围生成 MinionPrefabKey 对应的敌人。
|
||||
/// </summary>
|
||||
public class BD_SummonMinions : Action
|
||||
{
|
||||
[SerializeField] private string m_MinionPrefabKey = "";
|
||||
[SerializeField] private int m_Count = 2;
|
||||
[SerializeField] private float m_SpawnRadius = 3f;
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_MinionPrefabKey)) return TaskStatus.Failure;
|
||||
|
||||
var pool = ServiceLocator.GetOrDefault<IObjectPoolService>();
|
||||
if (pool == null) return TaskStatus.Failure;
|
||||
|
||||
for (int i = 0; i < m_Count; i++)
|
||||
{
|
||||
var angle = Random.Range(0f, 360f) * Mathf.Deg2Rad;
|
||||
var offset = new Vector2(Mathf.Cos(angle), 0f) * m_SpawnRadius;
|
||||
var spawnPos = new Vector3(
|
||||
transform.position.x + offset.x,
|
||||
transform.position.y + offset.y,
|
||||
0f);
|
||||
pool.Spawn(m_MinionPrefabKey, spawnPos, Quaternion.identity);
|
||||
}
|
||||
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_SummonMinions.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_SummonMinions.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8fe673f758db6ef4c81938d19b7a4fde
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/Scripts/Enemies/AI/BD_TelegraphAttack.cs
Normal file
40
Assets/Scripts/Enemies/AI/BD_TelegraphAttack.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies.Boss.Patterns;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:触发攻击预警(TelegraphSystem),等待 Duration 秒后返回 Success。
|
||||
/// 对应架构 07_EnemyModule §8 BD_TelegraphAttack;与 TelegraphSystem 协作。
|
||||
/// </summary>
|
||||
public class BD_TelegraphAttack : Action
|
||||
{
|
||||
[SerializeField] private float m_Duration = 1f;
|
||||
[SerializeField] private string m_VfxKey = "";
|
||||
|
||||
private TelegraphSystem _telegraph;
|
||||
private float _elapsed;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_telegraph = GetComponent<TelegraphSystem>();
|
||||
_elapsed = 0f;
|
||||
|
||||
if (_telegraph != null && !string.IsNullOrEmpty(m_VfxKey))
|
||||
{
|
||||
_telegraph.StartCoroutine(
|
||||
_telegraph.ShowTelegraph(m_VfxKey, m_Duration, transform.position));
|
||||
}
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
_elapsed += Time.deltaTime;
|
||||
return _elapsed >= m_Duration ? TaskStatus.Success : TaskStatus.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_TelegraphAttack.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_TelegraphAttack.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbc61c8ed12b2bb4b9c50b6aa1046576
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
23
Assets/Scripts/Enemies/AI/BD_TeleportTo.cs
Normal file
23
Assets/Scripts/Enemies/AI/BD_TeleportTo.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:瞬移(传送)到目标坐标,Boss 专用。
|
||||
/// 单帧直接修改 transform.position,不使用导航。
|
||||
/// </summary>
|
||||
public class BD_TeleportTo : Action
|
||||
{
|
||||
[SerializeField] private Vector2 m_Target;
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
transform.position = new Vector3(m_Target.x, m_Target.y, transform.position.z);
|
||||
return TaskStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_TeleportTo.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_TeleportTo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9d4cc838f773cd44ad029c86f722906
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
30
Assets/Scripts/Enemies/AI/BD_Wait.cs
Normal file
30
Assets/Scripts/Enemies/AI/BD_Wait.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:等待固定 Duration 秒后返回 Success。
|
||||
/// 适用于攻击前摇、冷却间隔等固定等待。
|
||||
/// </summary>
|
||||
public class BD_Wait : Action
|
||||
{
|
||||
[UnityEngine.SerializeField] private float m_Duration = 1f;
|
||||
|
||||
private float _elapsed;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_elapsed = 0f;
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
_elapsed += Time.deltaTime;
|
||||
return _elapsed >= m_Duration ? TaskStatus.Success : TaskStatus.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_Wait.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_Wait.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a145269c2d2c3094994ff16e72ba4d68
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/Scripts/Enemies/AI/BD_WaitForAnimation.cs
Normal file
34
Assets/Scripts/Enemies/AI/BD_WaitForAnimation.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using Opsive.BehaviorDesigner.Runtime;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
using BaseGames.Enemies;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:等待当前 Animancer 动画播放完毕再返回 Success。
|
||||
/// 通过 EnemyBase.IsAnimationComplete 轮询(Animancer CurrentState.NormalizedTime >= 1)。
|
||||
/// </summary>
|
||||
public class BD_WaitForAnimation : Action
|
||||
{
|
||||
private EnemyBase _enemy;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_enemy = GetComponent<EnemyBase>();
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
if (_enemy == null) return TaskStatus.Failure;
|
||||
|
||||
var state = _enemy.Animancer?.States?.Current;
|
||||
if (state == null) return TaskStatus.Success; // 无动画直接继续
|
||||
if (state.NormalizedTime >= 1f) return TaskStatus.Success;
|
||||
|
||||
return TaskStatus.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_WaitForAnimation.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_WaitForAnimation.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 437d55b7c77973b4c8a80b688ab153d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Assets/Scripts/Enemies/AI/BD_WaitRandom.cs
Normal file
33
Assets/Scripts/Enemies/AI/BD_WaitRandom.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
#if GRAPH_DESIGNER
|
||||
using UnityEngine;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks;
|
||||
using Opsive.BehaviorDesigner.Runtime.Tasks.Actions;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// BD Action:在 [Min, Max] 范围内随机等待后返回 Success。
|
||||
/// 适用于巡逻间隔、随机攻击延迟等自然行为。
|
||||
/// </summary>
|
||||
public class BD_WaitRandom : Action
|
||||
{
|
||||
[UnityEngine.SerializeField] private float m_Min = 0.5f;
|
||||
[UnityEngine.SerializeField] private float m_Max = 2.0f;
|
||||
|
||||
private float _elapsed;
|
||||
private float _target;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_elapsed = 0f;
|
||||
_target = Random.Range(m_Min, m_Max);
|
||||
}
|
||||
|
||||
public override TaskStatus OnUpdate()
|
||||
{
|
||||
_elapsed += Time.deltaTime;
|
||||
return _elapsed >= _target ? TaskStatus.Success : TaskStatus.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
11
Assets/Scripts/Enemies/AI/BD_WaitRandom.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BD_WaitRandom.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15e39022725082a44a41a79a7a655814
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -9,6 +9,7 @@
|
||||
"rootNamespace": "BaseGames.Enemies.AI",
|
||||
"references": [
|
||||
"BaseGames.Enemies",
|
||||
"BaseGames.Enemies.Boss.Patterns",
|
||||
"Opsive.BehaviorDesigner.Runtime"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
|
||||
97
Assets/Scripts/Enemies/AI/BatchLOSSystem.cs
Normal file
97
Assets/Scripts/Enemies/AI/BatchLOSSystem.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Jobs;
|
||||
|
||||
namespace BaseGames.Enemies.AI
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量视线检测系统(架构 07_EnemyModule §12)。
|
||||
/// 每 FixedUpdate:
|
||||
/// 1. 读取上一帧 RaycastHit2D 结果,回调各注册者。
|
||||
/// 2. 重新构建本帧 RaycastCommand 批次,使用 Physics2D.RaycastAll 同步模式执行。
|
||||
///
|
||||
/// ⚠️ Unity 2022.3 中 Physics2D 批量命令(RaycastCommand2D)尚未稳定,
|
||||
/// 此实现使用 FixedUpdate 内顺序 Raycast2D(节流),确保零 GC 分配。
|
||||
/// 当敌人数量 > 20 时建议切换到 Job System RaycastCommand。
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-200)]
|
||||
public class BatchLOSSystem : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Min(1)] private int _maxRequestersPerFrame = 8;
|
||||
|
||||
private readonly List<ILOSRequester> _requesters = new();
|
||||
private readonly HashSet<ILOSRequester> _requesterSet = new(); // O(1) 包含查询
|
||||
// _indexMap 记录每个 requester 在 _requesters 中的下标,供 Unregister 实现 O(1) 删除
|
||||
private readonly Dictionary<ILOSRequester, int> _indexMap = new();
|
||||
private int _currentOffset = 0;
|
||||
|
||||
// ── 注册 ──────────────────────────────────────────────────────────
|
||||
public void Register(ILOSRequester requester)
|
||||
{
|
||||
if (_requesterSet.Add(requester))
|
||||
{
|
||||
_indexMap[requester] = _requesters.Count;
|
||||
_requesters.Add(requester);
|
||||
}
|
||||
}
|
||||
|
||||
public void Unregister(ILOSRequester requester)
|
||||
{
|
||||
if (!_requesterSet.Remove(requester)) return;
|
||||
|
||||
int idx = _indexMap[requester];
|
||||
int last = _requesters.Count - 1;
|
||||
|
||||
// Swap-and-pop:将末尾元素移动到被删除的位置,避免 O(n) 搬移
|
||||
if (idx != last)
|
||||
{
|
||||
var moved = _requesters[last];
|
||||
_requesters[idx] = moved;
|
||||
_indexMap[moved] = idx;
|
||||
}
|
||||
|
||||
_requesters.RemoveAt(last);
|
||||
_indexMap.Remove(requester);
|
||||
|
||||
// 修正偏移量,防止越界
|
||||
if (_currentOffset >= _requesters.Count) _currentOffset = 0;
|
||||
}
|
||||
|
||||
// ── FixedUpdate ───────────────────────────────────────────────────
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (_requesters.Count == 0) return;
|
||||
|
||||
// 每帧轮询部分请求者(均匀分配,避免单帧全量射线)
|
||||
int count = Mathf.Min(_maxRequestersPerFrame, _requesters.Count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int idx = (_currentOffset + i) % _requesters.Count;
|
||||
var requester = _requesters[idx];
|
||||
|
||||
bool hasLOS = false;
|
||||
if (requester != null)
|
||||
{
|
||||
Vector2 origin = requester.LOSOrigin;
|
||||
Vector2 target = requester.LOSTarget;
|
||||
Vector2 direction = target - origin;
|
||||
float distance = direction.magnitude;
|
||||
|
||||
if (distance > 0.01f)
|
||||
{
|
||||
var hit = Physics2D.Raycast(origin, direction.normalized, distance, requester.LOSBlockingMask);
|
||||
// 若无遮挡物(hit.collider == null),则视线畅通
|
||||
hasLOS = hit.collider == null;
|
||||
}
|
||||
|
||||
|
||||
requester.ReceiveLOSResult(hasLOS);
|
||||
}
|
||||
}
|
||||
|
||||
_currentOffset = (_currentOffset + count) % Mathf.Max(1, _requesters.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/AI/BatchLOSSystem.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/BatchLOSSystem.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a482b11f99a870f4ea28cd36b716a69b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/Scripts/Enemies/AI/ILOSRequester.cs
Normal file
27
Assets/Scripts/Enemies/AI/ILOSRequester.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// BatchLOSSystem 的注册接口(架构 07_EnemyModule §12)。
|
||||
/// 实现此接口的 EnemyBase 子类可以注册到 BatchLOSSystem,
|
||||
/// 以批处理方式接收 LOS(Line of Sight)检测结果。
|
||||
/// </summary>
|
||||
public interface ILOSRequester
|
||||
{
|
||||
/// <summary>射线起点(通常是眼部位置)。</summary>
|
||||
Vector2 LOSOrigin { get; }
|
||||
|
||||
/// <summary>射线终点(通常是玩家位置)。</summary>
|
||||
Vector2 LOSTarget { get; }
|
||||
|
||||
/// <summary>遮挡 LOS 的物理图层。</summary>
|
||||
LayerMask LOSBlockingMask { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收 LOS 检测结果。
|
||||
/// <paramref name="hasLineOfSight"/>: true = 有视线,false = 被遮挡。
|
||||
/// </summary>
|
||||
void ReceiveLOSResult(bool hasLineOfSight);
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Enemies/AI/ILOSRequester.cs.meta
Normal file
11
Assets/Scripts/Enemies/AI/ILOSRequester.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c504654ba79100742a30b448b0233d63
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user