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));