diff --git a/DungeonShooting_Godot/scene/Main.tscn b/DungeonShooting_Godot/scene/Main.tscn index 35a4274..aff7b6d 100644 --- a/DungeonShooting_Godot/scene/Main.tscn +++ b/DungeonShooting_Godot/scene/Main.tscn @@ -24,7 +24,6 @@ [node name="Main" type="Node2D"] script = ExtResource( 3 ) -Debug = true CursorPack = ExtResource( 4 ) RoomPath = NodePath("ViewCanvas/ViewportContainer/Viewport/Room") ViewportPath = NodePath("ViewCanvas/ViewportContainer/Viewport") diff --git a/DungeonShooting_Godot/src/game/SignNames.cs b/DungeonShooting_Godot/src/game/SignNames.cs new file mode 100644 index 0000000..5bc2b82 --- /dev/null +++ b/DungeonShooting_Godot/src/game/SignNames.cs @@ -0,0 +1,11 @@ + +/// +/// 标记名称 +/// +public class SignNames +{ + /// + /// Ai 对武器的标记名称, 一旦有该标记, Ai AiFindAmmoState 状态下寻找可用武器将忽略该武器 + /// + public const string AiFindWeaponSign = "AiFindWeaponSign"; +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/item/package/Holster.cs b/DungeonShooting_Godot/src/game/item/package/Holster.cs index 2b281b2..8cdac81 100644 --- a/DungeonShooting_Godot/src/game/item/package/Holster.cs +++ b/DungeonShooting_Godot/src/game/item/package/Holster.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Godot; /// @@ -117,14 +118,12 @@ /// /// 通过回调函数查询武器在武器袋中的位置, 如果没有, 则返回 -1 /// - /// - /// - public int FindWeapon(Func handler) + public int FindWeapon(Func handler) { for (int i = 0; i < SlotList.Length; i++) { var item = SlotList[i]; - if (item.Weapon != null && handler(item.Weapon)) + if (item.Weapon != null && handler(item.Weapon, i)) { return i; } @@ -133,6 +132,43 @@ } /// + /// 遍历所有武器 + /// + public void ForEach(Action handler) + { + for (int i = 0; i < SlotList.Length; i++) + { + var item = SlotList[i]; + if (item.Weapon != null) + { + handler(item.Weapon, i); + } + } + } + + /// + /// 从武器袋中移除所有武器, 并返回 + /// + public Weapon[] GetAndClearWeapon() + { + List weapons = new List(); + for (int i = 0; i < SlotList.Length; i++) + { + var slot = SlotList[i]; + var weapon = slot.Weapon; + if (weapon != null) + { + weapon.GetParent().RemoveChild(weapon); + weapon.Remove(); + weapons.Add(weapon); + slot.Weapon = null; + } + } + + return weapons.ToArray(); + } + + /// /// 返回是否能放入武器 /// /// 武器对象 diff --git a/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs b/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs index a870c8b..6c5ca8e 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs @@ -952,7 +952,7 @@ //禁用碰撞 CollisionShape2D.Disabled = true; //清除 Ai 拾起标记 - RemoveSign(AiFindAmmoState.AiFindWeaponSign); + RemoveSign(SignNames.AiFindWeaponSign); OnPickUp(master); } diff --git a/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs b/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs index dd070fc..28f33d1 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/bullet/Bullet.cs @@ -70,7 +70,7 @@ var role = other.AsActivityObject(); if (role != null) { - role.Hurt(1); + role.CallDeferred(nameof(Role.Hurt), 4); Destroy(); } } diff --git a/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs b/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs index aee151f..b90ac06 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs @@ -118,7 +118,7 @@ { if (activityObject is Role role) { - role.Hurt(1); + role.CallDeferred(nameof(Role.Hurt), 10); } } } diff --git a/DungeonShooting_Godot/src/game/role/Role.cs b/DungeonShooting_Godot/src/game/role/Role.cs index a524d9b..b1ee678 100644 --- a/DungeonShooting_Godot/src/game/role/Role.cs +++ b/DungeonShooting_Godot/src/game/role/Role.cs @@ -79,6 +79,11 @@ public Vector2 Velocity { get; set; } = Vector2.Zero; /// + /// 是否死亡 + /// + public bool IsDie { get; private set; } + + /// /// 血量 /// public int Hp @@ -91,7 +96,7 @@ if (temp != _hp) OnChangeHp(_hp); } } - private int _hp = 0; + private int _hp = 20; /// /// 最大血量 @@ -107,7 +112,7 @@ if (temp != _maxHp) OnChangeMaxHp(_maxHp); } } - private int _maxHp = 0; + private int _maxHp = 20; /// /// 当前护盾值 @@ -257,7 +262,7 @@ public override void _Process(float delta) { base._Process(delta); - + //看向目标 if (LookTarget != null) { @@ -368,6 +373,22 @@ } /// + /// 返回所有武器是否弹药都打光了 + /// + public bool IsAllWeaponTotalAmmoEmpty() + { + foreach (var weaponSlot in Holster.SlotList) + { + if (weaponSlot.Weapon != null && !weaponSlot.Weapon.IsTotalAmmoEmpty()) + { + return false; + } + } + + return true; + } + + /// /// 拾起一个武器, 返回是否成功拾取, 如果不想立刻切换到该武器, exchange 请传 false /// /// 武器对象 @@ -468,7 +489,7 @@ } /// - /// 受到伤害 + /// 受到伤害, 如果是在碰撞信号处理函数中调用该函数, 请使用 CallDeferred 来延时调用, 否则很有可能导致报错 /// /// 伤害的量 public virtual void Hurt(int damage) @@ -485,6 +506,17 @@ AnimationPlayer.Stop(); AnimationPlayer.Play("hit"); + + //死亡判定 + if (Hp <= 0) + { + //死亡 + if (!IsDie) + { + IsDie = true; + OnDie(); + } + } } /// diff --git a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs index 74bd59d..e2e3143 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs @@ -81,6 +81,9 @@ Holster.SlotList[2].Enable = true; Holster.SlotList[3].Enable = true; + + MaxHp = 20; + Hp = 20; //视野射线 ViewRay = GetNode("ViewRay"); @@ -122,6 +125,17 @@ _enemies.Remove(this); } + protected override void OnDie() + { + //扔掉所有武器 + var weapons = Holster.GetAndClearWeapon(); + for (var i = 0; i < weapons.Length; i++) + { + weapons[i].ThrowWeapon(this); + } + Destroy(); + } + public override void _PhysicsProcess(float delta) { base._PhysicsProcess(delta); @@ -131,6 +145,33 @@ EnemyPickUpWeapon(); } + protected override void OnHit(int damage) + { + //受到伤害 + var state = StateController.CurrState; + if (state == AiStateEnum.AiNormal || state == AiStateEnum.AiProbe || state == AiStateEnum.AiLeaveFor) + { + StateController.ChangeStateLate(AiStateEnum.AiTailAfter); + } + } + + /// + /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器 + /// + public bool CheckUsableWeaponInUnclaimed() + { + //如果存在有子弹的武器 + foreach (var unclaimedWeapon in Weapon.UnclaimedWeapons) + { + if (!unclaimedWeapon.IsTotalAmmoEmpty() && !unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign)) + { + return true; + } + } + + return false; + } + /// /// 更新敌人视野 /// @@ -140,7 +181,8 @@ for (var i = 0; i < _enemies.Count; i++) { var enemy = _enemies[i]; - if (enemy.StateController.CurrState == AiStateEnum.AiFollowUp) //目标在视野内 + var state = enemy.StateController.CurrState; + if (state == AiStateEnum.AiFollowUp || state == AiStateEnum.AiSurround) //目标在视野内 { IsFindTarget = true; FindTargetPosition = Player.Current.GetCenterPosition(); @@ -159,21 +201,14 @@ if (weapon.IsTotalAmmoEmpty()) //当前武器弹药打空 { //切换到有子弹的武器 - var index = Holster.FindWeapon(we => !we.IsTotalAmmoEmpty()); + var index = Holster.FindWeapon((we, i) => !we.IsTotalAmmoEmpty()); if (index != -1) { Holster.ExchangeByIndex(index); } - else //所有子弹打光, 去寻找武器 + else //所有子弹打光 { - //如果存在有子弹的武器 - foreach (var unclaimedWeapon in Weapon.UnclaimedWeapons) - { - if (!unclaimedWeapon.IsTotalAmmoEmpty()) - { - StateController.ChangeStateLate(AiStateEnum.AiFindAmmo); - } - } + } } else if (weapon.Reloading) //换弹中 @@ -297,7 +332,7 @@ return; } - var index = Holster.FindWeapon(we => we.TypeId == weapon.TypeId); + var index = Holster.FindWeapon((we, i) => we.TypeId == weapon.TypeId); if (index != -1) //与武器袋中武器类型相同, 补充子弹 { if (!Holster.GetWeapon(index).IsAmmoFull()) @@ -308,7 +343,7 @@ return; } - var index2 = Holster.FindWeapon(we => + var index2 = Holster.FindWeapon((we, i) => we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty()); if (index2 != -1) //扔掉没子弹的武器 { diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs index 816e2c7..bdf95b2 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs @@ -18,11 +18,11 @@ /// AiTailAfter, /// - /// 目标在视野内, 跟进目标 + /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 /// AiFollowUp, /// - /// 距离足够进, 在目标附近随机移动 + /// 距离足够近, 在目标附近随机移动 /// AiSurround, /// diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs index 182dd06..53d0854 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs @@ -1,5 +1,4 @@  -using System.Linq; using Godot; /// @@ -7,11 +6,6 @@ /// public class AiFindAmmoState : StateBase { - /// - /// Ai 对武器的标记名称, 一旦有该标记, Ai AiFindAmmoState 状态下寻找可用武器将忽略该武器 - /// - public const string AiFindWeaponSign = "AiFindWeaponSign"; - private Weapon _target; @@ -19,6 +13,9 @@ private float _navigationUpdateTimer = 0; private float _navigationInterval = 1f; + private bool _isInTailAfterRange = false; + private float _tailAfterTimer = 0; + public AiFindAmmoState() : base(AiStateEnum.AiFindAmmo) { } @@ -26,6 +23,9 @@ public override void Enter(AiStateEnum prev, params object[] args) { _navigationUpdateTimer = 0; + _isInTailAfterRange = false; + _tailAfterTimer = 0; + //找到能用的武器 FindTargetWeapon(); if (_target == null) { @@ -34,7 +34,7 @@ } //标记武器 - _target.SetSign(AiFindWeaponSign, Master); + _target.SetSign(SignNames.AiFindWeaponSign, Master); } public override void PhysicsProcess(float delta) @@ -42,81 +42,101 @@ var activeWeapon = Master.Holster.ActiveWeapon; if (activeWeapon != null && !activeWeapon.IsTotalAmmoEmpty()) //已经有弹药了 { - ChangeStateLate(AiStateEnum.AiNormal); + ChangeStateLate(GetNextState()); return; } - - if (_target != null) + + //更新目标位置 + if (_navigationUpdateTimer <= 0) { - //更新目标位置 - if (_navigationUpdateTimer <= 0) + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + var position = _target.GlobalPosition; + if (Master.NavigationAgent2D.GetTargetLocation() != position) { - //每隔一段时间秒更改目标位置 - _navigationUpdateTimer = _navigationInterval; - var position = _target.GlobalPosition; - if (Master.NavigationAgent2D.GetTargetLocation() != position) - { - Master.NavigationAgent2D.SetTargetLocation(position); - } - } - else - { - _navigationUpdateTimer -= delta; - } - - //枪口指向玩家 - Master.LookTargetPosition(Player.Current.GlobalPosition); - - if (_target.IsQueuedForDeletion() || _target.IsTotalAmmoEmpty()) //已经被销毁, 或者弹药已经被其他角色捡走 - { - //再去寻找其他武器 - FindTargetWeapon(); - - if (_target == null) //也没有其他可用的武器了 - { - ChangeStateLate(AiStateEnum.AiNormal); - } - } - else if (_target.Master == Master) //已经被自己拾起 - { - ChangeStateLate(AiStateEnum.AiNormal); - } - else if (_target.Master != null) //武器已经被其他角色拾起! - { - //再去寻找其他武器 - FindTargetWeapon(); - - if (_target == null) //也没有其他可用的武器了 - { - ChangeStateLate(AiStateEnum.AiNormal); - } - } - else - { - //向武器移动 - if (!Master.NavigationAgent2D.IsNavigationFinished()) - { - //计算移动 - var nextPos = Master.NavigationAgent2D.GetNextLocation(); - Master.AnimatedSprite.Animation = AnimatorNames.Run; - Master.Velocity = - (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * - Master.MoveSpeed; - Master.CalcMove(delta); - } + Master.NavigationAgent2D.SetTargetLocation(position); } } else { + _navigationUpdateTimer -= delta; + } + var playerPos = Player.Current.GetCenterPosition(); + //枪口指向玩家 + Master.LookTargetPosition(playerPos); + + if (_target.IsQueuedForDeletion() || _target.IsTotalAmmoEmpty()) //已经被销毁, 或者弹药已经被其他角色捡走 + { + //再去寻找其他武器 + FindTargetWeapon(); + + if (_target == null) //也没有其他可用的武器了 + { + ChangeStateLate(GetNextState()); + } + } + else if (_target.Master == Master) //已经被自己拾起 + { + ChangeStateLate(GetNextState()); + } + else if (_target.Master != null) //武器已经被其他角色拾起! + { + //再去寻找其他武器 + FindTargetWeapon(); + + if (_target == null) //也没有其他可用的武器了 + { + ChangeStateLate(GetNextState()); + } + } + else + { + //检测目标没有超出跟随视野距离 + _isInTailAfterRange = Master.IsInTailAfterViewRange(playerPos); + if (_isInTailAfterRange) + { + _tailAfterTimer = 0; + } + else + { + _tailAfterTimer += delta; + } + + //向武器移动 + if (!Master.NavigationAgent2D.IsNavigationFinished()) + { + //计算移动 + var nextPos = Master.NavigationAgent2D.GetNextLocation(); + Master.AnimatedSprite.Animation = AnimatorNames.Run; + Master.Velocity = + (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * + Master.MoveSpeed; + Master.CalcMove(delta); + } } } + private AiStateEnum GetNextState() + { + return _tailAfterTimer > 10 ? AiStateEnum.AiNormal : AiStateEnum.AiTailAfter; + } + public override void DebugDraw() { if (_target != null) { Master.DrawLine(Vector2.Zero, Master.ToLocal(_target.GlobalPosition), Colors.Purple); + + if (_tailAfterTimer <= 0) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(Player.Current.GetCenterPosition()), Colors.Orange); + } + else if (_tailAfterTimer <= 10) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(Player.Current.GetCenterPosition()), Colors.Blue); + } + } } @@ -129,16 +149,16 @@ if (!weapon.IsTotalAmmoEmpty()) { //查询是否有其他敌人标记要拾起该武器 - if (weapon.HasSign(AiFindWeaponSign)) + if (weapon.HasSign(SignNames.AiFindWeaponSign)) { - var enemy = weapon.GetSign(AiFindWeaponSign); + var enemy = weapon.GetSign(SignNames.AiFindWeaponSign); if (enemy == Master) //就是自己标记的 { - + } else if (enemy == null || enemy.IsQueuedForDeletion()) //标记当前武器的敌人已经被销毁 { - weapon.RemoveSign(AiFindWeaponSign); + weapon.RemoveSign(SignNames.AiFindWeaponSign); } else //放弃这把武器 { diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiFollowUpState.cs index af61de8..f4a8371 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiFollowUpState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiFollowUpState.cs @@ -2,7 +2,7 @@ using Godot; /// -/// 目标在视野范围内, 跟进目标 +/// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 /// public class AiFollowUpState : StateBase { @@ -16,10 +16,6 @@ private float _navigationUpdateTimer = 0; private float _navigationInterval = 0.3f; - //是否存在下一个移动点 - //private bool _hasNextPosition; - //private Vector2 _nextPosition; - public AiFollowUpState() : base(AiStateEnum.AiFollowUp) { } @@ -32,6 +28,18 @@ public override void PhysicsProcess(float delta) { + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + if (Master.CheckUsableWeaponInUnclaimed()) + { + //切换到寻找武器状态 + ChangeStateLate(AiStateEnum.AiFindAmmo); + return; + } + } + var playerPos = Player.Current.GetCenterPosition(); //更新玩家位置 diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs index 2b3c3e3..96babc1 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiLeaveForState.cs @@ -2,7 +2,7 @@ using Godot; /// -/// 前往目标位置 +/// 收到其他敌人通知, 前往发现目标的位置 /// public class AiLeaveForState : StateBase { @@ -20,14 +20,32 @@ { Master.NavigationAgent2D.SetTargetLocation(Enemy.FindTargetPosition); } + // else if (args.Length > 0 && args[0] is Vector2 targetPos) + // { + // Master.NavigationAgent2D.SetTargetLocation(targetPos); + // } else { ChangeStateLate(prev); + return; + } + + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + if (Master.CheckUsableWeaponInUnclaimed()) + { + //切换到寻找武器状态 + ChangeStateLate(AiStateEnum.AiFindAmmo); + } } } public override void PhysicsProcess(float delta) { + //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 + //更新玩家位置 if (_navigationUpdateTimer <= 0) { diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiSurroundState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiSurroundState.cs index aaa465d..36c6594 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiSurroundState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiSurroundState.cs @@ -2,7 +2,7 @@ using Godot; /// -/// 距离足够进, 在目标附近随机移动 +/// 距离目标足够近, 在目标附近随机移动, 并开火 /// public class AiSurroundState : StateBase { @@ -33,6 +33,18 @@ public override void PhysicsProcess(float delta) { + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + if (Master.CheckUsableWeaponInUnclaimed()) + { + //切换到寻找武器状态 + ChangeStateLate(AiStateEnum.AiFindAmmo); + return; + } + } + var playerPos = Player.Current.GetCenterPosition(); var weapon = Master.Holster.ActiveWeapon; diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs index 32b2a97..7a094e7 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiTailAfterState.cs @@ -27,10 +27,23 @@ _isInViewRange = true; _navigationUpdateTimer = 0; _viewTimer = 0; + + //先检查弹药是否打光 + if (Master.IsAllWeaponTotalAmmoEmpty()) + { + //再寻找是否有可用的武器 + if (Master.CheckUsableWeaponInUnclaimed()) + { + //切换到寻找武器状态 + ChangeStateLate(AiStateEnum.AiFindAmmo); + } + } } public override void PhysicsProcess(float delta) { + //这个状态下不会有攻击事件, 所以没必要每一帧检查是否弹药耗尽 + var playerPos = GameApplication.Instance.Room.Player.GetCenterPosition(); //更新玩家位置 @@ -48,11 +61,12 @@ _navigationUpdateTimer -= delta; } + //枪口指向玩家 + Master.LookTargetPosition(playerPos); if (!Master.NavigationAgent2D.IsNavigationFinished()) { //计算移动 var nextPos = Master.NavigationAgent2D.GetNextLocation(); - Master.LookTargetPosition(playerPos); Master.AnimatedSprite.Animation = AnimatorNames.Run; Master.Velocity = (nextPos - Master.GlobalPosition - Master.NavigationPoint.Position).Normalized() * Master.MoveSpeed; diff --git a/DungeonShooting_Godot/src/game/room/RoomManager.cs b/DungeonShooting_Godot/src/game/room/RoomManager.cs index 9cb6d31..29c8bfa 100644 --- a/DungeonShooting_Godot/src/game/room/RoomManager.cs +++ b/DungeonShooting_Godot/src/game/room/RoomManager.cs @@ -117,7 +117,7 @@ //WeaponManager.GetGun("1003").PutDown(new Vector2(180, 180)); WeaponManager.GetGun("1002").PutDown(new Vector2(180, 120)); - //WeaponManager.GetGun("1004").PutDown(new Vector2(220, 120)); + WeaponManager.GetGun("1004").PutDown(new Vector2(220, 120)); } public override void _Process(float delta)