diff --git a/DungeonShooting_Godot/scene/Main.tscn b/DungeonShooting_Godot/scene/Main.tscn index aff7b6d..35a4274 100644 --- a/DungeonShooting_Godot/scene/Main.tscn +++ b/DungeonShooting_Godot/scene/Main.tscn @@ -24,6 +24,7 @@ [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/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/item/weapon/Weapon.cs b/DungeonShooting_Godot/src/game/item/weapon/Weapon.cs index 297cfd3..a870c8b 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,6 +8,11 @@ public abstract class Weapon : ActivityObject { /// + /// 所有被扔在地上的武器 + /// + public static readonly HashSet UnclaimedWeapons = new HashSet(); + + /// /// 武器的类型 id /// public string TypeId { get; } @@ -279,6 +285,24 @@ 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); @@ -927,12 +951,14 @@ ZIndex = 0; //禁用碰撞 CollisionShape2D.Disabled = true; + //清除 Ai 拾起标记 + RemoveSign(AiFindAmmoState.AiFindWeaponSign); OnPickUp(master); } /// /// 触发移除, 这个函数由 Holster 对象调用 - /// a + /// public void Remove() { Master = null; @@ -961,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/role/StateController.cs b/DungeonShooting_Godot/src/game/role/StateController.cs index ce64474..969dcdf 100644 --- a/DungeonShooting_Godot/src/game/role/StateController.cs +++ b/DungeonShooting_Godot/src/game/role/StateController.cs @@ -138,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 773a1f9..74bd59d 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/Enemy.cs @@ -166,7 +166,14 @@ } else //所有子弹打光, 去寻找武器 { - //StateController.ChangeStateLate(AiStateEnum.AiFindAmmo); + //如果存在有子弹的武器 + foreach (var unclaimedWeapon in Weapon.UnclaimedWeapons) + { + if (!unclaimedWeapon.IsTotalAmmoEmpty()) + { + StateController.ChangeStateLate(AiStateEnum.AiFindAmmo); + } + } } } else if (weapon.Reloading) //换弹中 @@ -268,6 +275,13 @@ /// private void EnemyPickUpWeapon() { + //这几个状态不需要主动拾起武器操作 + var state = StateController.CurrState; + if (state == AiStateEnum.AiNormal) + { + return; + } + //拾起地上的武器 if (InteractiveItem is Weapon weapon) { @@ -296,7 +310,7 @@ var index2 = Holster.FindWeapon(we => we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty()); - if (index2 != -1) //则扔掉没子弹的武器 + if (index2 != -1) //扔掉没子弹的武器 { ThrowWeapon(index2); TriggerInteractive(); diff --git a/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs index 5ef4304..182dd06 100644 --- a/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs +++ b/DungeonShooting_Godot/src/game/role/enemy/state/AiFindAmmoState.cs @@ -1,4 +1,5 @@  +using System.Linq; using Godot; /// @@ -6,22 +7,161 @@ /// public class AiFindAmmoState : StateBase { + /// + /// Ai 对武器的标记名称, 一旦有该标记, Ai AiFindAmmoState 状态下寻找可用武器将忽略该武器 + /// + public const string AiFindWeaponSign = "AiFindWeaponSign"; + + + private Weapon _target; + + //导航目标点刷新计时器 + private float _navigationUpdateTimer = 0; + private float _navigationInterval = 1f; + public AiFindAmmoState() : base(AiStateEnum.AiFindAmmo) { } public override void Enter(AiStateEnum prev, params object[] args) { - GD.Print("寻找武器"); + _navigationUpdateTimer = 0; + FindTargetWeapon(); + if (_target == null) + { + ChangeStateLate(prev); + return; + } + + //标记武器 + _target.SetSign(AiFindWeaponSign, Master); } public override void PhysicsProcess(float delta) { + var activeWeapon = Master.Holster.ActiveWeapon; + if (activeWeapon != null && !activeWeapon.IsTotalAmmoEmpty()) //已经有弹药了 + { + ChangeStateLate(AiStateEnum.AiNormal); + return; + } + if (_target != null) + { + //更新目标位置 + if (_navigationUpdateTimer <= 0) + { + //每隔一段时间秒更改目标位置 + _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); + } + } + } + else + { + + } } public override void DebugDraw() { - + if (_target != null) + { + Master.DrawLine(Vector2.Zero, Master.ToLocal(_target.GlobalPosition), Colors.Purple); + } + } + + private void FindTargetWeapon() + { + _target = null; + var position = Master.Position; + foreach (var weapon in Weapon.UnclaimedWeapons) + { + if (!weapon.IsTotalAmmoEmpty()) + { + //查询是否有其他敌人标记要拾起该武器 + if (weapon.HasSign(AiFindWeaponSign)) + { + var enemy = weapon.GetSign(AiFindWeaponSign); + if (enemy == Master) //就是自己标记的 + { + + } + else if (enemy == null || enemy.IsQueuedForDeletion()) //标记当前武器的敌人已经被销毁 + { + weapon.RemoveSign(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/room/RoomManager.cs b/DungeonShooting_Godot/src/game/room/RoomManager.cs index c08cbcd..9cb6d31 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++) @@ -109,15 +109,15 @@ // enemy3.PickUpWeapon(WeaponManager.GetGun("1002")); // WeaponManager.GetGun("1001").PutDown(new Vector2(80, 100)); - // WeaponManager.GetGun("1001").PutDown(new Vector2(80, 80)); + 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("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)); + //WeaponManager.GetGun("1004").PutDown(new Vector2(220, 120)); } public override void _Process(float delta)