diff --git a/DungeonShooting_Godot/prefab/role/Player.tscn b/DungeonShooting_Godot/prefab/role/Player.tscn index 7bfb950..5786938 100644 --- a/DungeonShooting_Godot/prefab/role/Player.tscn +++ b/DungeonShooting_Godot/prefab/role/Player.tscn @@ -24,4 +24,4 @@ [node name="AnimatedSprite" parent="." index="2"] material = SubResource( 2 ) -frame = 3 +frame = 2 diff --git a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs index 98940e9..9016553 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs @@ -18,20 +18,26 @@ /// public class Enemy : Role { + + /// + /// 公共属性, 是否找到玩家, 如果找到玩家, 则所有敌人都会知道玩家的位置 + /// + public static bool IsFindPlayer { get; set; } + /// /// 敌人身上的状态机控制器 /// public StateController StateController { get; } /// - /// 视野半径, 单位像素 + /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙 /// - public float ViewRange { get; set; } = 200; + public float ViewRange { get; set; } = 250; /// /// 发现玩家后的视野半径 /// - public float TailAfterViewRange { get; set; } = 250; + public float TailAfterViewRange { get; set; } = 400; /// /// 背后的视野半径, 单位像素 @@ -53,6 +59,8 @@ /// public Position2D NavigationPoint { get; } + private float _enemyAttackTimer = 0; + public Enemy() : base(ResourcePath.prefab_role_Enemy_tscn) { StateController = new StateController(); @@ -88,6 +96,36 @@ NavigationAgent2D.SetTargetLocation(GameApplication.Instance.Room.Player.GlobalPosition); } + public override void _PhysicsProcess(float delta) + { + base._PhysicsProcess(delta); + + _enemyAttackTimer -= delta; + } + + /// + /// Ai触发的攻击 + /// + public void EnemyAttack() + { + var weapon = Holster.ActiveWeapon; + if (weapon != null) + { + if (weapon.Attribute.ContinuousShoot) //连发 + { + Attack(); + } + else //单发 + { + if (_enemyAttackTimer <= 0) + { + _enemyAttackTimer = 60f / weapon.Attribute.StartFiringSpeed; + Attack(); + } + } + } + } + /// /// 返回目标点是否在视野范围内 /// diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs index 03a0d35..35e44bd 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiNormalState.cs @@ -8,11 +8,22 @@ { //是否发现玩家 private bool _isFindPlayer; + //下一个运动的角度 private Vector2 _nextPos; + //是否移动结束 private bool _isMoveOver; + + //上一次移动是否撞墙 + private bool _againstWall; + //撞墙法线角度 + private float _againstWallNormalAngle; + + //移动停顿计时器 + private float _pauseTimer; + public AiNormalState() : base(AIStateEnum.AINormal) { } @@ -21,6 +32,9 @@ { _isFindPlayer = false; _isMoveOver = true; + _againstWall = false; + _againstWallNormalAngle = 0; + _pauseTimer = 0; } public override void PhysicsProcess(float delta) @@ -43,37 +57,67 @@ //发现玩家 _isFindPlayer = true; } + else if (_pauseTimer >= 0) + { + _pauseTimer -= delta; + } else if (_isMoveOver) //没发现玩家, 且已经移动完成 { - var angle = Utils.RandRange(0, Mathf.Pi * 2f); - var len = Utils.RandRangeInt(50, 500); - _nextPos = new Vector2(len, 0).Rotated(angle); - //获取射线碰到的坐标 - if (Master.TestViewRayCast(_nextPos)) //碰到墙壁 - { - _nextPos = Master.ViewRay.GetCollisionPoint(); - } - Master.NavigationAgent2D.SetTargetLocation(_nextPos); + RunOver(); _isMoveOver = false; } else //移动中 { //计算移动 var nextPos = Master.NavigationAgent2D.GetNextLocation(); - Master.LookTargetPosition(_nextPos); Master.AnimatedSprite.Animation = AnimatorNames.Run; - Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * Master.MoveSpeed; + Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * + Master.MoveSpeed; Master.CalcMove(delta); - if (Master.NavigationAgent2D.IsNavigationFinished()) + if (Master.NavigationAgent2D.IsNavigationFinished()) //到达终点 { + _pauseTimer = 1; _isMoveOver = true; } } + Master.TestViewRayCastOver(); } } + //移动结束 + private void RunOver() + { + float angle; + if (_againstWall) + { + angle = Utils.RandRange(_againstWallNormalAngle - Mathf.Pi * 0.5f, + _againstWallNormalAngle + Mathf.Pi * 0.5f); + } + else + { + angle = Utils.RandRange(0, Mathf.Pi * 2f); + } + + var len = Utils.RandRangeInt(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.SetTargetLocation(_nextPos); + Master.LookTargetPosition(_nextPos); + } + public override void DebugDraw() { Master.DrawLine(Vector2.Zero, Master.ToLocal(_nextPos), Colors.Green); diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs index 5ff99ac..909715f 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs @@ -10,9 +10,14 @@ private float _navigationUpdateTimer = 0; private float _navigationInterval = 0.3f; - private bool _isInView = true; + //目标是否在视野半径内 + private bool _isInViewRange; - private float _viewTimer = 0; + //是否在视野内 + private bool _isInView; + + //目标从视野消失时已经过去的时间 + private float _viewTimer; public AiTailAfterState() : base(AIStateEnum.AITailAfter) { @@ -20,16 +25,14 @@ public override void Enter(AIStateEnum prev, params object[] args) { + _isInViewRange = true; _isInView = true; _navigationUpdateTimer = 0; + _viewTimer = 0; } public override void PhysicsProcess(float delta) { - if (Master.NavigationAgent2D.IsNavigationFinished()) - { - return; - } var masterPos = Master.GlobalPosition; var playerPos = GameApplication.Instance.Room.Player.GlobalPosition; @@ -56,16 +59,40 @@ Master.CalcMove(delta); //检测玩家是否在视野内, 此时视野可穿墙, 直接检测距离即可 - _isInView = masterPos.DistanceSquaredTo(playerPos) <= Master.TailAfterViewRange * Master.TailAfterViewRange; - if (_isInView) + + if (masterPos.DistanceSquaredTo(playerPos) <= Master.TailAfterViewRange * Master.TailAfterViewRange) + { + _isInView = !Master.TestViewRayCast(playerPos); + if (_isInView) + { + _isInViewRange = true; + } + else + { + _isInViewRange = masterPos.DistanceSquaredTo(playerPos) <= Master.ViewRange * Master.ViewRange; + } + } + else + { + _isInViewRange = masterPos.DistanceSquaredTo(playerPos) <= Master.ViewRange * Master.ViewRange; + _isInView = false; + } + + if (_isInViewRange) { _viewTimer = 0; + + if (_isInView) + { + //攻击 + Master.EnemyAttack(); + } } else //超出视野 { if (_viewTimer > 10) //10秒 { - ChangeStateLate(AIStateEnum.AIProbe); + ChangeStateLate(AIStateEnum.AINormal); } else { @@ -81,6 +108,10 @@ { Master.DrawLine(Vector2.Zero, Master.ToLocal(playerPos), Colors.Red); } + else if (_isInViewRange) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(playerPos), Colors.Orange); + } else { Master.DrawLine(Vector2.Zero, Master.ToLocal(playerPos), Colors.Blue); diff --git a/DungeonShooting_Godot/src/game/room/RoomManager.cs b/DungeonShooting_Godot/src/game/room/RoomManager.cs index 9a39e41..9643006 100644 --- a/DungeonShooting_Godot/src/game/room/RoomManager.cs +++ b/DungeonShooting_Godot/src/game/room/RoomManager.cs @@ -91,6 +91,12 @@ enemy2.PickUpWeapon(WeaponManager.GetGun("1002")); enemy2.PickUpWeapon(WeaponManager.GetGun("1004")); enemy2.PickUpWeapon(WeaponManager.GetGun("1003")); + + var enemy3 = new Enemy(); + enemy3.Name = "Enemy3"; + enemy3.PutDown(new Vector2(540, 300)); + enemy3.PickUpWeapon(WeaponManager.GetGun("1003")); + enemy3.PickUpWeapon(WeaponManager.GetGun("1002")); WeaponManager.GetGun("1001").PutDown(new Vector2(80, 100)); WeaponManager.GetGun("1001").PutDown(new Vector2(80, 80));