diff --git a/DungeonShooting_Godot/src/framework/ActivityObject.cs b/DungeonShooting_Godot/src/framework/ActivityObject.cs index 530d06a..a527242 100644 --- a/DungeonShooting_Godot/src/framework/ActivityObject.cs +++ b/DungeonShooting_Godot/src/framework/ActivityObject.cs @@ -13,17 +13,17 @@ /// 是否是调试模式 /// public static bool IsDebug { get; set; } - + /// /// 当前物体类型id, 用于区分是否是同一种物体, 如果不是通过 ActivityObject.Create() 函数创建出来的对象那么 ItemId 为 null /// public string ItemId { get; private set; } - + /// /// 是否放入 ySort 节点下 /// public bool UseYSort { get; } - + /// /// 当前物体显示的精灵图像, 节点名称必须叫 "AnimatedSprite", 类型为 AnimatedSprite /// @@ -44,7 +44,7 @@ /// /// public AnimationPlayer AnimationPlayer { get; } - + /// /// 是否调用过 Destroy() 函数 /// @@ -69,6 +69,9 @@ //存储投抛该物体时所产生的数据 private ObjectThrowData _throwData; + //标记字典 + private Dictionary _signMap; + public ActivityObject(string scenePath) { //加载预制体 @@ -130,9 +133,10 @@ //切换阴影动画 ShadowSprite.Texture = AnimatedSprite.Frames.GetFrame(anim, frame); } + _prevAnimation = anim; _prevAnimationFrame = frame; - + CalcShadow(); ShadowSprite.Visible = true; } @@ -507,20 +511,21 @@ //切换阴影动画 ShadowSprite.Texture = AnimatedSprite.Frames.GetFrame(anim, AnimatedSprite.Frame); } + _prevAnimation = anim; _prevAnimationFrame = frame; - + //计算阴影 CalcShadow(); } - + //调试绘制 if (IsDebug) { Update(); } } - + public override void _PhysicsProcess(float delta) { //更新组件 @@ -584,7 +589,7 @@ } } - + /// /// 销毁物体 /// @@ -596,7 +601,7 @@ } IsDestroyed = true; - + OnDestroy(); QueueFree(); var arr = _components.ToArray(); @@ -729,6 +734,72 @@ } /// + /// 设置标记, 用于在物体上记录自定义数据 + /// + /// 标记名称 + /// 存入值 + public void SetSign(string name, object v) + { + if (_signMap == null) + { + _signMap = new Dictionary(); + } + + _signMap[name] = v; + } + + /// + /// 返回是否存在指定名称的标记数据 + /// + public bool HasSign(string name) + { + return _signMap == null ? false : _signMap.ContainsKey(name); + } + + /// + /// 根据名称获取标记值 + /// + public object GetSign(string name) + { + if (_signMap == null) + { + return null; + } + + _signMap.TryGetValue(name, out var value); + return value; + } + + /// + /// 根据名称获取标记值 + /// + public T GetSign(string name) + { + if (_signMap == null) + { + return default; + } + + _signMap.TryGetValue(name, out var value); + if (value is T v) + { + return v; + } + return default; + } + + /// + /// 根据名称删除标记 + /// + public void RemoveSign(string name) + { + if (_signMap != null) + { + _signMap.Remove(name); + } + } + + /// /// 通过 ItemId 实例化 ActivityObject 对象 /// public static T Create(string itemId) where T : ActivityObject 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 606b519..8cdac81 100644 --- a/DungeonShooting_Godot/src/game/item/package/Holster.cs +++ b/DungeonShooting_Godot/src/game/item/package/Holster.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using Godot; /// @@ -28,7 +30,9 @@ /// 归属者 /// public Role Master { get; } - + + //public Weapon HandheldWeapon { get; private set; } + /// /// 当前使用的武器对象 /// @@ -39,6 +43,9 @@ /// public int ActiveIndex { get; private set; } = 0; + /// + /// 武器插槽 + /// public WeaponSlot[] SlotList { get; } = new WeaponSlot[4]; public Holster(Role master) @@ -64,6 +71,23 @@ } /// + /// 返回当前武器袋是否还有空位 + /// + public bool HasVacancy() + { + for (int i = 0; i < SlotList.Length; i++) + { + var item = SlotList[i]; + if (item.Enable && item.Weapon == null) + { + return true; + } + } + + return false; + } + + /// /// 根据索引获取武器 /// public Weapon GetWeapon(int index) { @@ -83,7 +107,7 @@ for (int i = 0; i < SlotList.Length; i++) { var item = SlotList[i]; - if (item.Weapon != null && item.Weapon.Id == id) + if (item.Weapon != null && item.Weapon.TypeId == id) { return i; } @@ -92,6 +116,59 @@ } /// + /// 通过回调函数查询武器在武器袋中的位置, 如果没有, 则返回 -1 + /// + public int FindWeapon(Func handler) + { + for (int i = 0; i < SlotList.Length; i++) + { + var item = SlotList[i]; + if (item.Weapon != null && handler(item.Weapon, i)) + { + return i; + } + } + return -1; + } + + /// + /// 遍历所有武器 + /// + 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(); + } + + /// /// 返回是否能放入武器 /// /// 武器对象 @@ -112,7 +189,8 @@ /// 拾起武器, 存入武器袋中, 返回存放在武器袋的位置, 如果容不下这把武器, 则会返回 -1 /// /// 武器对象 - public int PickupWeapon(Weapon weapon) + /// 是否立即切换到该武器, 默认 true + public int PickupWeapon(Weapon weapon, bool exchange = true) { //已经被拾起了 if (weapon.Master != null) @@ -127,7 +205,11 @@ weapon.Pickup(); item.Weapon = weapon; weapon.PickUpWeapon(Master); - ExchangeByIndex(i); + if (exchange) + { + ExchangeByIndex(i); + } + return i; } } diff --git a/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs b/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs index f165656..6c5ca8e 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs @@ -1,5 +1,6 @@ using Godot; using System; +using System.Collections.Generic; /// /// 武器的基类 @@ -7,9 +8,14 @@ public abstract class Weapon : ActivityObject { /// - /// 武器的唯一id + /// 所有被扔在地上的武器 /// - public string Id { get; } + public static readonly HashSet UnclaimedWeapons = new HashSet(); + + /// + /// 武器的类型 id + /// + public string TypeId { get; } /// /// 开火回调事件 @@ -158,15 +164,15 @@ //当前后坐力导致的偏移长度 private float _currBacklashLength = 0; - + /// /// 根据属性创建一把武器 /// - /// 武器唯一id + /// 武器的类型id /// 属性 - public Weapon(string id, WeaponAttribute attribute) : base(attribute.WeaponPrefab) + public Weapon(string typeId, WeaponAttribute attribute) : base(attribute.WeaponPrefab) { - Id = id; + TypeId = typeId; Attribute = attribute; FirePoint = GetNode("WeaponBody/FirePoint"); @@ -186,7 +192,7 @@ if (Attribute.AmmoCapacity > Attribute.MaxAmmoCapacity) { Attribute.AmmoCapacity = Attribute.MaxAmmoCapacity; - GD.PrintErr("弹夹的容量不能超过弹药上限, 武器id: " + id); + GD.PrintErr("弹夹的容量不能超过弹药上限, 武器id: " + typeId); } //弹药量 CurrAmmo = Attribute.AmmoCapacity; @@ -279,23 +285,45 @@ return 1; } + public override void _EnterTree() + { + base._EnterTree(); + + //收集落在地上的武器 + if (Master == null && GetParent() == GameApplication.Instance.Room.GetRoot(false)) + { + UnclaimedWeapons.Add(this); + } + } + + public override void _ExitTree() + { + base._ExitTree(); + + UnclaimedWeapons.Remove(this); + } + public override void _Process(float delta) { base._Process(delta); //这把武器被扔在地上, 或者当前武器没有被使用 if (Master == null || Master.Holster.ActiveWeapon != this) { + //_triggerTimer _triggerTimer = _triggerTimer > 0 ? _triggerTimer - delta : 0; + //攻击冷却计时 _attackTimer = _attackTimer > 0 ? _attackTimer - delta : 0; + //武器的当前散射半径 CurrScatteringRange = Mathf.Max(CurrScatteringRange - Attribute.ScatteringRangeBackSpeed * delta, Attribute.StartScatteringRange); //松开扳机 if (_triggerFlag || _downTimer > 0) { UpTrigger(); - _triggerFlag = false; _downTimer = 0; } + + _triggerFlag = false; //重置数据 if (_dirtyFlag) @@ -406,6 +434,9 @@ /// public void Trigger() { + //这一帧已经按过了, 不需要再按下 + if (_triggerFlag) return; + //是否第一帧按下 var justDown = _downTimer == 0; //是否能发射 @@ -454,11 +485,14 @@ fireFlag = false; } } - else if (CurrAmmo <= 0) + else if (CurrAmmo <= 0) //子弹不够 { fireFlag = false; - //子弹不够 - Reload(); + if (justDown) + { + //第一帧按下, 触发换弹 + Reload(); + } } if (fireFlag) @@ -631,15 +665,23 @@ /// /// 返回弹药是否到达上限 /// - public bool IsFullAmmo() + public bool IsAmmoFull() { return CurrAmmo + ResidueAmmo >= Attribute.MaxAmmoCapacity; } /// + /// 返回弹夹是否打空 + /// + public bool IsAmmoEmpty() + { + return CurrAmmo == 0; + } + + /// /// 返回是否弹药耗尽 /// - public bool IsEmptyAmmo() + public bool IsTotalAmmoEmpty() { return CurrAmmo + ResidueAmmo == 0; } @@ -745,18 +787,18 @@ { var masterWeapon = roleMaster.Holster.ActiveWeapon; //查找是否有同类型武器 - var index = roleMaster.Holster.FindWeapon(Id); + var index = roleMaster.Holster.FindWeapon(TypeId); if (index != -1) //如果有这个武器 { if (CurrAmmo + ResidueAmmo != 0) //子弹不为空 { var targetWeapon = roleMaster.Holster.GetWeapon(index); - if (!targetWeapon.IsFullAmmo()) //背包里面的武器子弹未满 + if (!targetWeapon.IsAmmoFull()) //背包里面的武器子弹未满 { //可以互动拾起弹药 result.CanInteractive = true; result.Message = Attribute.Name; - result.ShowIcon = "res://resource/sprite/ui/icon/icon_bullet.png"; + result.ShowIcon = ResourcePath.resource_sprite_ui_icon_icon_bullet_png; return result; } } @@ -768,7 +810,7 @@ //可以互动, 拾起武器 result.CanInteractive = true; result.Message = Attribute.Name; - result.ShowIcon = "res://resource/sprite/ui/icon/icon_pickup.png"; + result.ShowIcon = ResourcePath.resource_sprite_ui_icon_icon_pickup_png; return result; } else if (masterWeapon != null && masterWeapon.Attribute.WeightType == Attribute.WeightType) //替换武器 @@ -776,7 +818,7 @@ //可以互动, 切换武器 result.CanInteractive = true; result.Message = Attribute.Name; - result.ShowIcon = "res://resource/sprite/ui/icon/icon_replace.png"; + result.ShowIcon = ResourcePath.resource_sprite_ui_icon_icon_replace_png; return result; } } @@ -792,7 +834,7 @@ { var holster = roleMaster.Holster; //查找是否有同类型武器 - var index = holster.FindWeapon(Id); + var index = holster.FindWeapon(TypeId); if (index != -1) //如果有这个武器 { if (CurrAmmo + ResidueAmmo == 0) //没有子弹了 @@ -909,12 +951,14 @@ ZIndex = 0; //禁用碰撞 CollisionShape2D.Disabled = true; + //清除 Ai 拾起标记 + RemoveSign(SignNames.AiFindWeaponSign); OnPickUp(master); } /// - /// 触发移除 - /// a + /// 触发移除, 这个函数由 Holster 对象调用 + /// public void Remove() { Master = null; @@ -943,4 +987,14 @@ HideShadowSprite(); OnConceal(); } + + //-------------------------------- Ai相关 ----------------------------- + + /// + /// 获取 Ai 对于该武器的评分, 评分越高, 代表 Ai 会越优先选择该武器, 如果为 -1, 则表示 Ai 不会使用该武器 + /// + public float GetAiScore() + { + return 1; + } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/item/weapon/WeaponAttribute.cs b/DungeonShooting_Godot/src/game/item/weapon/WeaponAttribute.cs index 4a6c8aa..39551b1 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/WeaponAttribute.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/WeaponAttribute.cs @@ -165,4 +165,9 @@ /// 开火后武器口角度恢复速度倍数 /// public float UpliftAngleRestore = 1f; + + //------------------------------ Ai相关 ----------------------------- + + //public bool Ai + } \ No newline at end of file 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/gun/Gun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs index eddf18b..abee1bf 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Gun.cs @@ -24,8 +24,8 @@ //连发 ContinuousShoot = true; AmmoCapacity = 30; - StandbyAmmoCapacity = 30 * 70; - MaxAmmoCapacity = 30 * 70; + StandbyAmmoCapacity = 30 * 3; + MaxAmmoCapacity = 30 * 3; //扳机检测间隔 TriggerInterval = 0f; //连发数量 @@ -86,7 +86,7 @@ } } - public Gun(string id, WeaponAttribute attribute): base(id, attribute) + public Gun(string typeId, WeaponAttribute attribute): base(typeId, attribute) { } diff --git a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs index 3e9d8e3..46713db 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/gun/Shotgun.cs @@ -34,8 +34,8 @@ MinDistance = 200; MaxDistance = 250; //发射子弹数量 - MinFireBulletCount = 1; - MaxFireBulletCount = 1; + MinFireBulletCount = 5; + MaxFireBulletCount = 5; //抬起角度 UpliftAngle = 15; MaxBacklash = 6; @@ -50,7 +50,7 @@ /// public PackedScene ShellPack; - public Shotgun(string id, WeaponAttribute attribute) : base(id, attribute) + public Shotgun(string typeId, WeaponAttribute attribute) : base(typeId, attribute) { ShellPack = ResourceManager.Load(ResourcePath.prefab_weapon_shell_ShellCase_tscn); } @@ -86,20 +86,15 @@ protected override void OnShoot(float fireRotation) { - for (int i = 0; i < 5; i++) - { - //创建子弹 - //CreateBullet(BulletPack, FirePoint.GlobalPosition, fireRotation + MathUtils.RandRange(-20 / 180f * Mathf.Pi, 20 / 180f * Mathf.Pi)); - - var bullet = new Bullet( - ResourcePath.prefab_weapon_bullet_Bullet_tscn, - Utils.RandRangeInt(280, 380), - Utils.RandRange(Attribute.MinDistance, Attribute.MaxDistance), - FirePoint.GlobalPosition, - fireRotation + Utils.RandRange(-20 / 180f * Mathf.Pi, 20 / 180f * Mathf.Pi), - GetAttackLayer() - ); - bullet.PutDown(); - } + //创建子弹 + var bullet = new Bullet( + ResourcePath.prefab_weapon_bullet_Bullet_tscn, + Utils.RandRangeInt(280, 380), + Utils.RandRange(Attribute.MinDistance, Attribute.MaxDistance), + FirePoint.GlobalPosition, + fireRotation + Utils.RandRange(-20 / 180f * Mathf.Pi, 20 / 180f * Mathf.Pi), + GetAttackLayer() + ); + bullet.PutDown(); } } diff --git a/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs b/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs index 60a3427..b90ac06 100644 --- a/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs +++ b/DungeonShooting_Godot/src/game/item/weapon/knife/Knife.cs @@ -22,6 +22,7 @@ MaxAmmoCapacity = AmmoCapacity; //握把位置 HoldPosition = new Vector2(10, 0); + MaxDistance = MinDistance = 35; //后坐力改为向前, 模拟手伸长的效果 MaxBacklash = -8; MinBacklash = -8; @@ -34,7 +35,7 @@ private int _attackIndex = 0; - public Knife(string id, WeaponAttribute attribute) : base(id, attribute) + public Knife(string typeId, WeaponAttribute attribute) : base(typeId, attribute) { _hitArea = GetNode("HitArea"); _hitArea.Monitoring = false; @@ -117,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 80a19ee..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; /// /// 当前护盾值 @@ -157,7 +162,7 @@ /// /// 可以互动的道具 /// - protected ActivityObject InteractiveItem { get; private set; } + public ActivityObject InteractiveItem { get; private set; } /// /// 当血量改变时调用 @@ -257,7 +262,7 @@ public override void _Process(float delta) { base._Process(delta); - + //看向目标 if (LookTarget != null) { @@ -368,12 +373,29 @@ } /// - /// 拾起一个武器, 并且切换到这个武器, 返回是否成功拾取 + /// 返回所有武器是否弹药都打光了 + /// + public bool IsAllWeaponTotalAmmoEmpty() + { + foreach (var weaponSlot in Holster.SlotList) + { + if (weaponSlot.Weapon != null && !weaponSlot.Weapon.IsTotalAmmoEmpty()) + { + return false; + } + } + + return true; + } + + /// + /// 拾起一个武器, 返回是否成功拾取, 如果不想立刻切换到该武器, exchange 请传 false /// /// 武器对象 - public virtual bool PickUpWeapon(Weapon weapon) + /// 是否立即切换到该武器, 默认 true + public virtual bool PickUpWeapon(Weapon weapon, bool exchange = true) { - if (Holster.PickupWeapon(weapon) != -1) + if (Holster.PickupWeapon(weapon, exchange) != -1) { //从可互动队列中移除 _interactiveItemList.Remove(weapon); @@ -404,7 +426,16 @@ /// public virtual void ThrowWeapon() { - var weapon = Holster.RemoveWeapon(Holster.ActiveIndex); + ThrowWeapon(Holster.ActiveIndex); + } + + /// + /// 扔掉指定位置的武器 + /// + /// 武器在武器袋中的位置 + public virtual void ThrowWeapon(int index) + { + var weapon = Holster.RemoveWeapon(index); //播放抛出效果 if (weapon != null) { @@ -431,6 +462,7 @@ item.Interactive(this); return item; } + return null; } @@ -457,7 +489,7 @@ } /// - /// 受到伤害 + /// 受到伤害, 如果是在碰撞信号处理函数中调用该函数, 请使用 CallDeferred 来延时调用, 否则很有可能导致报错 /// /// 伤害的量 public virtual void Hurt(int damage) @@ -474,6 +506,17 @@ AnimationPlayer.Stop(); AnimationPlayer.Play("hit"); + + //死亡判定 + if (Hp <= 0) + { + //死亡 + if (!IsDie) + { + IsDie = true; + OnDie(); + } + } } /// diff --git a/DungeonShooting_Godot/src/game/role/StateController.cs b/DungeonShooting_Godot/src/game/role/StateController.cs index f349561..969dcdf 100644 --- a/DungeonShooting_Godot/src/game/role/StateController.cs +++ b/DungeonShooting_Godot/src/game/role/StateController.cs @@ -66,6 +66,14 @@ } /// + /// 返回该状态机控制器中是否存在指定的状态实例 + /// + public bool HasState(S state) + { + return _states.ContainsKey(state); + } + + /// /// 获取指定状态对应的实例对象 /// public StateBase GetState(S state) @@ -130,7 +138,6 @@ _isChangeState = !late; var prev = CurrStateBase.State; CurrStateBase.Exit(next); - GD.Print("nextState => " + next); CurrStateBase = newState; CurrStateBase.Enter(prev, arg); } diff --git a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs index e27189d..e2e3143 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs @@ -19,12 +19,12 @@ /// public class Enemy : Role { - + /// /// 公共属性, 是否找到目标, 如果找到目标, 则所有敌人都会知道玩家的位置 /// public static bool IsFindTarget { get; private set; } - + /// /// 找到的目标的位置, 如果目标在视野内, 则一直更新 /// @@ -56,7 +56,7 @@ /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 /// public RayCast2D ViewRay { get; } - + /// /// 导航代理 /// @@ -68,7 +68,7 @@ public Position2D NavigationPoint { get; } private float _enemyAttackTimer = 0; - + public Enemy() : base(ResourcePath.prefab_role_Enemy_tscn) { StateController = new StateController(); @@ -81,6 +81,9 @@ Holster.SlotList[2].Enable = true; Holster.SlotList[3].Enable = true; + + MaxHp = 20; + Hp = 20; //视野射线 ViewRay = GetNode("ViewRay"); @@ -96,14 +99,12 @@ StateController.Register(new AiFollowUpState()); StateController.Register(new AiLeaveForState()); StateController.Register(new AiSurroundState()); + StateController.Register(new AiFindAmmoState()); } public override void _Ready() { base._Ready(); - //防撞速度计算 - NavigationAgent2D.Connect("velocity_computed", this, nameof(OnVelocityComputed)); - //默认状态 StateController.ChangeState(AiStateEnum.AiNormal); @@ -120,14 +121,55 @@ public override void _ExitTree() { + base._ExitTree(); _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); _enemyAttackTimer -= delta; + + 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; } /// @@ -139,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(); @@ -155,17 +198,41 @@ var weapon = Holster.ActiveWeapon; if (weapon != null) { - if (weapon.Attribute.ContinuousShoot) //连发 + if (weapon.IsTotalAmmoEmpty()) //当前武器弹药打空 { - Attack(); - } - else //单发 - { - if (_enemyAttackTimer <= 0) + //切换到有子弹的武器 + var index = Holster.FindWeapon((we, i) => !we.IsTotalAmmoEmpty()); + if (index != -1) { - _enemyAttackTimer = 60f / weapon.Attribute.StartFiringSpeed; + Holster.ExchangeByIndex(index); + } + else //所有子弹打光 + { + + } + } + else if (weapon.Reloading) //换弹中 + { + + } + else if (weapon.IsAmmoEmpty()) //弹夹已经打空 + { + Reload(); + } + else //正常射击 + { + if (weapon.Attribute.ContinuousShoot) //连发 + { Attack(); } + else //单发 + { + if (_enemyAttackTimer <= 0) + { + _enemyAttackTimer = 60f / weapon.Attribute.StartFiringSpeed + Utils.RandRange(0, 0.06f); + Attack(); + } + } } } } @@ -238,8 +305,59 @@ ViewRay.Enabled = false; } - private void OnVelocityComputed(Vector2 velocity) + /// + /// AI 拾起武器操作 + /// + private void EnemyPickUpWeapon() { - GD.Print("velocity: " + velocity); + //这几个状态不需要主动拾起武器操作 + var state = StateController.CurrState; + if (state == AiStateEnum.AiNormal) + { + return; + } + + //拾起地上的武器 + if (InteractiveItem is Weapon weapon) + { + if (Holster.ActiveWeapon == null) //手上没有武器, 无论如何也要拾起 + { + TriggerInteractive(); + return; + } + + //没弹药了 + if (weapon.IsTotalAmmoEmpty()) + { + return; + } + + var index = Holster.FindWeapon((we, i) => we.TypeId == weapon.TypeId); + if (index != -1) //与武器袋中武器类型相同, 补充子弹 + { + if (!Holster.GetWeapon(index).IsAmmoFull()) + { + TriggerInteractive(); + } + + return; + } + + var index2 = Holster.FindWeapon((we, i) => + we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty()); + if (index2 != -1) //扔掉没子弹的武器 + { + ThrowWeapon(index2); + TriggerInteractive(); + return; + } + + if (Holster.HasVacancy()) //有空位, 拾起武器 + { + TriggerInteractive(); + return; + } + } } + } diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs index e94b6fb..bdf95b2 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AIStateEnum.cs @@ -2,7 +2,7 @@ public enum AiStateEnum { /// - /// ai状态, 正常, 未发现目标 + /// Ai 状态, 正常, 未发现目标 /// AiNormal, /// @@ -18,11 +18,15 @@ /// AiTailAfter, /// - /// 目标在视野内, 跟进目标 + /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 /// AiFollowUp, /// - /// 距离足够进, 在目标附近随机移动 + /// 距离足够近, 在目标附近随机移动 /// AiSurround, + /// + /// Ai 寻找弹药 + /// + AiFindAmmo, } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs new file mode 100644 index 0000000..53d0854 --- /dev/null +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs @@ -0,0 +1,187 @@ + +using Godot; + +/// +/// Ai 寻找弹药 +/// +public class AiFindAmmoState : StateBase +{ + + private Weapon _target; + + //导航目标点刷新计时器 + private float _navigationUpdateTimer = 0; + private float _navigationInterval = 1f; + + private bool _isInTailAfterRange = false; + private float _tailAfterTimer = 0; + + public AiFindAmmoState() : base(AiStateEnum.AiFindAmmo) + { + } + + public override void Enter(AiStateEnum prev, params object[] args) + { + _navigationUpdateTimer = 0; + _isInTailAfterRange = false; + _tailAfterTimer = 0; + //找到能用的武器 + FindTargetWeapon(); + if (_target == null) + { + ChangeStateLate(prev); + return; + } + + //标记武器 + _target.SetSign(SignNames.AiFindWeaponSign, Master); + } + + public override void PhysicsProcess(float delta) + { + var activeWeapon = Master.Holster.ActiveWeapon; + if (activeWeapon != null && !activeWeapon.IsTotalAmmoEmpty()) //已经有弹药了 + { + ChangeStateLate(GetNextState()); + return; + } + + //更新目标位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _navigationUpdateTimer = _navigationInterval; + var position = _target.GlobalPosition; + if (Master.NavigationAgent2D.GetTargetLocation() != position) + { + 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); + } + + } + } + + private void FindTargetWeapon() + { + _target = null; + var position = Master.Position; + foreach (var weapon in Weapon.UnclaimedWeapons) + { + if (!weapon.IsTotalAmmoEmpty()) + { + //查询是否有其他敌人标记要拾起该武器 + if (weapon.HasSign(SignNames.AiFindWeaponSign)) + { + var enemy = weapon.GetSign(SignNames.AiFindWeaponSign); + if (enemy == Master) //就是自己标记的 + { + + } + else if (enemy == null || enemy.IsQueuedForDeletion()) //标记当前武器的敌人已经被销毁 + { + weapon.RemoveSign(SignNames.AiFindWeaponSign); + } + else //放弃这把武器 + { + continue; + } + } + + if (_target == null) //第一把武器 + { + _target = weapon; + } + else if (_target.Position.DistanceSquaredTo(position) > + weapon.Position.DistanceSquaredTo(position)) //距离更近 + { + _target = weapon; + } + } + } + + //设置目标点 + if (_target != null) + { + Master.NavigationAgent2D.SetTargetLocation(_target.GlobalPosition); + } + } +} \ No newline at end of file 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 17d5396..29c8bfa 100644 --- a/DungeonShooting_Godot/src/game/room/RoomManager.cs +++ b/DungeonShooting_Godot/src/game/room/RoomManager.cs @@ -83,7 +83,7 @@ var enemy1 = new Enemy(); enemy1.Name = "Enemy"; enemy1.PutDown(new Vector2(150, 300)); - enemy1.PickUpWeapon(WeaponManager.GetGun("1003")); + //enemy1.PickUpWeapon(WeaponManager.GetGun("1003")); enemy1.PickUpWeapon(WeaponManager.GetGun("1001")); // for (int i = 0; i < 10; i++) @@ -95,26 +95,26 @@ // enemyTemp.PickUpWeapon(WeaponManager.GetGun("1001")); // } - var enemy2 = new Enemy(); - enemy2.Name = "Enemy2"; - enemy2.PutDown(new Vector2(540, 100)); - 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")); + // var enemy2 = new Enemy(); + // enemy2.Name = "Enemy2"; + // enemy2.PutDown(new Vector2(540, 100)); + // 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, 100)); WeaponManager.GetGun("1001").PutDown(new Vector2(80, 80)); - WeaponManager.GetGun("1002").PutDown(new Vector2(80, 120)); - WeaponManager.GetGun("1003").PutDown(new Vector2(120, 80)); + // WeaponManager.GetGun("1002").PutDown(new Vector2(80, 120)); + // WeaponManager.GetGun("1003").PutDown(new Vector2(120, 80)); WeaponManager.GetGun("1003").PutDown(new Vector2(180, 80)); - WeaponManager.GetGun("1003").PutDown(new Vector2(180, 180)); + //WeaponManager.GetGun("1003").PutDown(new Vector2(180, 180)); WeaponManager.GetGun("1002").PutDown(new Vector2(180, 120)); WeaponManager.GetGun("1004").PutDown(new Vector2(220, 120));