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