diff --git a/DungeonShooting_Godot/src/game/event/EventEnum.cs b/DungeonShooting_Godot/src/game/event/EventEnum.cs
index ec25a87..7e59f96 100644
--- a/DungeonShooting_Godot/src/game/event/EventEnum.cs
+++ b/DungeonShooting_Godot/src/game/event/EventEnum.cs
@@ -4,5 +4,8 @@
///
public enum EventEnum
{
- Test,
+ ///
+ /// 敌人发现玩家
+ ///
+ OnEnemyFindPlayer,
}
diff --git a/DungeonShooting_Godot/src/game/role/Role.cs b/DungeonShooting_Godot/src/game/role/Role.cs
index dc7063b..e3a2568 100644
--- a/DungeonShooting_Godot/src/game/role/Role.cs
+++ b/DungeonShooting_Godot/src/game/role/Role.cs
@@ -302,6 +302,14 @@
}
///
+ /// 获取当前角色的中心点坐标
+ ///
+ public Vector2 GetCenterPosition()
+ {
+ return MountPoint.GlobalPosition;
+ }
+
+ ///
/// 使角色看向指定的坐标,
/// 注意, 调用该函数会清空 LookTarget, 因为拥有 LookTarget 时也会每帧更新玩家视野位置
///
diff --git a/DungeonShooting_Godot/src/game/role/StateController.cs b/DungeonShooting_Godot/src/game/role/StateController.cs
index d3c40b4..f349561 100644
--- a/DungeonShooting_Godot/src/game/role/StateController.cs
+++ b/DungeonShooting_Godot/src/game/role/StateController.cs
@@ -8,6 +8,11 @@
public class StateController : Component where T : ActivityObject where S : Enum
{
///
+ /// 获取当前状态
+ ///
+ public S CurrState => CurrStateBase != null ? CurrStateBase.State : default;
+
+ ///
/// 当前活跃的状态对象实例
///
public StateBase CurrStateBase { get; private set; }
diff --git a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
index 3c3e222..3d9a920 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs
@@ -30,7 +30,7 @@
///
/// 敌人身上的状态机控制器
///
- public StateController StateController { get; }
+ public StateController StateController { get; }
///
/// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
@@ -63,10 +63,11 @@
public Position2D NavigationPoint { get; }
private float _enemyAttackTimer = 0;
+ private EventBinder _eventBinder;
public Enemy() : base(ResourcePath.prefab_role_Enemy_tscn)
{
- StateController = new StateController();
+ StateController = new StateController();
AddComponent(StateController);
AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Props | PhysicsLayer.Player;
@@ -88,14 +89,15 @@
StateController.Register(new AiNormalState());
StateController.Register(new AiProbeState());
StateController.Register(new AiTailAfterState());
- StateController.Register(new AIAttackState());
+ StateController.Register(new AiTargetInViewState());
+ StateController.Register(new AiLeaveForState());
}
public override void _Ready()
{
base._Ready();
//默认状态
- StateController.ChangeState(AIStateEnum.AINormal);
+ StateController.ChangeState(AiStateEnum.AiNormal);
NavigationAgent2D.SetTargetLocation(GameApplication.Instance.Room.Player.GlobalPosition);
}
@@ -106,11 +108,14 @@
{
_enemies.Add(this);
}
+ _eventBinder = EventManager.AddEventListener(EventEnum.OnEnemyFindPlayer, OnEnemyFindPlayer);
}
public override void _ExitTree()
{
_enemies.Remove(this);
+ _eventBinder.RemoveEventListener();
+ _eventBinder = null;
}
public override void _PhysicsProcess(float delta)
@@ -174,6 +179,23 @@
}
///
+ /// 返回目标点是否在跟随状态下的视野半径内
+ ///
+ public bool IsInTailAfterViewRange(Vector2 target)
+ {
+ var isForward = IsPositionInForward(target);
+ if (isForward)
+ {
+ if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
/// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null
///
public bool TestViewRayCast(Vector2 target)
@@ -191,4 +213,15 @@
{
ViewRay.Enabled = false;
}
+
+ //其他敌人发现玩家
+ private void OnEnemyFindPlayer(object obj)
+ {
+ var state = StateController.CurrState;
+ if (state != AiStateEnum.AiLeaveFor && state != AiStateEnum.AiTailAfter && state != AiStateEnum.AiTargetInView)
+ {
+ //前往指定地点
+ StateController.ChangeStateLate(AiStateEnum.AiLeaveFor, GameApplication.Instance.Room.Player.GetCenterPosition());
+ }
+ }
}
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AIAttackState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AIAttackState.cs
deleted file mode 100644
index ba2241e..0000000
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AIAttackState.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-
-using Godot;
-
-///
-/// 目标在视野范围内, 发起攻击
-///
-public class AIAttackState : StateBase
-{
-
- ///
- /// 是否在视野内
- ///
- public bool IsInView;
-
- //导航目标点刷新计时器
- private float _navigationUpdateTimer = 0;
- private float _navigationInterval = 0.3f;
-
- public AIAttackState() : base(AIStateEnum.AIAttack)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- _navigationUpdateTimer = 0;
- IsInView = true;
- }
-
- public override void PhysicsProcess(float delta)
- {
- var masterPos = Master.GlobalPosition;
- var playerPos = GameApplication.Instance.Room.Player.GlobalPosition;
-
- //更新玩家位置
- if (_navigationUpdateTimer <= 0)
- {
- //每隔一段时间秒更改目标位置
- _navigationUpdateTimer = _navigationInterval;
- if (Master.NavigationAgent2D.GetTargetLocation() != playerPos)
- {
- Master.NavigationAgent2D.SetTargetLocation(playerPos);
- }
- }
- else
- {
- _navigationUpdateTimer -= delta;
- }
-
- //计算移动
- var nextPos = Master.NavigationAgent2D.GetNextLocation();
- Master.LookTargetPosition(playerPos);
- Master.AnimatedSprite.Animation = AnimatorNames.Run;
- Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * Master.MoveSpeed;
- Master.CalcMove(delta);
-
- //检测玩家是否在视野内
- if (masterPos.DistanceSquaredTo(playerPos) <= Master.TailAfterViewRange * Master.TailAfterViewRange)
- {
- IsInView = !Master.TestViewRayCast(playerPos);
- Master.TestViewRayCastOver();
- }
- else
- {
- IsInView = false;
- }
-
- if (IsInView)
- {
- Master.EnemyAttack();
- }
- else
- {
- ChangeStateLate(AIStateEnum.AITailAfter);
- }
- }
-
- public override void DebugDraw()
- {
- var playerPos = GameApplication.Instance.Room.Player.GlobalPosition;
- Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Red);
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs
index 6e94ffa..d7f44b6 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs
@@ -1,20 +1,24 @@
-public enum AIStateEnum
+public enum AiStateEnum
{
///
/// ai状态, 正常, 未发现目标
///
- AINormal,
+ AiNormal,
///
/// 发现目标, 但不知道在哪
///
- AIProbe,
+ AiProbe,
+ ///
+ /// 收到其他敌人通知, 前往发现目标的位置
+ ///
+ AiLeaveFor,
///
/// 发现目标, 并且知道位置
///
- AITailAfter,
+ AiTailAfter,
///
- /// 目标在视野内, 发起攻击
+ /// 目标在视野内
///
- AIAttack,
+ AiTargetInView,
}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs
new file mode 100644
index 0000000..6cb48a9
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs
@@ -0,0 +1,70 @@
+
+using Godot;
+
+///
+/// 前往目标位置
+///
+public class AiLeaveForState : StateBase
+{
+ private Vector2 _targetPosition;
+
+ public AiLeaveForState() : base(AiStateEnum.AiLeaveFor)
+ {
+ }
+
+ public override void Enter(AiStateEnum prev, params object[] args)
+ {
+ if (args.Length > 0 && args[0] is Vector2 pos)
+ {
+ UpdateTargetPosition(pos);
+ }
+ else
+ {
+ ChangeState(AiStateEnum.AiNormal);
+ }
+ }
+
+ public override void PhysicsProcess(float delta)
+ {
+ //计算移动
+ var nextPos = Master.NavigationAgent2D.GetNextLocation();
+ Master.LookTargetPosition(_targetPosition);
+ Master.AnimatedSprite.Animation = AnimatorNames.Run;
+ Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() *
+ Master.MoveSpeed;
+ Master.CalcMove(delta);
+
+ var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
+ //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ if (!Master.TestViewRayCast(playerPos)) //看到玩家
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ //切换成发现目标状态
+ ChangeStateLate(AiStateEnum.AiTargetInView);
+ //派发发现玩家事件
+ EventManager.EmitEvent(EventEnum.OnEnemyFindPlayer);
+ return;
+ }
+ else
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ }
+
+ //移动到目标掉了, 还没发现目标
+ if (Master.NavigationAgent2D.IsNavigationFinished())
+ {
+ ChangeStateLate(AiStateEnum.AiNormal);
+ }
+ }
+
+ public void UpdateTargetPosition(Vector2 pos)
+ {
+ _targetPosition = pos;
+ Master.NavigationAgent2D.SetTargetLocation(pos);
+ }
+}
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
index 426f362..9e51c18 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs
@@ -4,7 +4,7 @@
///
/// AI 正常状态
///
-public class AiNormalState : StateBase
+public class AiNormalState : StateBase
{
//是否发现玩家
private bool _isFindPlayer;
@@ -24,11 +24,11 @@
//移动停顿计时器
private float _pauseTimer;
- public AiNormalState() : base(AIStateEnum.AINormal)
+ public AiNormalState() : base(AiStateEnum.AiNormal)
{
}
- public override void Enter(AIStateEnum prev, params object[] args)
+ public override void Enter(AiStateEnum prev, params object[] args)
{
_isFindPlayer = false;
_isMoveOver = true;
@@ -43,14 +43,14 @@
if (_isFindPlayer) //已经找到玩家了
{
//现临时处理, 直接切换状态
- ChangeStateLate(AIStateEnum.AITailAfter);
+ ChangeStateLate(AiStateEnum.AiTailAfter);
}
else //没有找到玩家
{
//检测玩家
var player = GameApplication.Instance.Room.Player;
//玩家中心点坐标
- var playerPos = player.MountPoint.GlobalPosition;
+ var playerPos = player.GetCenterPosition();
if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家
{
@@ -81,7 +81,7 @@
_isMoveOver = true;
}
}
-
+ //关闭射线检测
Master.TestViewRayCastOver();
}
}
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiProbeState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiProbeState.cs
index 4b1d6ca..9e610d1 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiProbeState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiProbeState.cs
@@ -2,9 +2,9 @@
///
/// Ai 不确定玩家位置
///
-public class AiProbeState : StateBase
+public class AiProbeState : StateBase
{
- public AiProbeState() : base(AIStateEnum.AIProbe)
+ public AiProbeState() : base(AiStateEnum.AiProbe)
{
}
}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
index c2331a8..1bd0e91 100644
--- a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs
@@ -2,14 +2,14 @@
using Godot;
///
-/// AI 发现玩家
+/// AI 发现玩家, 跟随玩家
///
-public class AiTailAfterState : StateBase
+public class AiTailAfterState : StateBase
{
///
/// 目标是否在视野半径内
///
- public bool IsInViewRange;
+ private bool _isInViewRange;
//导航目标点刷新计时器
private float _navigationUpdateTimer = 0;
@@ -17,22 +17,21 @@
//目标从视野消失时已经过去的时间
private float _viewTimer;
-
- public AiTailAfterState() : base(AIStateEnum.AITailAfter)
+
+ public AiTailAfterState() : base(AiStateEnum.AiTailAfter)
{
}
- public override void Enter(AIStateEnum prev, params object[] args)
+ public override void Enter(AiStateEnum prev, params object[] args)
{
- IsInViewRange = true;
+ _isInViewRange = true;
_navigationUpdateTimer = 0;
_viewTimer = 0;
}
-
+
public override void PhysicsProcess(float delta)
{
- var masterPos = Master.GlobalPosition;
- var playerPos = GameApplication.Instance.Room.Player.GlobalPosition;
+ var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
//更新玩家位置
if (_navigationUpdateTimer <= 0)
@@ -56,26 +55,29 @@
Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * Master.MoveSpeed;
Master.CalcMove(delta);
- //是否看到玩家'
- //检测玩家是否在视野内
- if (masterPos.DistanceSquaredTo(playerPos) <= Master.TailAfterViewRange * Master.TailAfterViewRange)
+ //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态
+ if (Master.IsInTailAfterViewRange(playerPos))
{
- if (!Master.TestViewRayCast(playerPos))
+ if (!Master.TestViewRayCast(playerPos)) //看到玩家
{
+ //关闭射线检测
Master.TestViewRayCastOver();
- //切换成攻击状态
- ChangeStateLate(AIStateEnum.AIAttack);
+ //切换成发现目标状态
+ ChangeStateLate(AiStateEnum.AiTargetInView);
+ //派发发现玩家事件
+ EventManager.EmitEvent(EventEnum.OnEnemyFindPlayer);
return;
}
else
{
+ //关闭射线检测
Master.TestViewRayCastOver();
}
}
- //检测玩家是否在视野内, 此时视野可穿墙, 直接检测距离即可
- IsInViewRange = masterPos.DistanceSquaredTo(playerPos) <= Master.ViewRange * Master.ViewRange;
- if (IsInViewRange)
+ //检测玩家是否在穿墙视野范围内, 直接检测距离即可
+ _isInViewRange = Master.IsInViewRange(playerPos);
+ if (_isInViewRange)
{
_viewTimer = 0;
}
@@ -83,7 +85,7 @@
{
if (_viewTimer > 10) //10秒
{
- ChangeStateLate(AIStateEnum.AINormal);
+ ChangeStateLate(AiStateEnum.AiNormal);
}
else
{
@@ -94,8 +96,8 @@
public override void DebugDraw()
{
- var playerPos = GameApplication.Instance.Room.Player.GlobalPosition;
- if (IsInViewRange)
+ var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
+ if (_isInViewRange)
{
Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Orange);
}
diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiTargetInViewState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiTargetInViewState.cs
new file mode 100644
index 0000000..1856869
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiTargetInViewState.cs
@@ -0,0 +1,82 @@
+
+using Godot;
+
+///
+/// 目标在视野范围内, 发起攻击
+///
+public class AiTargetInViewState : StateBase
+{
+
+ ///
+ /// 是否在视野内
+ ///
+ public bool IsInView;
+
+ //导航目标点刷新计时器
+ private float _navigationUpdateTimer = 0;
+ private float _navigationInterval = 0.3f;
+
+ public AiTargetInViewState() : base(AiStateEnum.AiTargetInView)
+ {
+ }
+
+ public override void Enter(AiStateEnum prev, params object[] args)
+ {
+ _navigationUpdateTimer = 0;
+ IsInView = true;
+ }
+
+ public override void PhysicsProcess(float delta)
+ {
+ var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
+
+ //更新玩家位置
+ if (_navigationUpdateTimer <= 0)
+ {
+ //每隔一段时间秒更改目标位置
+ _navigationUpdateTimer = _navigationInterval;
+ if (Master.NavigationAgent2D.GetTargetLocation() != playerPos)
+ {
+ Master.NavigationAgent2D.SetTargetLocation(playerPos);
+ }
+ }
+ else
+ {
+ _navigationUpdateTimer -= delta;
+ }
+
+ //计算移动
+ var nextPos = Master.NavigationAgent2D.GetNextLocation();
+ Master.LookTargetPosition(playerPos);
+ Master.AnimatedSprite.Animation = AnimatorNames.Run;
+ Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * Master.MoveSpeed;
+ Master.CalcMove(delta);
+
+ //检测玩家是否在视野内
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ IsInView = !Master.TestViewRayCast(playerPos);
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ else
+ {
+ IsInView = false;
+ }
+
+ if (IsInView)
+ {
+ Master.EnemyAttack();
+ }
+ else
+ {
+ ChangeStateLate(AiStateEnum.AiTailAfter);
+ }
+ }
+
+ public override void DebugDraw()
+ {
+ var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition();
+ Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Red);
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/room/RoomManager.cs b/DungeonShooting_Godot/src/game/room/RoomManager.cs
index 66265a7..9643006 100644
--- a/DungeonShooting_Godot/src/game/room/RoomManager.cs
+++ b/DungeonShooting_Godot/src/game/room/RoomManager.cs
@@ -112,7 +112,6 @@
public override void _Process(float delta)
{
- Enemy.UpdateEnemiesView();
if (GameApplication.Instance.Debug)
{
Update();