diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs b/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs
new file mode 100644
index 0000000..bbbd45f
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs
@@ -0,0 +1,381 @@
+
+using AiState;
+using Godot;
+
+///
+/// Ai角色
+///
+public abstract partial class AiRole : Role
+{
+ ///
+ /// 目标是否在视野内
+ ///
+ public bool TargetInView { get; set; } = true;
+
+ ///
+ /// 敌人身上的状态机控制器
+ ///
+ public StateController StateController { get; private set; }
+
+ ///
+ /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙
+ ///
+ [Export, ExportFillNode]
+ public RayCast2D ViewRay { get; set; }
+
+ ///
+ /// 导航代理
+ ///
+ [Export, ExportFillNode]
+ public NavigationAgent2D NavigationAgent2D { get; set; }
+
+ ///
+ /// 导航代理中点
+ ///
+ [Export, ExportFillNode]
+ public Marker2D NavigationPoint { get; set; }
+
+ ///
+ /// 不通过武发射子弹的开火点
+ ///
+ [Export, ExportFillNode]
+ public Marker2D FirePoint { get; set; }
+
+ ///
+ /// 当前敌人所看向的对象, 也就是枪口指向的对象
+ ///
+ public ActivityObject LookTarget { get; set; }
+
+ ///
+ /// 攻击锁定目标时间
+ ///
+ public float LockingTime { get; set; } = 1f;
+
+ ///
+ /// 锁定目标已经走过的时间
+ ///
+ public float LockTargetTime { get; set; } = 0;
+
+ ///
+ /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
+ ///
+ public float ViewRange { get; set; } = 250;
+
+ ///
+ /// 发现玩家后跟随玩家的视野半径
+ ///
+ public float TailAfterViewRange { get; set; } = 400;
+
+ ///
+ /// 背后的视野半径, 单位像素
+ ///
+ public float BackViewRange { get; set; } = 50;
+
+ ///
+ /// 攻击间隔时间, 秒
+ ///
+ public float AttackInterval { get; set; } = 0;
+
+ public override void OnInit()
+ {
+ base.OnInit();
+ IsAi = true;
+
+ StateController = AddComponent>();
+
+ //注册Ai状态机
+ StateController.Register(new AiNormalState());
+ StateController.Register(new AiTailAfterState());
+ StateController.Register(new AiFollowUpState());
+ StateController.Register(new AiLeaveForState());
+ StateController.Register(new AiSurroundState());
+ StateController.Register(new AiFindAmmoState());
+ StateController.Register(new AiAttackState());
+ StateController.Register(new AiAstonishedState());
+ StateController.Register(new AiNotifyState());
+
+ //默认状态
+ StateController.ChangeStateInstant(AIStateEnum.AiNormal);
+
+ //NavigationAgent2D.VelocityComputed += OnVelocityComputed;
+ }
+
+ ///
+ /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
+ ///
+ public bool CheckUsableWeaponInUnclaimed()
+ {
+ foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons)
+ {
+ //判断是否能拾起武器, 条件: 相同的房间
+ if (unclaimedWeapon.AffiliationArea == AffiliationArea)
+ {
+ if (!unclaimedWeapon.IsTotalAmmoEmpty())
+ {
+ if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
+ {
+ return true;
+ }
+ else
+ {
+ //判断是否可以移除该标记
+ var enemy = unclaimedWeapon.GetSign(SignNames.AiFindWeaponSign);
+ if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
+ {
+ unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
+ return true;
+ }
+ else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
+ {
+ unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 寻找可用的武器
+ ///
+ public Weapon FindTargetWeapon()
+ {
+ Weapon target = null;
+ var position = Position;
+ foreach (var weapon in World.Weapon_UnclaimedWeapons)
+ {
+ //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间
+ if (weapon.AffiliationArea == AffiliationArea)
+ {
+ //还有弹药
+ if (!weapon.IsTotalAmmoEmpty())
+ {
+ //查询是否有其他敌人标记要拾起该武器
+ if (weapon.HasSign(SignNames.AiFindWeaponSign))
+ {
+ var enemy = weapon.GetSign(SignNames.AiFindWeaponSign);
+ if (enemy == this) //就是自己标记的
+ {
+
+ }
+ else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
+ {
+ weapon.RemoveSign(SignNames.AiFindWeaponSign);
+ }
+ else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
+ {
+ weapon.RemoveSign(SignNames.AiFindWeaponSign);
+ }
+ else //放弃这把武器
+ {
+ continue;
+ }
+ }
+
+ if (target == null) //第一把武器
+ {
+ target = weapon;
+ }
+ else if (target.Position.DistanceSquaredTo(position) >
+ weapon.Position.DistanceSquaredTo(position)) //距离更近
+ {
+ target = weapon;
+ }
+ }
+ }
+ }
+
+ return target;
+ }
+
+ ///
+ /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
+ ///
+ /// 从最小到最大距离的过渡量, 0 - 1, 默认 0.5
+ public float GetWeaponRange(float weight = 0.5f)
+ {
+ if (WeaponPack.ActiveItem != null)
+ {
+ var attribute = WeaponPack.ActiveItem.Attribute;
+ return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight);
+ }
+
+ return 0;
+ }
+
+ ///
+ /// 返回目标点是否在视野范围内
+ ///
+ public virtual bool IsInViewRange(Vector2 target)
+ {
+ var isForward = IsPositionInForward(target);
+ if (isForward)
+ {
+ if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 返回目标点是否在跟随状态下的视野半径内
+ ///
+ public virtual bool IsInTailAfterViewRange(Vector2 target)
+ {
+ var isForward = IsPositionInForward(target);
+ if (isForward)
+ {
+ if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true
+ ///
+ public bool TestViewRayCast(Vector2 target)
+ {
+ ViewRay.Enabled = true;
+ ViewRay.TargetPosition = ViewRay.ToLocal(target);
+ ViewRay.ForceRaycastUpdate();
+ return ViewRay.IsColliding();
+ }
+
+ ///
+ /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
+ ///
+ public void TestViewRayCastOver()
+ {
+ ViewRay.Enabled = false;
+ }
+
+ ///
+ /// AI 拾起武器操作
+ ///
+ public void DoPickUpWeapon()
+ {
+ //这几个状态不需要主动拾起武器操作
+ var state = StateController.CurrState;
+ if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiNotify || state == AIStateEnum.AiAstonished || state == AIStateEnum.AiAttack)
+ {
+ return;
+ }
+
+ //拾起地上的武器
+ if (InteractiveItem is Weapon weapon)
+ {
+ if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起
+ {
+ TriggerInteractive();
+ return;
+ }
+
+ //没弹药了
+ if (weapon.IsTotalAmmoEmpty())
+ {
+ return;
+ }
+
+ var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id);
+ if (index != -1) //与武器背包中武器类型相同, 补充子弹
+ {
+ if (!WeaponPack.GetItem(index).IsAmmoFull())
+ {
+ TriggerInteractive();
+ }
+
+ return;
+ }
+
+ // var index2 = Holster.FindWeapon((we, i) =>
+ // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
+ var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty());
+ if (index2 != -1) //扔掉没子弹的武器
+ {
+ ThrowWeapon(index2);
+ TriggerInteractive();
+ return;
+ }
+
+ // if (Holster.HasVacancy()) //有空位, 拾起武器
+ // {
+ // TriggerInteractive();
+ // return;
+ // }
+ }
+ }
+
+ ///
+ /// 获取锁定目标的剩余时间
+ ///
+ public float GetLockRemainderTime()
+ {
+ var weapon = WeaponPack.ActiveItem;
+ if (weapon == null)
+ {
+ return LockingTime - LockTargetTime;
+ }
+ return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime;
+ }
+
+ public override void LookTargetPosition(Vector2 pos)
+ {
+ LookTarget = null;
+ base.LookTargetPosition(pos);
+ }
+
+ ///
+ /// 执行移动操作
+ ///
+ public void DoMove()
+ {
+ // //计算移动
+ // NavigationAgent2D.MaxSpeed = EnemyRoleState.MoveSpeed;
+ // var nextPos = NavigationAgent2D.GetNextPathPosition();
+ // NavigationAgent2D.Velocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
+
+ AnimatedSprite.Play(AnimatorNames.Run);
+ //计算移动
+ var nextPos = NavigationAgent2D.GetNextPathPosition();
+ BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
+ }
+
+ ///
+ /// 执行站立操作
+ ///
+ public void DoIdle()
+ {
+ AnimatedSprite.Play(AnimatorNames.Idle);
+ BasisVelocity = Vector2.Zero;
+ }
+
+ ///
+ /// 更新房间中标记的目标位置
+ ///
+ public void UpdateMarkTargetPosition()
+ {
+ if (LookTarget != null)
+ {
+ AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position;
+ }
+ }
+
+ // private void OnVelocityComputed(Vector2 velocity)
+ // {
+ // if (Mathf.Abs(velocity.X) >= 0.01f && Mathf.Abs(velocity.Y) >= 0.01f)
+ // {
+ // AnimatedSprite.Play(AnimatorNames.Run);
+ // BasisVelocity = velocity;
+ // }
+ // }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAstonishedState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAstonishedState.cs
new file mode 100644
index 0000000..0d7131b
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAstonishedState.cs
@@ -0,0 +1,56 @@
+using Godot;
+
+namespace AiState;
+
+///
+/// 发现目标时的惊讶状态
+///
+public class AiAstonishedState : StateBase
+{
+ ///
+ /// 下一个状态
+ ///
+ public AIStateEnum NextState;
+
+ private float _timer;
+ private object[] _args;
+
+ public AiAstonishedState() : base(AIStateEnum.AiAstonished)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (args.Length == 0)
+ {
+ Debug.Log("进入 AINormalStateEnum.AiAstonished 状态必传入下一个状态做完参数!");
+ ChangeState(prev);
+ return;
+ }
+
+ _args = args;
+
+ NextState = (AIStateEnum)args[0];
+ _timer = 0.6f;
+
+ //播放惊讶表情
+ Master.AnimationPlayer.Play(AnimatorNames.Astonished);
+ }
+
+ public override void Process(float delta)
+ {
+ Master.DoIdle();
+ _timer -= delta;
+ if (_timer <= 0)
+ {
+ if (_args.Length == 1)
+ {
+ ChangeState(NextState);
+ }
+ else if (_args.Length == 2)
+ {
+ ChangeState(NextState, _args[1]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAttackState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAttackState.cs
new file mode 100644
index 0000000..12acf2e
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAttackState.cs
@@ -0,0 +1,314 @@
+using System;
+using Godot;
+
+namespace AiState;
+
+///
+/// ai 攻击状态
+///
+public class AiAttackState : StateBase
+{
+ ///
+ /// 上一个状态
+ ///
+ public AIStateEnum PrevState;
+
+ ///
+ /// 武器攻击状态
+ ///
+ public AiAttackEnum AttackState;
+
+ //是否移动结束
+ private bool _isMoveOver;
+
+ //移动停顿计时器
+ private float _pauseTimer;
+ private bool _moveFlag;
+
+ //下一个移动点
+ private Vector2 _nextPosition;
+
+ //上一帧位置
+ private Vector2 _prevPos;
+ //卡在一个位置的时间
+ private float _lockTimer;
+ //进入状态的时候是否有武器
+ private bool _hasWeapon = true;
+
+ public AiAttackState() : base(AIStateEnum.AiAttack)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (Master.LookTarget == null)
+ {
+ throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色没有攻击目标!");
+ }
+
+ var weapon = Master.WeaponPack.ActiveItem;
+
+ if (weapon != null)
+ {
+ _hasWeapon = true;
+ if (Master.IsAttack || !weapon.TriggerIsReady())
+ {
+ throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色武器还无法触动扳机!");
+ }
+ }
+ else
+ {
+ _hasWeapon = false;
+ if (Master.IsAttack)
+ {
+ throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色攻击状态还没准备好");
+ }
+ }
+
+ Master.BasisVelocity = Vector2.Zero;
+ AttackState = AiAttackEnum.None;
+ Master.LockTargetTime = 0;
+ PrevState = prev;
+
+ _isMoveOver = true;
+ _pauseTimer = 0;
+ _moveFlag = false;
+ }
+
+ public override void Exit(AIStateEnum next)
+ {
+ Master.MountLookTarget = true;
+ Master.LockTargetTime = 0;
+ }
+
+ public override void Process(float delta)
+ {
+ //更新标记位置
+ Master.UpdateMarkTargetPosition();
+
+ if (_hasWeapon)
+ {
+ WeaponRoleProcess(delta);
+ }
+ else
+ {
+ NoWeaponRoleProcess(delta);
+ }
+ }
+
+ //有武器的敌人更新逻辑
+ private void WeaponRoleProcess(float delta)
+ {
+ var weapon = Master.WeaponPack.ActiveItem;
+ if (weapon == null)
+ {
+ //攻击结束
+ ChangeState(PrevState);
+ }
+ else if (AttackState == AiAttackEnum.AttackInterval) //攻击完成
+ {
+ if (weapon.GetAttackTimer() <= 0) //攻击冷却完成
+ {
+ Master.MountLookTarget = true;
+ //这里要做换弹判断, 还有上膛判断
+ if (weapon.CurrAmmo <= 0) //换弹判断
+ {
+ if (!weapon.Reloading)
+ {
+ weapon.Reload();
+ }
+ }
+ else if (weapon.GetBeLoadedStateState() != 2) //上膛
+ {
+ if (weapon.GetBeLoadedStateState() == 0)
+ {
+ weapon.BeLoaded();
+ }
+ }
+ else
+ {
+ //攻击结束
+ ChangeState(PrevState);
+ }
+ }
+ MoveHandler(delta);
+ }
+ else //攻击状态
+ {
+ //触发扳机
+ AttackState = weapon.AiTriggerAttackState(AttackState);
+
+ if (AttackState == AiAttackEnum.LockingTime) //锁定玩家状态
+ {
+ Master.LockTargetTime += delta;
+
+ var aiLockRemainderTime = Master.GetLockRemainderTime();
+ Master.MountLookTarget = aiLockRemainderTime >= weapon.Attribute.AiAttackAttr.LockAngleTime;
+ //更新瞄准辅助线
+ if (weapon.Attribute.AiAttackAttr.ShowSubline)
+ {
+ if (Master.SubLine == null)
+ {
+ Master.InitSubLine();
+ }
+ else
+ {
+ Master.SubLine.Enable = true;
+ }
+
+ //播放警告删掉动画
+ if (!Master.SubLine.IsPlayWarnAnimation && aiLockRemainderTime <= 0.5f)
+ {
+ Master.SubLine.PlayWarnAnimation(0.5f);
+ }
+ }
+
+ if (weapon.Attribute.AiAttackAttr.LockingStand) //锁定目标时站立不动
+ {
+ Master.DoIdle();
+ }
+ else //正常移动
+ {
+ MoveHandler(delta);
+ }
+ }
+ else
+ {
+ Master.LockTargetTime = 0;
+ //关闭辅助线
+ if (Master.SubLine != null)
+ {
+ Master.SubLine.Enable = false;
+ }
+
+ if (AttackState == AiAttackEnum.Attack || AttackState == AiAttackEnum.AttackInterval)
+ {
+ if (weapon.Attribute.AiAttackAttr.AttackLockAngle) //开火时锁定枪口角度
+ {
+ //连发状态锁定角度
+ Master.MountLookTarget = !(weapon.GetContinuousCount() > 0 || weapon.GetAttackTimer() > 0);
+ }
+ else
+ {
+ Master.MountLookTarget = true;
+ }
+ }
+ else
+ {
+ Master.MountLookTarget = true;
+ }
+
+ if (AttackState == AiAttackEnum.Attack && weapon.Attribute.AiAttackAttr.FiringStand) //开火时站立不动
+ {
+ Master.DoIdle();
+ }
+ else //正常移动
+ {
+ MoveHandler(delta);
+ }
+
+ if (AttackState == AiAttackEnum.AttackInterval) //触发攻击完成
+ {
+ Master.AttackTimer = weapon.Attribute.TriggerInterval + Master.AttackInterval;
+ }
+ }
+ }
+ }
+
+ //没有武器的敌人攻击逻辑
+ private void NoWeaponRoleProcess(float delta)
+ {
+ var weapon = Master.WeaponPack.ActiveItem;
+ if (weapon != null)
+ {
+ //找到武器了, 攻击结束
+ ChangeState(PrevState);
+ }
+ else if (Master.AttackTimer > 0 || Master.MeleeAttackTimer > 0) //攻击结束
+ {
+ ChangeState(PrevState);
+ }
+ else //攻击状态
+ {
+ Master.Attack();
+ }
+ }
+
+ private void MoveHandler(float delta)
+ {
+
+ if (_pauseTimer >= 0)
+ {
+ Master.AnimatedSprite.Play(AnimatorNames.Idle);
+ _pauseTimer -= delta;
+ }
+ else if (_isMoveOver) //移动已经完成
+ {
+ RunOver(Master.LookTarget.Position);
+ _isMoveOver = false;
+ }
+ else
+ {
+ var masterPosition = Master.Position;
+ if (_lockTimer >= 1) //卡在一个点超过一秒
+ {
+ RunOver(Master.LookTarget.Position);
+ _isMoveOver = false;
+ _lockTimer = 0;
+ }
+ else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点
+ {
+ _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f);
+ _isMoveOver = true;
+ _moveFlag = false;
+ //站立
+ Master.DoIdle();
+ }
+ else if (!_moveFlag)
+ {
+ _moveFlag = true;
+ //移动
+ Master.DoMove();
+ }
+ else
+ {
+ var lastSlideCollision = Master.GetLastSlideCollision();
+ if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色
+ {
+ _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f);
+ _isMoveOver = true;
+ _moveFlag = false;
+ //站立
+ Master.DoIdle();
+ }
+ else
+ {
+ //移动
+ Master.DoMove();
+ }
+
+ if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta)
+ {
+ _lockTimer += delta;
+ }
+ else
+ {
+ _lockTimer = 0;
+ _prevPos = masterPosition;
+ }
+ }
+ }
+ }
+
+ private void RunOver(Vector2 targetPos)
+ {
+ var weapon = Master.WeaponPack.ActiveItem;
+ var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f));
+ _nextPosition = new Vector2(
+ targetPos.X + Utils.Random.RandomRangeInt(-distance, distance),
+ targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance)
+ );
+ Master.NavigationAgent2D.TargetPosition = _nextPosition;
+ }
+
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs
new file mode 100644
index 0000000..2ebc81e
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs
@@ -0,0 +1,204 @@
+
+using System;
+using Godot;
+
+namespace AiState;
+
+///
+/// Ai 寻找弹药, 进入该状态需要在参数中传入目标武器对象
+///
+public class AiFindAmmoState : StateBase
+{
+ ///
+ /// 目标武器
+ ///
+ public Weapon TargetWeapon;
+
+ //导航目标点刷新计时器
+ private float _navigationUpdateTimer = 0;
+ private float _navigationInterval = 1f;
+
+ private float _tailAfterTimer = 0;
+ private ActivityObject _attackTarget;
+
+ private float _idleTimer = 0;
+ private bool _playAnimFlag = false;
+
+ public AiFindAmmoState() : base(AIStateEnum.AiFindAmmo)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (args.Length == 0)
+ {
+ throw new Exception("进入 AiStateEnum.AiFindAmmo 状态必须要把目标武器当成参数传过来");
+ }
+
+ if (args.Length >= 2)
+ {
+ _attackTarget = (ActivityObject)args[1];
+ }
+ else
+ {
+ _attackTarget = null;
+ }
+
+ SetTargetWeapon((Weapon)args[0]);
+ _navigationUpdateTimer = _navigationInterval;
+ _tailAfterTimer = 0;
+
+ //标记武器
+ TargetWeapon.SetSign(SignNames.AiFindWeaponSign, Master);
+
+ _playAnimFlag = prev == AIStateEnum.AiLeaveFor;
+ if (_playAnimFlag)
+ {
+ Master.AnimationPlayer.Play(AnimatorNames.Query);
+ }
+ }
+
+ public override void Process(float delta)
+ {
+ if (_playAnimFlag && _idleTimer > 0)
+ {
+ _idleTimer -= delta;
+ return;
+ }
+
+ if (!Master.IsAllWeaponTotalAmmoEmpty()) //已经有弹药了
+ {
+ RunNextState();
+ return;
+ }
+
+ if (Master.LookTarget == null) //没有目标
+ {
+ //临时处理
+ var player = Player.Current;
+ var playerPos = player.GetCenterPosition();
+ if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ //发现玩家
+ Master.LookTarget = player;
+ //进入惊讶状态, 然后再进入通知状态
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFindAmmo, TargetWeapon);
+ return;
+ }
+ }
+
+ //更新目标位置
+ if (_navigationUpdateTimer <= 0)
+ {
+ //每隔一段时间秒更改目标位置
+ _navigationUpdateTimer = _navigationInterval;
+ var position = TargetWeapon.GlobalPosition;
+ Master.NavigationAgent2D.TargetPosition = position;
+ }
+ else
+ {
+ _navigationUpdateTimer -= delta;
+ }
+
+ if (TargetWeapon.IsDestroyed || TargetWeapon.IsTotalAmmoEmpty()) //已经被销毁, 或者弹药已经被其他角色捡走
+ {
+ //再去寻找其他武器
+ SetTargetWeapon(Master.FindTargetWeapon());
+
+ if (TargetWeapon == null) //也没有其他可用的武器了
+ {
+ RunNextState();
+ }
+ }
+ else if (TargetWeapon.Master == Master) //已经被自己拾起
+ {
+ RunNextState();
+ }
+ else if (TargetWeapon.Master != null) //武器已经被其他角色拾起!
+ {
+ //再去寻找其他武器
+ SetTargetWeapon(Master.FindTargetWeapon());
+
+ if (TargetWeapon == null) //也没有其他可用的武器了
+ {
+ RunNextState();
+ }
+ }
+ else
+ {
+ if (Master.LookTarget != null)
+ {
+ //检测目标没有超出跟随视野距离
+ var isInTailAfterRange = Master.IsInTailAfterViewRange(Master.LookTarget.GetCenterPosition());
+ if (isInTailAfterRange)
+ {
+ _tailAfterTimer = 0;
+ }
+ else
+ {
+ _tailAfterTimer += delta;
+ }
+ }
+
+ //向武器移动
+ if (!Master.NavigationAgent2D.IsNavigationFinished())
+ {
+ //移动
+ Master.DoMove();
+ }
+ else
+ {
+ //站立
+ Master.DoIdle();
+ }
+ }
+ }
+
+ private void RunNextState()
+ {
+ if (_attackTarget != null)
+ {
+ ChangeState(AIStateEnum.AiLeaveFor, _attackTarget);
+ }
+ else if (Master.LookTarget != null)
+ {
+ ChangeState(_tailAfterTimer > 10 ? AIStateEnum.AiNormal : AIStateEnum.AiTailAfter);
+ }
+ else
+ {
+ ChangeState(AIStateEnum.AiNormal);
+ }
+ }
+
+ private void SetTargetWeapon(Weapon weapon)
+ {
+ TargetWeapon = weapon;
+ if (weapon != null)
+ {
+ //设置目标点
+ Master.NavigationAgent2D.TargetPosition = TargetWeapon.GlobalPosition;
+ }
+ }
+
+ public override void DebugDraw()
+ {
+ if (TargetWeapon != null)
+ {
+ Master.DrawLine(Vector2.Zero, Master.ToLocal(TargetWeapon.GlobalPosition), Colors.Purple);
+
+ if (Master.LookTarget != null)
+ {
+ if (_tailAfterTimer <= 0)
+ {
+ Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Orange);
+ }
+ else if (_tailAfterTimer <= 10)
+ {
+ Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Blue);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFollowUpState.cs
new file mode 100644
index 0000000..c231a9a
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFollowUpState.cs
@@ -0,0 +1,147 @@
+
+using System;
+using Godot;
+
+namespace AiState;
+
+///
+/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火
+///
+public class AiFollowUpState : StateBase
+{
+ //导航目标点刷新计时器
+ private float _navigationUpdateTimer = 0;
+ private float _navigationInterval = 0.3f;
+
+ public AiFollowUpState() : base(AIStateEnum.AiFollowUp)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (Master.LookTarget == null)
+ {
+ throw new Exception("进入 AIAdvancedStateEnum.AiFollowUp 状态时角色没有攻击目标!");
+ }
+
+ _navigationUpdateTimer = 0;
+ Master.TargetInView = true;
+ }
+
+ public override void Process(float delta)
+ {
+ //先检查弹药是否打光
+ if (Master.IsAllWeaponTotalAmmoEmpty())
+ {
+ //再寻找是否有可用的武器
+ var targetWeapon = Master.FindTargetWeapon();
+ if (targetWeapon != null)
+ {
+ ChangeState(AIStateEnum.AiFindAmmo, targetWeapon);
+ return;
+ }
+ else
+ {
+ //切换到随机移动状态
+ ChangeState(AIStateEnum.AiSurround);
+ }
+ }
+
+ var playerPos = Master.LookTarget.GetCenterPosition();
+
+ //更新玩家位置
+ if (_navigationUpdateTimer <= 0)
+ {
+ //每隔一段时间秒更改目标位置
+ _navigationUpdateTimer = _navigationInterval;
+ Master.NavigationAgent2D.TargetPosition = playerPos;
+ }
+ else
+ {
+ _navigationUpdateTimer -= delta;
+ }
+
+ //是否在攻击范围内
+ var inAttackRange = false;
+
+ var weapon = Master.WeaponPack.ActiveItem;
+ var distanceSquared = Master.Position.DistanceSquaredTo(playerPos);
+ if (weapon != null)
+ {
+ inAttackRange = distanceSquared <= Mathf.Pow(Master.GetWeaponRange(0.7f), 2);
+ }
+ else
+ {
+ inAttackRange = distanceSquared <= Mathf.Pow(Master.ViewRange * 0.7f, 2);
+ }
+
+ if (!Master.NavigationAgent2D.IsNavigationFinished())
+ {
+ //移动
+ Master.DoMove();
+ }
+ else
+ {
+ //站立
+ Master.DoIdle();
+ }
+
+ //检测玩家是否在视野内
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ Master.TargetInView = !Master.TestViewRayCast(playerPos);
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ else
+ {
+ Master.TargetInView = false;
+ }
+
+ //在视野中
+ if (Master.TargetInView)
+ {
+ //更新标记位置
+ Master.UpdateMarkTargetPosition();
+ if (inAttackRange) //在攻击范围内
+ {
+ if (weapon != null)
+ {
+ //距离够近, 可以切换到环绕模式
+ if (distanceSquared <= Mathf.Pow(Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f, 2))
+ {
+ ChangeState(AIStateEnum.AiSurround);
+ }
+ else if (!Master.IsAttack && weapon.TriggerIsReady()) //可以攻击
+ {
+ //攻击状态
+ ChangeState(AIStateEnum.AiAttack);
+ }
+ }
+ else
+ {
+ //距离够近, 可以切换到环绕模式
+ if (distanceSquared <= Mathf.Pow(Master.ViewRange * 0.7f, 2))
+ {
+ ChangeState(AIStateEnum.AiSurround);
+ }
+ else if (!Master.IsAttack && Master.NoWeaponAttack) //可以在没有武器时发起攻击
+ {
+ //攻击状态
+ ChangeState(AIStateEnum.AiAttack);
+ }
+ }
+ }
+ }
+ else //不在视野中
+ {
+ ChangeState(AIStateEnum.AiTailAfter);
+ }
+ }
+
+ public override void DebugDraw()
+ {
+ var playerPos = Master.LookTarget.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/activity/role/ai/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs
new file mode 100644
index 0000000..f539df1
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs
@@ -0,0 +1,136 @@
+
+using System;
+using Godot;
+
+namespace AiState;
+
+///
+/// 收到其他敌人通知, 前往发现目标的位置
+///
+public class AiLeaveForState : StateBase
+{
+ //导航目标点刷新计时器
+ private float _navigationUpdateTimer = 0;
+ private float _navigationInterval = 0.3f;
+
+ //目标
+ private ActivityObject _target;
+ //目标点
+ private Vector2 _targetPosition;
+
+ private float _idleTimer = 0;
+ private bool _playAnimFlag = false;
+
+ public AiLeaveForState() : base(AIStateEnum.AiLeaveFor)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (args.Length == 0)
+ {
+ throw new Exception("进入 AINormalStateEnum.AiLeaveFor 状态必须带上目标对象");
+ }
+
+ _target = (ActivityObject)args[0];
+
+ //先检查弹药是否打光
+ if (Master.IsAllWeaponTotalAmmoEmpty())
+ {
+ //再寻找是否有可用的武器
+ var targetWeapon = Master.FindTargetWeapon();
+ if (targetWeapon != null)
+ {
+ ChangeState(AIStateEnum.AiFindAmmo, targetWeapon, _target);
+ return;
+ }
+ }
+
+ _idleTimer = 1;
+ _targetPosition = _target.GetCenterPosition();
+ Master.LookTargetPosition(_targetPosition);
+
+ _playAnimFlag = prev != AIStateEnum.AiFindAmmo;
+ if (_playAnimFlag)
+ {
+ Master.AnimationPlayer.Play(AnimatorNames.Query);
+ }
+
+ //看向目标位置
+ Master.LookTargetPosition(_target.GetCenterPosition());
+ }
+
+ public override void Exit(AIStateEnum next)
+ {
+ Master.AnimationPlayer.Play(AnimatorNames.Reset);
+ }
+
+ public override void Process(float delta)
+ {
+ if (_playAnimFlag && _idleTimer > 0)
+ {
+ _idleTimer -= delta;
+ return;
+ }
+ //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽
+
+ //更新玩家位置
+ if (_navigationUpdateTimer <= 0)
+ {
+ //每隔一段时间秒更改目标位置
+ _navigationUpdateTimer = _navigationInterval;
+ if (Master.AffiliationArea.RoomInfo.MarkTargetPosition.TryGetValue(_target.Id, out var pos))
+ {
+ _targetPosition = pos;
+ }
+ Master.NavigationAgent2D.TargetPosition = _targetPosition;
+ }
+ else
+ {
+ _navigationUpdateTimer -= delta;
+ }
+
+ if (!Master.NavigationAgent2D.IsNavigationFinished())
+ {
+ Master.LookTargetPosition(_targetPosition);
+ //移动
+ Master.DoMove();
+ }
+ else
+ {
+ //站立
+ Master.DoIdle();
+ }
+
+ var playerPos = Player.Current.GetCenterPosition();
+ //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ if (!Master.TestViewRayCast(playerPos)) //看到玩家
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ //切换成发现目标状态
+ Master.LookTarget = Player.Current;
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFollowUp);
+ return;
+ }
+ else
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ }
+
+ //移动到目标掉了, 还没发现目标
+ if (Master.NavigationAgent2D.IsNavigationFinished())
+ {
+ ChangeState(AIStateEnum.AiNormal);
+ }
+ }
+
+ public override void DebugDraw()
+ {
+ Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.NavigationAgent2D.TargetPosition), Colors.Yellow);
+ }
+}
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs
new file mode 100644
index 0000000..6bcb86d
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs
@@ -0,0 +1,178 @@
+
+using System.Linq;
+using Godot;
+
+namespace AiState;
+
+///
+/// AI 正常状态
+///
+public class AiNormalState : StateBase
+{
+ //下一个运动的坐标
+ private Vector2 _nextPos;
+
+ //是否移动结束
+ private bool _isMoveOver;
+
+ //上一次移动是否撞墙
+ private bool _againstWall;
+
+ //撞墙法线角度
+ private float _againstWallNormalAngle;
+
+ //移动停顿计时器
+ private float _pauseTimer;
+ private bool _moveFlag;
+
+ //上一帧位置
+ private Vector2 _prevPos;
+ //卡在一个位置的时间
+ private float _lockTimer;
+
+ public AiNormalState() : base(AIStateEnum.AiNormal)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ _isMoveOver = true;
+ _againstWall = false;
+ _againstWallNormalAngle = 0;
+ _pauseTimer = 0;
+ _moveFlag = false;
+ Master.LookTarget = null;
+ }
+
+ public override void Process(float delta)
+ {
+ //检测玩家
+ var player = Player.Current;
+ //玩家中心点坐标
+ var playerPos = player.GetCenterPosition();
+
+ if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ //发现玩家
+ Master.LookTarget = player;
+ //判断是否进入通知状态
+ if (Master.World.Enemy_InstanceList.FindIndex(enemy =>
+ enemy != Master && !enemy.IsDie && enemy.AffiliationArea == Master.AffiliationArea &&
+ enemy.StateController.CurrState == AIStateEnum.AiNormal) != -1)
+ {
+ //进入惊讶状态, 然后再进入通知状态
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify);
+ }
+ else
+ {
+ //进入惊讶状态, 然后再进入跟随状态
+ ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
+ }
+ return;
+ }
+ else if (_pauseTimer >= 0)
+ {
+ Master.AnimatedSprite.Play(AnimatorNames.Idle);
+ _pauseTimer -= delta;
+ }
+ else if (_isMoveOver) //没发现玩家, 且已经移动完成
+ {
+ RunOver();
+ _isMoveOver = false;
+ }
+ else //移动中
+ {
+ if (_lockTimer >= 1) //卡在一个点超过一秒
+ {
+ RunOver();
+ _isMoveOver = false;
+ _lockTimer = 0;
+ }
+ else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点
+ {
+ _pauseTimer = Utils.Random.RandomRangeFloat(0.3f, 2f);
+ _isMoveOver = true;
+ _moveFlag = false;
+ //站立
+ Master.DoIdle();
+ }
+ else if (!_moveFlag)
+ {
+ _moveFlag = true;
+ var pos = Master.Position;
+ //移动
+ Master.DoMove();
+ _prevPos = pos;
+ }
+ else
+ {
+ var pos = Master.Position;
+ var lastSlideCollision = Master.GetLastSlideCollision();
+ if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色
+ {
+ _pauseTimer = Utils.Random.RandomRangeFloat(0.1f, 0.5f);
+ _isMoveOver = true;
+ _moveFlag = false;
+ //站立
+ Master.DoIdle();
+ }
+ else
+ {
+ //移动
+ Master.DoMove();
+ }
+
+ if (_prevPos.DistanceSquaredTo(pos) <= 0.01f)
+ {
+ _lockTimer += delta;
+ }
+ else
+ {
+ _prevPos = pos;
+ }
+ }
+ }
+
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+
+ //移动结束
+ private void RunOver()
+ {
+ float angle;
+ if (_againstWall)
+ {
+ angle = Utils.Random.RandomRangeFloat(_againstWallNormalAngle - Mathf.Pi * 0.5f,
+ _againstWallNormalAngle + Mathf.Pi * 0.5f);
+ }
+ else
+ {
+ angle = Utils.Random.RandomRangeFloat(0, Mathf.Pi * 2f);
+ }
+
+ var len = Utils.Random.RandomRangeInt(30, 200);
+ _nextPos = new Vector2(len, 0).Rotated(angle) + Master.GlobalPosition;
+ //获取射线碰到的坐标
+ if (Master.TestViewRayCast(_nextPos)) //碰到墙壁
+ {
+ _nextPos = Master.ViewRay.GetCollisionPoint();
+ _againstWall = true;
+ _againstWallNormalAngle = Master.ViewRay.GetCollisionNormal().Angle();
+ }
+ else
+ {
+ _againstWall = false;
+ }
+
+ Master.NavigationAgent2D.TargetPosition = _nextPos;
+ Master.LookTargetPosition(_nextPos);
+ }
+
+ public override void DebugDraw()
+ {
+ Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPos), Colors.Green);
+ }
+}
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNotifyState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNotifyState.cs
new file mode 100644
index 0000000..22726f1
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNotifyState.cs
@@ -0,0 +1,37 @@
+using System;
+
+namespace AiState;
+
+///
+/// 发现目标, 通知其它敌人
+///
+public class AiNotifyState : StateBase
+{
+ private float _timer;
+
+ public AiNotifyState() : base(AIStateEnum.AiNotify)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (Master.LookTarget == null)
+ {
+ throw new Exception("进入 AIAdvancedStateEnum.AiNotify 没有攻击目标!");
+ }
+ _timer = 1.2f;
+ //通知其它角色
+ Master.World.NotifyEnemyTarget(Master, Master.LookTarget);
+ Master.AnimationPlayer.Play(AnimatorNames.Notify);
+ }
+
+ public override void Process(float delta)
+ {
+ Master.DoIdle();
+ _timer -= delta;
+ if (_timer <= 0)
+ {
+ ChangeState(AIStateEnum.AiTailAfter);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiSurroundState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiSurroundState.cs
new file mode 100644
index 0000000..94afce7
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiSurroundState.cs
@@ -0,0 +1,187 @@
+
+using System;
+using Godot;
+
+namespace AiState;
+
+///
+/// 距离目标足够近, 在目标附近随机移动, 并开火
+///
+public class AiSurroundState : StateBase
+{
+ //是否移动结束
+ private bool _isMoveOver;
+
+ //移动停顿计时器
+ private float _pauseTimer;
+ private bool _moveFlag;
+
+ //下一个移动点
+ private Vector2 _nextPosition;
+
+ //上一帧位置
+ private Vector2 _prevPos;
+ //卡在一个位置的时间
+ private float _lockTimer;
+
+ public AiSurroundState() : base(AIStateEnum.AiSurround)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (Master.LookTarget == null)
+ {
+ throw new Exception("进入 AIAdvancedStateEnum.AiSurround 状态时角色没有攻击目标!");
+ }
+
+ Master.TargetInView = true;
+ _isMoveOver = true;
+ _pauseTimer = 0;
+ _moveFlag = false;
+ }
+
+ public override void Process(float delta)
+ {
+ //先检查弹药是否打光
+ if (Master.IsAllWeaponTotalAmmoEmpty())
+ {
+ //再寻找是否有可用的武器
+ var targetWeapon = Master.FindTargetWeapon();
+ if (targetWeapon != null)
+ {
+ ChangeState(AIStateEnum.AiFindAmmo, targetWeapon);
+ return;
+ }
+ }
+
+ var playerPos = Master.LookTarget.GetCenterPosition();
+
+ //检测玩家是否在视野内
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ Master.TargetInView = !Master.TestViewRayCast(playerPos);
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ else
+ {
+ Master.TargetInView = false;
+ }
+
+ //在视野中
+ if (Master.TargetInView)
+ {
+ //更新标记位置
+ Master.UpdateMarkTargetPosition();
+
+ if (_pauseTimer >= 0)
+ {
+ Master.AnimatedSprite.Play(AnimatorNames.Idle);
+ _pauseTimer -= delta;
+ }
+ else if (_isMoveOver) //移动已经完成
+ {
+ RunOver(playerPos);
+ _isMoveOver = false;
+ }
+ else
+ {
+ var masterPosition = Master.Position;
+ if (_lockTimer >= 1) //卡在一个点超过一秒
+ {
+ RunOver(playerPos);
+ _isMoveOver = false;
+ _lockTimer = 0;
+ }
+ else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点
+ {
+ _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f);
+ _isMoveOver = true;
+ _moveFlag = false;
+ //站立
+ Master.DoIdle();
+ }
+ else if (!_moveFlag)
+ {
+ _moveFlag = true;
+ //移动
+ Master.DoMove();
+ }
+ else
+ {
+ var lastSlideCollision = Master.GetLastSlideCollision();
+ if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色
+ {
+ _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f);
+ _isMoveOver = true;
+ _moveFlag = false;
+ //站立
+ Master.DoIdle();
+ }
+ else
+ {
+ //移动
+ Master.DoMove();
+ }
+
+ if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta)
+ {
+ _lockTimer += delta;
+ }
+ else
+ {
+ _lockTimer = 0;
+ _prevPos = masterPosition;
+ }
+ }
+
+ var weapon = Master.WeaponPack.ActiveItem;
+ if (weapon != null)
+ {
+ if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetWeaponRange(0.7f), 2)) //玩家离开正常射击范围
+ {
+ ChangeState(AIStateEnum.AiFollowUp);
+ }
+ else if (!Master.IsAttack && weapon.TriggerIsReady()) //可以攻击
+ {
+ //发起攻击
+ ChangeState(AIStateEnum.AiAttack);
+ }
+ }
+ else
+ {
+ if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.ViewRange * 0.7f, 2)) //玩家离开正常射击范围
+ {
+ ChangeState(AIStateEnum.AiFollowUp);
+ }
+ else if (!Master.IsAttack && Master.NoWeaponAttack) //可以在没有武器时发起攻击
+ {
+ //攻击状态
+ ChangeState(AIStateEnum.AiAttack);
+ }
+ }
+ }
+ }
+ else //目标离开视野
+ {
+ ChangeState(AIStateEnum.AiTailAfter);
+ }
+ }
+
+ private void RunOver(Vector2 targetPos)
+ {
+ var weapon = Master.WeaponPack.ActiveItem;
+ var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f));
+ _nextPosition = new Vector2(
+ targetPos.X + Utils.Random.RandomRangeInt(-distance, distance),
+ targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance)
+ );
+ Master.NavigationAgent2D.TargetPosition = _nextPosition;
+ }
+
+ public override void DebugDraw()
+ {
+ Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPosition), Colors.White);
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs
new file mode 100644
index 0000000..05a5d9e
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs
@@ -0,0 +1,128 @@
+
+using System;
+using Godot;
+
+namespace AiState;
+
+///
+/// AI 发现玩家, 跟随玩家, 但是不在视野范围内
+///
+public class AiTailAfterState : StateBase
+{
+ ///
+ /// 目标是否在视野半径内
+ ///
+ private bool _isInViewRange;
+
+ //导航目标点刷新计时器
+ private float _navigationUpdateTimer = 0;
+ private float _navigationInterval = 0.3f;
+
+ //目标从视野消失时已经过去的时间
+ private float _viewTimer;
+
+ public AiTailAfterState() : base(AIStateEnum.AiTailAfter)
+ {
+ }
+
+ public override void Enter(AIStateEnum prev, params object[] args)
+ {
+ if (Master.LookTarget == null)
+ {
+ throw new Exception("进入 AIAdvancedStateEnum.AiTailAfter 状态时角色没有攻击目标!");
+ }
+
+ _isInViewRange = true;
+ _navigationUpdateTimer = 0;
+ _viewTimer = 0;
+
+ //先检查弹药是否打光
+ if (Master.IsAllWeaponTotalAmmoEmpty())
+ {
+ //再寻找是否有可用的武器
+ var targetWeapon = Master.FindTargetWeapon();
+ if (targetWeapon != null)
+ {
+ ChangeState(AIStateEnum.AiFindAmmo, targetWeapon);
+ }
+ }
+ }
+
+ public override void Process(float delta)
+ {
+ //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽
+
+ var playerPos = Master.LookTarget.GetCenterPosition();
+
+ //更新玩家位置
+ if (_navigationUpdateTimer <= 0)
+ {
+ //每隔一段时间秒更改目标位置
+ _navigationUpdateTimer = _navigationInterval;
+ Master.NavigationAgent2D.TargetPosition = playerPos;
+ }
+ else
+ {
+ _navigationUpdateTimer -= delta;
+ }
+
+ if (!Master.NavigationAgent2D.IsNavigationFinished())
+ {
+ //移动
+ Master.DoMove();
+ }
+ else
+ {
+ //站立
+ Master.DoIdle();
+ }
+ //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态
+ if (Master.IsInTailAfterViewRange(playerPos))
+ {
+ if (!Master.TestViewRayCast(playerPos)) //看到玩家
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ //切换成发现目标状态
+ ChangeState(AIStateEnum.AiFollowUp);
+ return;
+ }
+ else
+ {
+ //关闭射线检测
+ Master.TestViewRayCastOver();
+ }
+ }
+
+ //检测玩家是否在穿墙视野范围内, 直接检测距离即可
+ _isInViewRange = Master.IsInViewRange(playerPos);
+ if (_isInViewRange)
+ {
+ _viewTimer = 0;
+ }
+ else //超出视野
+ {
+ if (_viewTimer > 10) //10秒
+ {
+ ChangeState(AIStateEnum.AiNormal);
+ }
+ else
+ {
+ _viewTimer += delta;
+ }
+ }
+ }
+
+ public override void DebugDraw()
+ {
+ var playerPos = Master.LookTarget.GetCenterPosition();
+ if (_isInViewRange)
+ {
+ Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Orange);
+ }
+ else
+ {
+ Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Blue);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs
index 33bf27b..e67c7aa 100644
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs
+++ b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs
@@ -2,70 +2,16 @@
using System;
using System.Collections.Generic;
using Config;
-using EnemyState;
+using AiState;
using Godot;
///
-/// 高级敌人,可以携带武器
+/// 敌人,可以携带武器
///
[Tool]
-public partial class Enemy : Role
+public partial class Enemy : AiRole
{
///
- /// 目标是否在视野内
- ///
- public bool TargetInView { get; set; } = true;
-
- ///
- /// 敌人身上的状态机控制器
- ///
- public StateController StateController { get; private set; }
-
- ///
- /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙
- ///
- [Export, ExportFillNode]
- public RayCast2D ViewRay { get; set; }
-
- ///
- /// 导航代理
- ///
- [Export, ExportFillNode]
- public NavigationAgent2D NavigationAgent2D { get; set; }
-
- ///
- /// 导航代理中点
- ///
- [Export, ExportFillNode]
- public Marker2D NavigationPoint { get; set; }
-
- ///
- /// 不通过武发射子弹的开火点
- ///
- [Export, ExportFillNode]
- public Marker2D FirePoint { get; set; }
-
- ///
- /// 当前敌人所看向的对象, 也就是枪口指向的对象
- ///
- public ActivityObject LookTarget { get; set; }
-
- ///
- /// 攻击锁定目标时间
- ///
- public float LockingTime { get; set; } = 1f;
-
- ///
- /// 锁定目标已经走过的时间
- ///
- public float LockTargetTime { get; set; } = 0;
-
- ///
- /// 敌人属性
- ///
- public EnemyRoleState EnemyRoleState { get; private set; }
-
- ///
/// 敌人属性
///
private ExcelConfig.EnemyBase _enemyAttribute;
@@ -117,10 +63,6 @@
public override void OnInit()
{
base.OnInit();
-
- IsAi = true;
-
- StateController = AddComponent>();
AttackLayer = PhysicsLayer.Obstacle | PhysicsLayer.Player;
EnemyLayer = PhysicsLayer.Player;
@@ -130,28 +72,11 @@
MaxHp = 20;
Hp = 20;
-
- //注册Ai状态机
- StateController.Register(new AiNormalState());
- StateController.Register(new AiTailAfterState());
- StateController.Register(new AiFollowUpState());
- StateController.Register(new AiLeaveForState());
- StateController.Register(new AiSurroundState());
- StateController.Register(new AiFindAmmoState());
- StateController.Register(new AiAttackState());
- StateController.Register(new AiAstonishedState());
- StateController.Register(new AiNotifyState());
-
- //默认状态
- StateController.ChangeStateInstant(AIStateEnum.AiNormal);
-
- //NavigationAgent2D.VelocityComputed += OnVelocityComputed;
}
protected override RoleState OnCreateRoleState()
{
- var roleState = new EnemyRoleState();
- EnemyRoleState = roleState;
+ var roleState = new RoleState();
var enemyBase = GetEnemyAttribute(ActivityBase.Id).Clone();
_enemyAttribute = enemyBase;
@@ -161,10 +86,10 @@
roleState.MoveSpeed = enemyBase.MoveSpeed;
roleState.Acceleration = enemyBase.Acceleration;
roleState.Friction = enemyBase.Friction;
- roleState.ViewRange = enemyBase.ViewRange;
- roleState.TailAfterViewRange = enemyBase.TailAfterViewRange;
- roleState.BackViewRange = enemyBase.BackViewRange;
- roleState.AttackInterval = enemyBase.AttackInterval;
+ ViewRange = enemyBase.ViewRange;
+ TailAfterViewRange = enemyBase.TailAfterViewRange;
+ BackViewRange = enemyBase.BackViewRange;
+ AttackInterval = enemyBase.AttackInterval;
roleState.Gold = Mathf.Max(0, Utils.Random.RandomConfigRange(enemyBase.Gold));
return roleState;
@@ -240,10 +165,10 @@
MountPoint.SetLookAt(pos);
}
- if (EnemyRoleState.CanPickUpWeapon)
+ if (RoleState.CanPickUpWeapon)
{
//拾起武器操作
- EnemyPickUpWeapon();
+ DoPickUpWeapon();
}
}
@@ -312,276 +237,6 @@
}
///
- /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
- ///
- public bool CheckUsableWeaponInUnclaimed()
- {
- foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons)
- {
- //判断是否能拾起武器, 条件: 相同的房间
- if (unclaimedWeapon.AffiliationArea == AffiliationArea)
- {
- if (!unclaimedWeapon.IsTotalAmmoEmpty())
- {
- if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
- {
- return true;
- }
- else
- {
- //判断是否可以移除该标记
- var enemy = unclaimedWeapon.GetSign(SignNames.AiFindWeaponSign);
- if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
- {
- unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
- return true;
- }
- else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
- {
- unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
- return true;
- }
- }
- }
- }
- }
-
- return false;
- }
-
- ///
- /// 寻找可用的武器
- ///
- public Weapon FindTargetWeapon()
- {
- Weapon target = null;
- var position = Position;
- foreach (var weapon in World.Weapon_UnclaimedWeapons)
- {
- //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间
- if (weapon.AffiliationArea == AffiliationArea)
- {
- //还有弹药
- if (!weapon.IsTotalAmmoEmpty())
- {
- //查询是否有其他敌人标记要拾起该武器
- if (weapon.HasSign(SignNames.AiFindWeaponSign))
- {
- var enemy = weapon.GetSign(SignNames.AiFindWeaponSign);
- if (enemy == this) //就是自己标记的
- {
-
- }
- else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
- {
- weapon.RemoveSign(SignNames.AiFindWeaponSign);
- }
- else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
- {
- weapon.RemoveSign(SignNames.AiFindWeaponSign);
- }
- else //放弃这把武器
- {
- continue;
- }
- }
-
- if (target == null) //第一把武器
- {
- target = weapon;
- }
- else if (target.Position.DistanceSquaredTo(position) >
- weapon.Position.DistanceSquaredTo(position)) //距离更近
- {
- target = weapon;
- }
- }
- }
- }
-
- return target;
- }
-
- ///
- /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
- ///
- /// 从最小到最大距离的过渡量, 0 - 1, 默认 0.5
- public float GetWeaponRange(float weight = 0.5f)
- {
- if (WeaponPack.ActiveItem != null)
- {
- var attribute = WeaponPack.ActiveItem.Attribute;
- return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight);
- }
-
- return 0;
- }
-
- ///
- /// 返回目标点是否在视野范围内
- ///
- public bool IsInViewRange(Vector2 target)
- {
- var isForward = IsPositionInForward(target);
- if (isForward)
- {
- if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.ViewRange * EnemyRoleState.ViewRange) //没有超出视野半径
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// 返回目标点是否在跟随状态下的视野半径内
- ///
- public bool IsInTailAfterViewRange(Vector2 target)
- {
- var isForward = IsPositionInForward(target);
- if (isForward)
- {
- if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.TailAfterViewRange * EnemyRoleState.TailAfterViewRange) //没有超出视野半径
- {
- return true;
- }
- }
-
- return false;
- }
-
- ///
- /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true
- ///
- public bool TestViewRayCast(Vector2 target)
- {
- ViewRay.Enabled = true;
- ViewRay.TargetPosition = ViewRay.ToLocal(target);
- ViewRay.ForceRaycastUpdate();
- return ViewRay.IsColliding();
- }
-
- ///
- /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
- ///
- public void TestViewRayCastOver()
- {
- ViewRay.Enabled = false;
- }
-
- ///
- /// AI 拾起武器操作
- ///
- private void EnemyPickUpWeapon()
- {
- //这几个状态不需要主动拾起武器操作
- var state = StateController.CurrState;
- if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiNotify || state == AIStateEnum.AiAstonished || state == AIStateEnum.AiAttack)
- {
- return;
- }
-
- //拾起地上的武器
- if (InteractiveItem is Weapon weapon)
- {
- if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起
- {
- TriggerInteractive();
- return;
- }
-
- //没弹药了
- if (weapon.IsTotalAmmoEmpty())
- {
- return;
- }
-
- var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id);
- if (index != -1) //与武器背包中武器类型相同, 补充子弹
- {
- if (!WeaponPack.GetItem(index).IsAmmoFull())
- {
- TriggerInteractive();
- }
-
- return;
- }
-
- // var index2 = Holster.FindWeapon((we, i) =>
- // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
- var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty());
- if (index2 != -1) //扔掉没子弹的武器
- {
- ThrowWeapon(index2);
- TriggerInteractive();
- return;
- }
-
- // if (Holster.HasVacancy()) //有空位, 拾起武器
- // {
- // TriggerInteractive();
- // return;
- // }
- }
- }
-
- ///
- /// 获取锁定目标的剩余时间
- ///
- public float GetLockRemainderTime()
- {
- var weapon = WeaponPack.ActiveItem;
- if (weapon == null)
- {
- return LockingTime - LockTargetTime;
- }
- return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime;
- }
-
- public override void LookTargetPosition(Vector2 pos)
- {
- LookTarget = null;
- base.LookTargetPosition(pos);
- }
-
- ///
- /// 执行移动操作
- ///
- public void DoMove()
- {
- // //计算移动
- // NavigationAgent2D.MaxSpeed = EnemyRoleState.MoveSpeed;
- // var nextPos = NavigationAgent2D.GetNextPathPosition();
- // NavigationAgent2D.Velocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
-
- AnimatedSprite.Play(AnimatorNames.Run);
- //计算移动
- var nextPos = NavigationAgent2D.GetNextPathPosition();
- BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
- }
-
- ///
- /// 执行站立操作
- ///
- public void DoIdle()
- {
- AnimatedSprite.Play(AnimatorNames.Idle);
- BasisVelocity = Vector2.Zero;
- }
-
- ///
- /// 更新房间中标记的目标位置
- ///
- public void UpdateMarkTargetPosition()
- {
- if (LookTarget != null)
- {
- AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position;
- }
- }
-
- ///
/// 从标记出生时调用, 预加载波不会调用
///
public virtual void OnBornFromMark()
@@ -590,13 +245,4 @@
StateController.Enable = false;
this.CallDelay(0.7f, () => StateController.Enable = true);
}
-
- // private void OnVelocityComputed(Vector2 velocity)
- // {
- // if (Mathf.Abs(velocity.X) >= 0.01f && Mathf.Abs(velocity.Y) >= 0.01f)
- // {
- // AnimatedSprite.Play(AnimatorNames.Run);
- // BasisVelocity = velocity;
- // }
- // }
}
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/EnemyRoleState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/EnemyRoleState.cs
deleted file mode 100644
index ef3618a..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/EnemyRoleState.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-
-public class EnemyRoleState : RoleState
-{
- ///
- /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
- ///
- public float ViewRange = 250;
-
- ///
- /// 发现玩家后跟随玩家的视野半径
- ///
- public float TailAfterViewRange = 400;
-
- ///
- /// 背后的视野半径, 单位像素
- ///
- public float BackViewRange = 50;
-
- ///
- /// 攻击间隔时间, 秒
- ///
- public float AttackInterval = 0;
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs
index ed3d271..495337c 100644
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs
+++ b/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs
@@ -82,7 +82,7 @@
{
if (name == AnimatorNames.Attack)
{
- AttackTimer = EnemyRoleState.AttackInterval;
+ AttackTimer = AttackInterval;
}
}
}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs
deleted file mode 100644
index c39aff9..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using Godot;
-
-namespace EnemyState;
-
-///
-/// 发现目标时的惊讶状态
-///
-public class AiAstonishedState : StateBase
-{
- ///
- /// 下一个状态
- ///
- public AIStateEnum NextState;
-
- private float _timer;
- private object[] _args;
-
- public AiAstonishedState() : base(AIStateEnum.AiAstonished)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (args.Length == 0)
- {
- Debug.Log("进入 AINormalStateEnum.AiAstonished 状态必传入下一个状态做完参数!");
- ChangeState(prev);
- return;
- }
-
- _args = args;
-
- NextState = (AIStateEnum)args[0];
- _timer = 0.6f;
-
- //播放惊讶表情
- Master.AnimationPlayer.Play(AnimatorNames.Astonished);
- }
-
- public override void Process(float delta)
- {
- Master.DoIdle();
- _timer -= delta;
- if (_timer <= 0)
- {
- if (_args.Length == 1)
- {
- ChangeState(NextState);
- }
- else if (_args.Length == 2)
- {
- ChangeState(NextState, _args[1]);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs
deleted file mode 100644
index ca847b3..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs
+++ /dev/null
@@ -1,314 +0,0 @@
-using System;
-using Godot;
-
-namespace EnemyState;
-
-///
-/// ai 攻击状态
-///
-public class AiAttackState : StateBase
-{
- ///
- /// 上一个状态
- ///
- public AIStateEnum PrevState;
-
- ///
- /// 武器攻击状态
- ///
- public AiAttackEnum AttackState;
-
- //是否移动结束
- private bool _isMoveOver;
-
- //移动停顿计时器
- private float _pauseTimer;
- private bool _moveFlag;
-
- //下一个移动点
- private Vector2 _nextPosition;
-
- //上一帧位置
- private Vector2 _prevPos;
- //卡在一个位置的时间
- private float _lockTimer;
- //进入状态的时候是否有武器
- private bool _hasWeapon = true;
-
- public AiAttackState() : base(AIStateEnum.AiAttack)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (Master.LookTarget == null)
- {
- throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色没有攻击目标!");
- }
-
- var weapon = Master.WeaponPack.ActiveItem;
-
- if (weapon != null)
- {
- _hasWeapon = true;
- if (Master.IsAttack || !weapon.TriggerIsReady())
- {
- throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色武器还无法触动扳机!");
- }
- }
- else
- {
- _hasWeapon = false;
- if (Master.IsAttack)
- {
- throw new Exception("进入 AIAdvancedStateEnum.AiAttack 状态时角色攻击状态还没准备好");
- }
- }
-
- Master.BasisVelocity = Vector2.Zero;
- AttackState = AiAttackEnum.None;
- Master.LockTargetTime = 0;
- PrevState = prev;
-
- _isMoveOver = true;
- _pauseTimer = 0;
- _moveFlag = false;
- }
-
- public override void Exit(AIStateEnum next)
- {
- Master.MountLookTarget = true;
- Master.LockTargetTime = 0;
- }
-
- public override void Process(float delta)
- {
- //更新标记位置
- Master.UpdateMarkTargetPosition();
-
- if (_hasWeapon)
- {
- WeaponRoleProcess(delta);
- }
- else
- {
- NoWeaponRoleProcess(delta);
- }
- }
-
- //有武器的敌人更新逻辑
- private void WeaponRoleProcess(float delta)
- {
- var weapon = Master.WeaponPack.ActiveItem;
- if (weapon == null)
- {
- //攻击结束
- ChangeState(PrevState);
- }
- else if (AttackState == AiAttackEnum.AttackInterval) //攻击完成
- {
- if (weapon.GetAttackTimer() <= 0) //攻击冷却完成
- {
- Master.MountLookTarget = true;
- //这里要做换弹判断, 还有上膛判断
- if (weapon.CurrAmmo <= 0) //换弹判断
- {
- if (!weapon.Reloading)
- {
- weapon.Reload();
- }
- }
- else if (weapon.GetBeLoadedStateState() != 2) //上膛
- {
- if (weapon.GetBeLoadedStateState() == 0)
- {
- weapon.BeLoaded();
- }
- }
- else
- {
- //攻击结束
- ChangeState(PrevState);
- }
- }
- MoveHandler(delta);
- }
- else //攻击状态
- {
- //触发扳机
- AttackState = weapon.AiTriggerAttackState(AttackState);
-
- if (AttackState == AiAttackEnum.LockingTime) //锁定玩家状态
- {
- Master.LockTargetTime += delta;
-
- var aiLockRemainderTime = Master.GetLockRemainderTime();
- Master.MountLookTarget = aiLockRemainderTime >= weapon.Attribute.AiAttackAttr.LockAngleTime;
- //更新瞄准辅助线
- if (weapon.Attribute.AiAttackAttr.ShowSubline)
- {
- if (Master.SubLine == null)
- {
- Master.InitSubLine();
- }
- else
- {
- Master.SubLine.Enable = true;
- }
-
- //播放警告删掉动画
- if (!Master.SubLine.IsPlayWarnAnimation && aiLockRemainderTime <= 0.5f)
- {
- Master.SubLine.PlayWarnAnimation(0.5f);
- }
- }
-
- if (weapon.Attribute.AiAttackAttr.LockingStand) //锁定目标时站立不动
- {
- Master.DoIdle();
- }
- else //正常移动
- {
- MoveHandler(delta);
- }
- }
- else
- {
- Master.LockTargetTime = 0;
- //关闭辅助线
- if (Master.SubLine != null)
- {
- Master.SubLine.Enable = false;
- }
-
- if (AttackState == AiAttackEnum.Attack || AttackState == AiAttackEnum.AttackInterval)
- {
- if (weapon.Attribute.AiAttackAttr.AttackLockAngle) //开火时锁定枪口角度
- {
- //连发状态锁定角度
- Master.MountLookTarget = !(weapon.GetContinuousCount() > 0 || weapon.GetAttackTimer() > 0);
- }
- else
- {
- Master.MountLookTarget = true;
- }
- }
- else
- {
- Master.MountLookTarget = true;
- }
-
- if (AttackState == AiAttackEnum.Attack && weapon.Attribute.AiAttackAttr.FiringStand) //开火时站立不动
- {
- Master.DoIdle();
- }
- else //正常移动
- {
- MoveHandler(delta);
- }
-
- if (AttackState == AiAttackEnum.AttackInterval) //触发攻击完成
- {
- Master.AttackTimer = weapon.Attribute.TriggerInterval + Master.EnemyRoleState.AttackInterval;
- }
- }
- }
- }
-
- //没有武器的敌人攻击逻辑
- private void NoWeaponRoleProcess(float delta)
- {
- var weapon = Master.WeaponPack.ActiveItem;
- if (weapon != null)
- {
- //找到武器了, 攻击结束
- ChangeState(PrevState);
- }
- else if (Master.AttackTimer > 0 || Master.MeleeAttackTimer > 0) //攻击结束
- {
- ChangeState(PrevState);
- }
- else //攻击状态
- {
- Master.Attack();
- }
- }
-
- private void MoveHandler(float delta)
- {
-
- if (_pauseTimer >= 0)
- {
- Master.AnimatedSprite.Play(AnimatorNames.Idle);
- _pauseTimer -= delta;
- }
- else if (_isMoveOver) //移动已经完成
- {
- RunOver(Master.LookTarget.Position);
- _isMoveOver = false;
- }
- else
- {
- var masterPosition = Master.Position;
- if (_lockTimer >= 1) //卡在一个点超过一秒
- {
- RunOver(Master.LookTarget.Position);
- _isMoveOver = false;
- _lockTimer = 0;
- }
- else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点
- {
- _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f);
- _isMoveOver = true;
- _moveFlag = false;
- //站立
- Master.DoIdle();
- }
- else if (!_moveFlag)
- {
- _moveFlag = true;
- //移动
- Master.DoMove();
- }
- else
- {
- var lastSlideCollision = Master.GetLastSlideCollision();
- if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色
- {
- _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f);
- _isMoveOver = true;
- _moveFlag = false;
- //站立
- Master.DoIdle();
- }
- else
- {
- //移动
- Master.DoMove();
- }
-
- if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta)
- {
- _lockTimer += delta;
- }
- else
- {
- _lockTimer = 0;
- _prevPos = masterPosition;
- }
- }
- }
- }
-
- private void RunOver(Vector2 targetPos)
- {
- var weapon = Master.WeaponPack.ActiveItem;
- var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f));
- _nextPosition = new Vector2(
- targetPos.X + Utils.Random.RandomRangeInt(-distance, distance),
- targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance)
- );
- Master.NavigationAgent2D.TargetPosition = _nextPosition;
- }
-
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs
deleted file mode 100644
index 207795b..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-
-using System;
-using Godot;
-
-namespace EnemyState;
-
-///
-/// Ai 寻找弹药, 进入该状态需要在参数中传入目标武器对象
-///
-public class AiFindAmmoState : StateBase
-{
- ///
- /// 目标武器
- ///
- public Weapon TargetWeapon;
-
- //导航目标点刷新计时器
- private float _navigationUpdateTimer = 0;
- private float _navigationInterval = 1f;
-
- private float _tailAfterTimer = 0;
- private ActivityObject _attackTarget;
-
- private float _idleTimer = 0;
- private bool _playAnimFlag = false;
-
- public AiFindAmmoState() : base(AIStateEnum.AiFindAmmo)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (args.Length == 0)
- {
- throw new Exception("进入 AiStateEnum.AiFindAmmo 状态必须要把目标武器当成参数传过来");
- }
-
- if (args.Length >= 2)
- {
- _attackTarget = (ActivityObject)args[1];
- }
- else
- {
- _attackTarget = null;
- }
-
- SetTargetWeapon((Weapon)args[0]);
- _navigationUpdateTimer = _navigationInterval;
- _tailAfterTimer = 0;
-
- //标记武器
- TargetWeapon.SetSign(SignNames.AiFindWeaponSign, Master);
-
- _playAnimFlag = prev == AIStateEnum.AiLeaveFor;
- if (_playAnimFlag)
- {
- Master.AnimationPlayer.Play(AnimatorNames.Query);
- }
- }
-
- public override void Process(float delta)
- {
- if (_playAnimFlag && _idleTimer > 0)
- {
- _idleTimer -= delta;
- return;
- }
-
- if (!Master.IsAllWeaponTotalAmmoEmpty()) //已经有弹药了
- {
- RunNextState();
- return;
- }
-
- if (Master.LookTarget == null) //没有目标
- {
- //临时处理
- var player = Player.Current;
- var playerPos = player.GetCenterPosition();
- if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- //发现玩家
- Master.LookTarget = player;
- //进入惊讶状态, 然后再进入通知状态
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFindAmmo, TargetWeapon);
- return;
- }
- }
-
- //更新目标位置
- if (_navigationUpdateTimer <= 0)
- {
- //每隔一段时间秒更改目标位置
- _navigationUpdateTimer = _navigationInterval;
- var position = TargetWeapon.GlobalPosition;
- Master.NavigationAgent2D.TargetPosition = position;
- }
- else
- {
- _navigationUpdateTimer -= delta;
- }
-
- if (TargetWeapon.IsDestroyed || TargetWeapon.IsTotalAmmoEmpty()) //已经被销毁, 或者弹药已经被其他角色捡走
- {
- //再去寻找其他武器
- SetTargetWeapon(Master.FindTargetWeapon());
-
- if (TargetWeapon == null) //也没有其他可用的武器了
- {
- RunNextState();
- }
- }
- else if (TargetWeapon.Master == Master) //已经被自己拾起
- {
- RunNextState();
- }
- else if (TargetWeapon.Master != null) //武器已经被其他角色拾起!
- {
- //再去寻找其他武器
- SetTargetWeapon(Master.FindTargetWeapon());
-
- if (TargetWeapon == null) //也没有其他可用的武器了
- {
- RunNextState();
- }
- }
- else
- {
- if (Master.LookTarget != null)
- {
- //检测目标没有超出跟随视野距离
- var isInTailAfterRange = Master.IsInTailAfterViewRange(Master.LookTarget.GetCenterPosition());
- if (isInTailAfterRange)
- {
- _tailAfterTimer = 0;
- }
- else
- {
- _tailAfterTimer += delta;
- }
- }
-
- //向武器移动
- if (!Master.NavigationAgent2D.IsNavigationFinished())
- {
- //移动
- Master.DoMove();
- }
- else
- {
- //站立
- Master.DoIdle();
- }
- }
- }
-
- private void RunNextState()
- {
- if (_attackTarget != null)
- {
- ChangeState(AIStateEnum.AiLeaveFor, _attackTarget);
- }
- else if (Master.LookTarget != null)
- {
- ChangeState(_tailAfterTimer > 10 ? AIStateEnum.AiNormal : AIStateEnum.AiTailAfter);
- }
- else
- {
- ChangeState(AIStateEnum.AiNormal);
- }
- }
-
- private void SetTargetWeapon(Weapon weapon)
- {
- TargetWeapon = weapon;
- if (weapon != null)
- {
- //设置目标点
- Master.NavigationAgent2D.TargetPosition = TargetWeapon.GlobalPosition;
- }
- }
-
- public override void DebugDraw()
- {
- if (TargetWeapon != null)
- {
- Master.DrawLine(Vector2.Zero, Master.ToLocal(TargetWeapon.GlobalPosition), Colors.Purple);
-
- if (Master.LookTarget != null)
- {
- if (_tailAfterTimer <= 0)
- {
- Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Orange);
- }
- else if (_tailAfterTimer <= 10)
- {
- Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.LookTarget.GetCenterPosition()), Colors.Blue);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs
deleted file mode 100644
index d0f1bde..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-
-using System;
-using Godot;
-
-namespace EnemyState;
-
-///
-/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火
-///
-public class AiFollowUpState : StateBase
-{
- //导航目标点刷新计时器
- private float _navigationUpdateTimer = 0;
- private float _navigationInterval = 0.3f;
-
- public AiFollowUpState() : base(AIStateEnum.AiFollowUp)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (Master.LookTarget == null)
- {
- throw new Exception("进入 AIAdvancedStateEnum.AiFollowUp 状态时角色没有攻击目标!");
- }
-
- _navigationUpdateTimer = 0;
- Master.TargetInView = true;
- }
-
- public override void Process(float delta)
- {
- //先检查弹药是否打光
- if (Master.IsAllWeaponTotalAmmoEmpty())
- {
- //再寻找是否有可用的武器
- var targetWeapon = Master.FindTargetWeapon();
- if (targetWeapon != null)
- {
- ChangeState(AIStateEnum.AiFindAmmo, targetWeapon);
- return;
- }
- else
- {
- //切换到随机移动状态
- ChangeState(AIStateEnum.AiSurround);
- }
- }
-
- var playerPos = Master.LookTarget.GetCenterPosition();
-
- //更新玩家位置
- if (_navigationUpdateTimer <= 0)
- {
- //每隔一段时间秒更改目标位置
- _navigationUpdateTimer = _navigationInterval;
- Master.NavigationAgent2D.TargetPosition = playerPos;
- }
- else
- {
- _navigationUpdateTimer -= delta;
- }
-
- //是否在攻击范围内
- var inAttackRange = false;
-
- var weapon = Master.WeaponPack.ActiveItem;
- var distanceSquared = Master.Position.DistanceSquaredTo(playerPos);
- if (weapon != null)
- {
- inAttackRange = distanceSquared <= Mathf.Pow(Master.GetWeaponRange(0.7f), 2);
- }
- else
- {
- inAttackRange = distanceSquared <= Mathf.Pow(Master.EnemyRoleState.ViewRange * 0.7f, 2);
- }
-
- if (!Master.NavigationAgent2D.IsNavigationFinished())
- {
- //移动
- Master.DoMove();
- }
- else
- {
- //站立
- Master.DoIdle();
- }
-
- //检测玩家是否在视野内
- if (Master.IsInTailAfterViewRange(playerPos))
- {
- Master.TargetInView = !Master.TestViewRayCast(playerPos);
- //关闭射线检测
- Master.TestViewRayCastOver();
- }
- else
- {
- Master.TargetInView = false;
- }
-
- //在视野中
- if (Master.TargetInView)
- {
- //更新标记位置
- Master.UpdateMarkTargetPosition();
- if (inAttackRange) //在攻击范围内
- {
- if (weapon != null)
- {
- //距离够近, 可以切换到环绕模式
- if (distanceSquared <= Mathf.Pow(Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f, 2))
- {
- ChangeState(AIStateEnum.AiSurround);
- }
- else if (!Master.IsAttack && weapon.TriggerIsReady()) //可以攻击
- {
- //攻击状态
- ChangeState(AIStateEnum.AiAttack);
- }
- }
- else
- {
- //距离够近, 可以切换到环绕模式
- if (distanceSquared <= Mathf.Pow(Master.EnemyRoleState.ViewRange * 0.7f, 2))
- {
- ChangeState(AIStateEnum.AiSurround);
- }
- else if (!Master.IsAttack && Master.NoWeaponAttack) //可以在没有武器时发起攻击
- {
- //攻击状态
- ChangeState(AIStateEnum.AiAttack);
- }
- }
- }
- }
- else //不在视野中
- {
- ChangeState(AIStateEnum.AiTailAfter);
- }
- }
-
- public override void DebugDraw()
- {
- var playerPos = Master.LookTarget.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/activity/role/enemy/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs
deleted file mode 100644
index 2ec0526..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-
-using System;
-using Godot;
-
-namespace EnemyState;
-
-///
-/// 收到其他敌人通知, 前往发现目标的位置
-///
-public class AiLeaveForState : StateBase
-{
- //导航目标点刷新计时器
- private float _navigationUpdateTimer = 0;
- private float _navigationInterval = 0.3f;
-
- //目标
- private ActivityObject _target;
- //目标点
- private Vector2 _targetPosition;
-
- private float _idleTimer = 0;
- private bool _playAnimFlag = false;
-
- public AiLeaveForState() : base(AIStateEnum.AiLeaveFor)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (args.Length == 0)
- {
- throw new Exception("进入 AINormalStateEnum.AiLeaveFor 状态必须带上目标对象");
- }
-
- _target = (ActivityObject)args[0];
-
- //先检查弹药是否打光
- if (Master.IsAllWeaponTotalAmmoEmpty())
- {
- //再寻找是否有可用的武器
- var targetWeapon = Master.FindTargetWeapon();
- if (targetWeapon != null)
- {
- ChangeState(AIStateEnum.AiFindAmmo, targetWeapon, _target);
- return;
- }
- }
-
- _idleTimer = 1;
- _targetPosition = _target.GetCenterPosition();
- Master.LookTargetPosition(_targetPosition);
-
- _playAnimFlag = prev != AIStateEnum.AiFindAmmo;
- if (_playAnimFlag)
- {
- Master.AnimationPlayer.Play(AnimatorNames.Query);
- }
-
- //看向目标位置
- Master.LookTargetPosition(_target.GetCenterPosition());
- }
-
- public override void Exit(AIStateEnum next)
- {
- Master.AnimationPlayer.Play(AnimatorNames.Reset);
- }
-
- public override void Process(float delta)
- {
- if (_playAnimFlag && _idleTimer > 0)
- {
- _idleTimer -= delta;
- return;
- }
- //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽
-
- //更新玩家位置
- if (_navigationUpdateTimer <= 0)
- {
- //每隔一段时间秒更改目标位置
- _navigationUpdateTimer = _navigationInterval;
- if (Master.AffiliationArea.RoomInfo.MarkTargetPosition.TryGetValue(_target.Id, out var pos))
- {
- _targetPosition = pos;
- }
- Master.NavigationAgent2D.TargetPosition = _targetPosition;
- }
- else
- {
- _navigationUpdateTimer -= delta;
- }
-
- if (!Master.NavigationAgent2D.IsNavigationFinished())
- {
- Master.LookTargetPosition(_targetPosition);
- //移动
- Master.DoMove();
- }
- else
- {
- //站立
- Master.DoIdle();
- }
-
- var playerPos = Player.Current.GetCenterPosition();
- //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态
- if (Master.IsInTailAfterViewRange(playerPos))
- {
- if (!Master.TestViewRayCast(playerPos)) //看到玩家
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- //切换成发现目标状态
- Master.LookTarget = Player.Current;
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFollowUp);
- return;
- }
- else
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- }
- }
-
- //移动到目标掉了, 还没发现目标
- if (Master.NavigationAgent2D.IsNavigationFinished())
- {
- ChangeState(AIStateEnum.AiNormal);
- }
- }
-
- public override void DebugDraw()
- {
- Master.DrawLine(Vector2.Zero, Master.ToLocal(Master.NavigationAgent2D.TargetPosition), Colors.Yellow);
- }
-}
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs
deleted file mode 100644
index c4635c9..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-
-using System.Linq;
-using Godot;
-
-namespace EnemyState;
-
-///
-/// AI 正常状态
-///
-public class AiNormalState : StateBase
-{
- //下一个运动的坐标
- private Vector2 _nextPos;
-
- //是否移动结束
- private bool _isMoveOver;
-
- //上一次移动是否撞墙
- private bool _againstWall;
-
- //撞墙法线角度
- private float _againstWallNormalAngle;
-
- //移动停顿计时器
- private float _pauseTimer;
- private bool _moveFlag;
-
- //上一帧位置
- private Vector2 _prevPos;
- //卡在一个位置的时间
- private float _lockTimer;
-
- public AiNormalState() : base(AIStateEnum.AiNormal)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- _isMoveOver = true;
- _againstWall = false;
- _againstWallNormalAngle = 0;
- _pauseTimer = 0;
- _moveFlag = false;
- Master.LookTarget = null;
- }
-
- public override void Process(float delta)
- {
- //检测玩家
- var player = Player.Current;
- //玩家中心点坐标
- var playerPos = player.GetCenterPosition();
-
- if (Master.IsInViewRange(playerPos) && !Master.TestViewRayCast(playerPos)) //发现玩家
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- //发现玩家
- Master.LookTarget = player;
- //判断是否进入通知状态
- if (Master.World.Enemy_InstanceList.FindIndex(enemy =>
- enemy != Master && !enemy.IsDie && enemy.AffiliationArea == Master.AffiliationArea &&
- enemy.StateController.CurrState == AIStateEnum.AiNormal) != -1)
- {
- //进入惊讶状态, 然后再进入通知状态
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify);
- }
- else
- {
- //进入惊讶状态, 然后再进入跟随状态
- ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
- }
- return;
- }
- else if (_pauseTimer >= 0)
- {
- Master.AnimatedSprite.Play(AnimatorNames.Idle);
- _pauseTimer -= delta;
- }
- else if (_isMoveOver) //没发现玩家, 且已经移动完成
- {
- RunOver();
- _isMoveOver = false;
- }
- else //移动中
- {
- if (_lockTimer >= 1) //卡在一个点超过一秒
- {
- RunOver();
- _isMoveOver = false;
- _lockTimer = 0;
- }
- else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点
- {
- _pauseTimer = Utils.Random.RandomRangeFloat(0.3f, 2f);
- _isMoveOver = true;
- _moveFlag = false;
- //站立
- Master.DoIdle();
- }
- else if (!_moveFlag)
- {
- _moveFlag = true;
- var pos = Master.Position;
- //移动
- Master.DoMove();
- _prevPos = pos;
- }
- else
- {
- var pos = Master.Position;
- var lastSlideCollision = Master.GetLastSlideCollision();
- if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色
- {
- _pauseTimer = Utils.Random.RandomRangeFloat(0.1f, 0.5f);
- _isMoveOver = true;
- _moveFlag = false;
- //站立
- Master.DoIdle();
- }
- else
- {
- //移动
- Master.DoMove();
- }
-
- if (_prevPos.DistanceSquaredTo(pos) <= 0.01f)
- {
- _lockTimer += delta;
- }
- else
- {
- _prevPos = pos;
- }
- }
- }
-
- //关闭射线检测
- Master.TestViewRayCastOver();
- }
-
- //移动结束
- private void RunOver()
- {
- float angle;
- if (_againstWall)
- {
- angle = Utils.Random.RandomRangeFloat(_againstWallNormalAngle - Mathf.Pi * 0.5f,
- _againstWallNormalAngle + Mathf.Pi * 0.5f);
- }
- else
- {
- angle = Utils.Random.RandomRangeFloat(0, Mathf.Pi * 2f);
- }
-
- var len = Utils.Random.RandomRangeInt(30, 200);
- _nextPos = new Vector2(len, 0).Rotated(angle) + Master.GlobalPosition;
- //获取射线碰到的坐标
- if (Master.TestViewRayCast(_nextPos)) //碰到墙壁
- {
- _nextPos = Master.ViewRay.GetCollisionPoint();
- _againstWall = true;
- _againstWallNormalAngle = Master.ViewRay.GetCollisionNormal().Angle();
- }
- else
- {
- _againstWall = false;
- }
-
- Master.NavigationAgent2D.TargetPosition = _nextPos;
- Master.LookTargetPosition(_nextPos);
- }
-
- public override void DebugDraw()
- {
- Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPos), Colors.Green);
- }
-}
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs
deleted file mode 100644
index 09a6b08..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-
-namespace EnemyState;
-
-///
-/// 发现目标, 通知其它敌人
-///
-public class AiNotifyState : StateBase
-{
- private float _timer;
-
- public AiNotifyState() : base(AIStateEnum.AiNotify)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (Master.LookTarget == null)
- {
- throw new Exception("进入 AIAdvancedStateEnum.AiNotify 没有攻击目标!");
- }
- _timer = 1.2f;
- //通知其它角色
- Master.World.NotifyEnemyTarget(Master, Master.LookTarget);
- Master.AnimationPlayer.Play(AnimatorNames.Notify);
- }
-
- public override void Process(float delta)
- {
- Master.DoIdle();
- _timer -= delta;
- if (_timer <= 0)
- {
- ChangeState(AIStateEnum.AiTailAfter);
- }
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs
deleted file mode 100644
index da694dd..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs
+++ /dev/null
@@ -1,187 +0,0 @@
-
-using System;
-using Godot;
-
-namespace EnemyState;
-
-///
-/// 距离目标足够近, 在目标附近随机移动, 并开火
-///
-public class AiSurroundState : StateBase
-{
- //是否移动结束
- private bool _isMoveOver;
-
- //移动停顿计时器
- private float _pauseTimer;
- private bool _moveFlag;
-
- //下一个移动点
- private Vector2 _nextPosition;
-
- //上一帧位置
- private Vector2 _prevPos;
- //卡在一个位置的时间
- private float _lockTimer;
-
- public AiSurroundState() : base(AIStateEnum.AiSurround)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (Master.LookTarget == null)
- {
- throw new Exception("进入 AIAdvancedStateEnum.AiSurround 状态时角色没有攻击目标!");
- }
-
- Master.TargetInView = true;
- _isMoveOver = true;
- _pauseTimer = 0;
- _moveFlag = false;
- }
-
- public override void Process(float delta)
- {
- //先检查弹药是否打光
- if (Master.IsAllWeaponTotalAmmoEmpty())
- {
- //再寻找是否有可用的武器
- var targetWeapon = Master.FindTargetWeapon();
- if (targetWeapon != null)
- {
- ChangeState(AIStateEnum.AiFindAmmo, targetWeapon);
- return;
- }
- }
-
- var playerPos = Master.LookTarget.GetCenterPosition();
-
- //检测玩家是否在视野内
- if (Master.IsInTailAfterViewRange(playerPos))
- {
- Master.TargetInView = !Master.TestViewRayCast(playerPos);
- //关闭射线检测
- Master.TestViewRayCastOver();
- }
- else
- {
- Master.TargetInView = false;
- }
-
- //在视野中
- if (Master.TargetInView)
- {
- //更新标记位置
- Master.UpdateMarkTargetPosition();
-
- if (_pauseTimer >= 0)
- {
- Master.AnimatedSprite.Play(AnimatorNames.Idle);
- _pauseTimer -= delta;
- }
- else if (_isMoveOver) //移动已经完成
- {
- RunOver(playerPos);
- _isMoveOver = false;
- }
- else
- {
- var masterPosition = Master.Position;
- if (_lockTimer >= 1) //卡在一个点超过一秒
- {
- RunOver(playerPos);
- _isMoveOver = false;
- _lockTimer = 0;
- }
- else if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点
- {
- _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.5f);
- _isMoveOver = true;
- _moveFlag = false;
- //站立
- Master.DoIdle();
- }
- else if (!_moveFlag)
- {
- _moveFlag = true;
- //移动
- Master.DoMove();
- }
- else
- {
- var lastSlideCollision = Master.GetLastSlideCollision();
- if (lastSlideCollision != null && lastSlideCollision.GetCollider() is Role) //碰到其他角色
- {
- _pauseTimer = Utils.Random.RandomRangeFloat(0f, 0.3f);
- _isMoveOver = true;
- _moveFlag = false;
- //站立
- Master.DoIdle();
- }
- else
- {
- //移动
- Master.DoMove();
- }
-
- if (_prevPos.DistanceSquaredTo(masterPosition) <= 1 * delta)
- {
- _lockTimer += delta;
- }
- else
- {
- _lockTimer = 0;
- _prevPos = masterPosition;
- }
- }
-
- var weapon = Master.WeaponPack.ActiveItem;
- if (weapon != null)
- {
- if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.GetWeaponRange(0.7f), 2)) //玩家离开正常射击范围
- {
- ChangeState(AIStateEnum.AiFollowUp);
- }
- else if (!Master.IsAttack && weapon.TriggerIsReady()) //可以攻击
- {
- //发起攻击
- ChangeState(AIStateEnum.AiAttack);
- }
- }
- else
- {
- if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.EnemyRoleState.ViewRange * 0.7f, 2)) //玩家离开正常射击范围
- {
- ChangeState(AIStateEnum.AiFollowUp);
- }
- else if (!Master.IsAttack && Master.NoWeaponAttack) //可以在没有武器时发起攻击
- {
- //攻击状态
- ChangeState(AIStateEnum.AiAttack);
- }
- }
- }
- }
- else //目标离开视野
- {
- ChangeState(AIStateEnum.AiTailAfter);
- }
- }
-
- private void RunOver(Vector2 targetPos)
- {
- var weapon = Master.WeaponPack.ActiveItem;
- var distance = (int)(weapon == null ? 150 : (Utils.GetConfigRangeStart(weapon.Attribute.Bullet.DistanceRange) * 0.7f));
- _nextPosition = new Vector2(
- targetPos.X + Utils.Random.RandomRangeInt(-distance, distance),
- targetPos.Y + Utils.Random.RandomRangeInt(-distance, distance)
- );
- Master.NavigationAgent2D.TargetPosition = _nextPosition;
- }
-
- public override void DebugDraw()
- {
- Master.DrawLine(new Vector2(0, -8), Master.ToLocal(_nextPosition), Colors.White);
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs
deleted file mode 100644
index 4fa2cc5..0000000
--- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-
-using System;
-using Godot;
-
-namespace EnemyState;
-
-///
-/// AI 发现玩家, 跟随玩家, 但是不在视野范围内
-///
-public class AiTailAfterState : StateBase
-{
- ///
- /// 目标是否在视野半径内
- ///
- private bool _isInViewRange;
-
- //导航目标点刷新计时器
- private float _navigationUpdateTimer = 0;
- private float _navigationInterval = 0.3f;
-
- //目标从视野消失时已经过去的时间
- private float _viewTimer;
-
- public AiTailAfterState() : base(AIStateEnum.AiTailAfter)
- {
- }
-
- public override void Enter(AIStateEnum prev, params object[] args)
- {
- if (Master.LookTarget == null)
- {
- throw new Exception("进入 AIAdvancedStateEnum.AiTailAfter 状态时角色没有攻击目标!");
- }
-
- _isInViewRange = true;
- _navigationUpdateTimer = 0;
- _viewTimer = 0;
-
- //先检查弹药是否打光
- if (Master.IsAllWeaponTotalAmmoEmpty())
- {
- //再寻找是否有可用的武器
- var targetWeapon = Master.FindTargetWeapon();
- if (targetWeapon != null)
- {
- ChangeState(AIStateEnum.AiFindAmmo, targetWeapon);
- }
- }
- }
-
- public override void Process(float delta)
- {
- //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽
-
- var playerPos = Master.LookTarget.GetCenterPosition();
-
- //更新玩家位置
- if (_navigationUpdateTimer <= 0)
- {
- //每隔一段时间秒更改目标位置
- _navigationUpdateTimer = _navigationInterval;
- Master.NavigationAgent2D.TargetPosition = playerPos;
- }
- else
- {
- _navigationUpdateTimer -= delta;
- }
-
- if (!Master.NavigationAgent2D.IsNavigationFinished())
- {
- //移动
- Master.DoMove();
- }
- else
- {
- //站立
- Master.DoIdle();
- }
- //检测玩家是否在视野内, 如果在, 则切换到 AiTargetInView 状态
- if (Master.IsInTailAfterViewRange(playerPos))
- {
- if (!Master.TestViewRayCast(playerPos)) //看到玩家
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- //切换成发现目标状态
- ChangeState(AIStateEnum.AiFollowUp);
- return;
- }
- else
- {
- //关闭射线检测
- Master.TestViewRayCastOver();
- }
- }
-
- //检测玩家是否在穿墙视野范围内, 直接检测距离即可
- _isInViewRange = Master.IsInViewRange(playerPos);
- if (_isInViewRange)
- {
- _viewTimer = 0;
- }
- else //超出视野
- {
- if (_viewTimer > 10) //10秒
- {
- ChangeState(AIStateEnum.AiNormal);
- }
- else
- {
- _viewTimer += delta;
- }
- }
- }
-
- public override void DebugDraw()
- {
- var playerPos = Master.LookTarget.GetCenterPosition();
- if (_isInViewRange)
- {
- Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Orange);
- }
- else
- {
- Master.DrawLine(new Vector2(0, -8), Master.ToLocal(playerPos), Colors.Blue);
- }
- }
-}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/role/shop/ShopBoss.cs b/DungeonShooting_Godot/src/game/activity/role/shop/ShopBoss.cs
new file mode 100644
index 0000000..b6153c3
--- /dev/null
+++ b/DungeonShooting_Godot/src/game/activity/role/shop/ShopBoss.cs
@@ -0,0 +1,14 @@
+
+using Godot;
+
+///
+/// 商店老板
+///
+[Tool]
+public partial class ShopBoss : AiRole
+{
+ public override void OnCreateWithMark(RoomPreinstall roomPreinstall, ActivityMark activityMark)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/DungeonShooting_Godot/src/game/activity/shop/ShopBoss.cs b/DungeonShooting_Godot/src/game/activity/shop/ShopBoss.cs
deleted file mode 100644
index 1811b53..0000000
--- a/DungeonShooting_Godot/src/game/activity/shop/ShopBoss.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-
-using Godot;
-
-///
-/// 商店老板
-///
-[Tool]
-public partial class ShopBoss : Role
-{
- public override void OnCreateWithMark(RoomPreinstall roomPreinstall, ActivityMark activityMark)
- {
-
- }
-}
\ No newline at end of file