From dd8583f5bf109e878027b04a2c0157143c4df2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=9D=8Exl?= <1911159016@qq.com> Date: Sun, 24 Mar 2024 01:53:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=BD=E5=87=BAAiRole=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/game/activity/role/ai/AiRole.cs | 381 ++++++++++++++++++ .../{enemy => ai}/state/AiAstonishedState.cs | 4 +- .../role/{enemy => ai}/state/AiAttackState.cs | 6 +- .../{enemy => ai}/state/AiFindAmmoState.cs | 4 +- .../{enemy => ai}/state/AiFollowUpState.cs | 8 +- .../{enemy => ai}/state/AiLeaveForState.cs | 4 +- .../role/{enemy => ai}/state/AiNormalState.cs | 4 +- .../role/{enemy => ai}/state/AiNotifyState.cs | 4 +- .../{enemy => ai}/state/AiSurroundState.cs | 6 +- .../{enemy => ai}/state/AiTailAfterState.cs | 4 +- .../src/game/activity/role/enemy/Enemy.cs | 374 +---------------- .../activity/role/enemy/EnemyRoleState.cs | 23 -- .../game/activity/role/enemy/NoWeaponEnemy.cs | 2 +- .../game/activity/{ => role}/shop/ShopBoss.cs | 2 +- 14 files changed, 415 insertions(+), 411 deletions(-) create mode 100644 DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiAstonishedState.cs (93%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiAttackState.cs (98%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiFindAmmoState.cs (98%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiFollowUpState.cs (95%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiLeaveForState.cs (97%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiNormalState.cs (98%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiNotifyState.cs (90%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiSurroundState.cs (97%) rename DungeonShooting_Godot/src/game/activity/role/{enemy => ai}/state/AiTailAfterState.cs (97%) delete mode 100644 DungeonShooting_Godot/src/game/activity/role/enemy/EnemyRoleState.cs rename DungeonShooting_Godot/src/game/activity/{ => role}/shop/ShopBoss.cs (83%) diff --git a/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs b/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs new file mode 100644 index 00000000..bbbd45fe --- /dev/null +++ b/DungeonShooting_Godot/src/game/activity/role/ai/AiRole.cs @@ -0,0 +1,381 @@ + +using AiState; +using Godot; + +/// +/// Ai角色 +/// +public abstract partial class AiRole : Role +{ + /// + /// 目标是否在视野内 + /// + public bool TargetInView { get; set; } = true; + + /// + /// 敌人身上的状态机控制器 + /// + public StateController StateController { get; private set; } + + /// + /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 + /// + [Export, ExportFillNode] + public RayCast2D ViewRay { get; set; } + + /// + /// 导航代理 + /// + [Export, ExportFillNode] + public NavigationAgent2D NavigationAgent2D { get; set; } + + /// + /// 导航代理中点 + /// + [Export, ExportFillNode] + public Marker2D NavigationPoint { get; set; } + + /// + /// 不通过武发射子弹的开火点 + /// + [Export, ExportFillNode] + public Marker2D FirePoint { get; set; } + + /// + /// 当前敌人所看向的对象, 也就是枪口指向的对象 + /// + public ActivityObject LookTarget { get; set; } + + /// + /// 攻击锁定目标时间 + /// + public float LockingTime { get; set; } = 1f; + + /// + /// 锁定目标已经走过的时间 + /// + public float LockTargetTime { get; set; } = 0; + + /// + /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙 + /// + public float ViewRange { get; set; } = 250; + + /// + /// 发现玩家后跟随玩家的视野半径 + /// + public float TailAfterViewRange { get; set; } = 400; + + /// + /// 背后的视野半径, 单位像素 + /// + public float BackViewRange { get; set; } = 50; + + /// + /// 攻击间隔时间, 秒 + /// + public float AttackInterval { get; set; } = 0; + + public override void OnInit() + { + base.OnInit(); + IsAi = true; + + StateController = AddComponent>(); + + //注册Ai状态机 + StateController.Register(new AiNormalState()); + StateController.Register(new AiTailAfterState()); + StateController.Register(new AiFollowUpState()); + StateController.Register(new AiLeaveForState()); + StateController.Register(new AiSurroundState()); + StateController.Register(new AiFindAmmoState()); + StateController.Register(new AiAttackState()); + StateController.Register(new AiAstonishedState()); + StateController.Register(new AiNotifyState()); + + //默认状态 + StateController.ChangeStateInstant(AIStateEnum.AiNormal); + + //NavigationAgent2D.VelocityComputed += OnVelocityComputed; + } + + /// + /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器 + /// + public bool CheckUsableWeaponInUnclaimed() + { + foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons) + { + //判断是否能拾起武器, 条件: 相同的房间 + if (unclaimedWeapon.AffiliationArea == AffiliationArea) + { + if (!unclaimedWeapon.IsTotalAmmoEmpty()) + { + if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign)) + { + return true; + } + else + { + //判断是否可以移除该标记 + var enemy = unclaimedWeapon.GetSign(SignNames.AiFindWeaponSign); + if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 + { + unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); + return true; + } + else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 + { + unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); + return true; + } + } + } + } + } + + return false; + } + + /// + /// 寻找可用的武器 + /// + public Weapon FindTargetWeapon() + { + Weapon target = null; + var position = Position; + foreach (var weapon in World.Weapon_UnclaimedWeapons) + { + //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间 + if (weapon.AffiliationArea == AffiliationArea) + { + //还有弹药 + if (!weapon.IsTotalAmmoEmpty()) + { + //查询是否有其他敌人标记要拾起该武器 + if (weapon.HasSign(SignNames.AiFindWeaponSign)) + { + var enemy = weapon.GetSign(SignNames.AiFindWeaponSign); + if (enemy == this) //就是自己标记的 + { + + } + else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 + { + weapon.RemoveSign(SignNames.AiFindWeaponSign); + } + else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 + { + weapon.RemoveSign(SignNames.AiFindWeaponSign); + } + else //放弃这把武器 + { + continue; + } + } + + if (target == null) //第一把武器 + { + target = weapon; + } + else if (target.Position.DistanceSquaredTo(position) > + weapon.Position.DistanceSquaredTo(position)) //距离更近 + { + target = weapon; + } + } + } + } + + return target; + } + + /// + /// 获取武器攻击范围 (最大距离值与最小距离的中间值) + /// + /// 从最小到最大距离的过渡量, 0 - 1, 默认 0.5 + public float GetWeaponRange(float weight = 0.5f) + { + if (WeaponPack.ActiveItem != null) + { + var attribute = WeaponPack.ActiveItem.Attribute; + return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight); + } + + return 0; + } + + /// + /// 返回目标点是否在视野范围内 + /// + public virtual bool IsInViewRange(Vector2 target) + { + var isForward = IsPositionInForward(target); + if (isForward) + { + if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径 + { + return true; + } + } + + return false; + } + + /// + /// 返回目标点是否在跟随状态下的视野半径内 + /// + public virtual bool IsInTailAfterViewRange(Vector2 target) + { + var isForward = IsPositionInForward(target); + if (isForward) + { + if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径 + { + return true; + } + } + + return false; + } + + /// + /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true + /// + public bool TestViewRayCast(Vector2 target) + { + ViewRay.Enabled = true; + ViewRay.TargetPosition = ViewRay.ToLocal(target); + ViewRay.ForceRaycastUpdate(); + return ViewRay.IsColliding(); + } + + /// + /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线 + /// + public void TestViewRayCastOver() + { + ViewRay.Enabled = false; + } + + /// + /// AI 拾起武器操作 + /// + public void DoPickUpWeapon() + { + //这几个状态不需要主动拾起武器操作 + var state = StateController.CurrState; + if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiNotify || state == AIStateEnum.AiAstonished || state == AIStateEnum.AiAttack) + { + return; + } + + //拾起地上的武器 + if (InteractiveItem is Weapon weapon) + { + if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起 + { + TriggerInteractive(); + return; + } + + //没弹药了 + if (weapon.IsTotalAmmoEmpty()) + { + return; + } + + var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id); + if (index != -1) //与武器背包中武器类型相同, 补充子弹 + { + if (!WeaponPack.GetItem(index).IsAmmoFull()) + { + TriggerInteractive(); + } + + return; + } + + // var index2 = Holster.FindWeapon((we, i) => + // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty()); + var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty()); + if (index2 != -1) //扔掉没子弹的武器 + { + ThrowWeapon(index2); + TriggerInteractive(); + return; + } + + // if (Holster.HasVacancy()) //有空位, 拾起武器 + // { + // TriggerInteractive(); + // return; + // } + } + } + + /// + /// 获取锁定目标的剩余时间 + /// + public float GetLockRemainderTime() + { + var weapon = WeaponPack.ActiveItem; + if (weapon == null) + { + return LockingTime - LockTargetTime; + } + return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime; + } + + public override void LookTargetPosition(Vector2 pos) + { + LookTarget = null; + base.LookTargetPosition(pos); + } + + /// + /// 执行移动操作 + /// + public void DoMove() + { + // //计算移动 + // NavigationAgent2D.MaxSpeed = EnemyRoleState.MoveSpeed; + // var nextPos = NavigationAgent2D.GetNextPathPosition(); + // NavigationAgent2D.Velocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed; + + AnimatedSprite.Play(AnimatorNames.Run); + //计算移动 + var nextPos = NavigationAgent2D.GetNextPathPosition(); + BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed; + } + + /// + /// 执行站立操作 + /// + public void DoIdle() + { + AnimatedSprite.Play(AnimatorNames.Idle); + BasisVelocity = Vector2.Zero; + } + + /// + /// 更新房间中标记的目标位置 + /// + public void UpdateMarkTargetPosition() + { + if (LookTarget != null) + { + AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position; + } + } + + // private void OnVelocityComputed(Vector2 velocity) + // { + // if (Mathf.Abs(velocity.X) >= 0.01f && Mathf.Abs(velocity.Y) >= 0.01f) + // { + // AnimatedSprite.Play(AnimatorNames.Run); + // BasisVelocity = velocity; + // } + // } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAstonishedState.cs similarity index 93% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiAstonishedState.cs index c39aff90..0d7131be 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAstonishedState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAstonishedState.cs @@ -1,11 +1,11 @@ using Godot; -namespace EnemyState; +namespace AiState; /// /// 发现目标时的惊讶状态 /// -public class AiAstonishedState : StateBase +public class AiAstonishedState : StateBase { /// /// 下一个状态 diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAttackState.cs similarity index 98% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiAttackState.cs index ca847b33..12acf2e5 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiAttackState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiAttackState.cs @@ -1,12 +1,12 @@ using System; using Godot; -namespace EnemyState; +namespace AiState; /// /// ai 攻击状态 /// -public class AiAttackState : StateBase +public class AiAttackState : StateBase { /// /// 上一个状态 @@ -209,7 +209,7 @@ public class AiAttackState : StateBase if (AttackState == AiAttackEnum.AttackInterval) //触发攻击完成 { - Master.AttackTimer = weapon.Attribute.TriggerInterval + Master.EnemyRoleState.AttackInterval; + Master.AttackTimer = weapon.Attribute.TriggerInterval + Master.AttackInterval; } } } diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs similarity index 98% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs index 207795be..2ebc81ea 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFindAmmoState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFindAmmoState.cs @@ -2,12 +2,12 @@ using System; using Godot; -namespace EnemyState; +namespace AiState; /// /// Ai 寻找弹药, 进入该状态需要在参数中传入目标武器对象 /// -public class AiFindAmmoState : StateBase +public class AiFindAmmoState : StateBase { /// /// 目标武器 diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFollowUpState.cs similarity index 95% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiFollowUpState.cs index d0f1bde3..c231a9af 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiFollowUpState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiFollowUpState.cs @@ -2,12 +2,12 @@ using System; using Godot; -namespace EnemyState; +namespace AiState; /// /// 目标在视野内, 跟进目标, 如果距离在子弹有效射程内, 则开火 /// -public class AiFollowUpState : StateBase +public class AiFollowUpState : StateBase { //导航目标点刷新计时器 private float _navigationUpdateTimer = 0; @@ -72,7 +72,7 @@ public class AiFollowUpState : StateBase } else { - inAttackRange = distanceSquared <= Mathf.Pow(Master.EnemyRoleState.ViewRange * 0.7f, 2); + inAttackRange = distanceSquared <= Mathf.Pow(Master.ViewRange * 0.7f, 2); } if (!Master.NavigationAgent2D.IsNavigationFinished()) @@ -121,7 +121,7 @@ public class AiFollowUpState : StateBase else { //距离够近, 可以切换到环绕模式 - if (distanceSquared <= Mathf.Pow(Master.EnemyRoleState.ViewRange * 0.7f, 2)) + if (distanceSquared <= Mathf.Pow(Master.ViewRange * 0.7f, 2)) { ChangeState(AIStateEnum.AiSurround); } diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs similarity index 97% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs index 2ec05264..f539df13 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiLeaveForState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiLeaveForState.cs @@ -2,12 +2,12 @@ using System; using Godot; -namespace EnemyState; +namespace AiState; /// /// 收到其他敌人通知, 前往发现目标的位置 /// -public class AiLeaveForState : StateBase +public class AiLeaveForState : StateBase { //导航目标点刷新计时器 private float _navigationUpdateTimer = 0; diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs similarity index 98% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs index c4635c90..6bcb86d3 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNormalState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNormalState.cs @@ -2,12 +2,12 @@ using System.Linq; using Godot; -namespace EnemyState; +namespace AiState; /// /// AI 正常状态 /// -public class AiNormalState : StateBase +public class AiNormalState : StateBase { //下一个运动的坐标 private Vector2 _nextPos; diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNotifyState.cs similarity index 90% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiNotifyState.cs index 09a6b086..22726f1e 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiNotifyState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiNotifyState.cs @@ -1,11 +1,11 @@ using System; -namespace EnemyState; +namespace AiState; /// /// 发现目标, 通知其它敌人 /// -public class AiNotifyState : StateBase +public class AiNotifyState : StateBase { private float _timer; diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiSurroundState.cs similarity index 97% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiSurroundState.cs index da694ddd..94afce73 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiSurroundState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiSurroundState.cs @@ -2,12 +2,12 @@ using System; using Godot; -namespace EnemyState; +namespace AiState; /// /// 距离目标足够近, 在目标附近随机移动, 并开火 /// -public class AiSurroundState : StateBase +public class AiSurroundState : StateBase { //是否移动结束 private bool _isMoveOver; @@ -151,7 +151,7 @@ public class AiSurroundState : StateBase } else { - if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.EnemyRoleState.ViewRange * 0.7f, 2)) //玩家离开正常射击范围 + if (masterPosition.DistanceSquaredTo(playerPos) > Mathf.Pow(Master.ViewRange * 0.7f, 2)) //玩家离开正常射击范围 { ChangeState(AIStateEnum.AiFollowUp); } diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs similarity index 97% rename from DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs rename to DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs index 4fa2cc57..05a5d9e7 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/state/AiTailAfterState.cs +++ b/DungeonShooting_Godot/src/game/activity/role/ai/state/AiTailAfterState.cs @@ -2,12 +2,12 @@ using System; using Godot; -namespace EnemyState; +namespace AiState; /// /// AI 发现玩家, 跟随玩家, 但是不在视野范围内 /// -public class AiTailAfterState : StateBase +public class AiTailAfterState : StateBase { /// /// 目标是否在视野半径内 diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs index 33bf27b7..e67c7aad 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/Enemy.cs @@ -2,69 +2,15 @@ using System; using System.Collections.Generic; using Config; -using EnemyState; +using AiState; using Godot; /// -/// 高级敌人,可以携带武器 +/// 敌人,可以携带武器 /// [Tool] -public partial class Enemy : Role +public partial class Enemy : AiRole { - /// - /// 目标是否在视野内 - /// - public bool TargetInView { get; set; } = true; - - /// - /// 敌人身上的状态机控制器 - /// - public StateController StateController { get; private set; } - - /// - /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙 - /// - [Export, ExportFillNode] - public RayCast2D ViewRay { get; set; } - - /// - /// 导航代理 - /// - [Export, ExportFillNode] - public NavigationAgent2D NavigationAgent2D { get; set; } - - /// - /// 导航代理中点 - /// - [Export, ExportFillNode] - public Marker2D NavigationPoint { get; set; } - - /// - /// 不通过武发射子弹的开火点 - /// - [Export, ExportFillNode] - public Marker2D FirePoint { get; set; } - - /// - /// 当前敌人所看向的对象, 也就是枪口指向的对象 - /// - public ActivityObject LookTarget { get; set; } - - /// - /// 攻击锁定目标时间 - /// - public float LockingTime { get; set; } = 1f; - - /// - /// 锁定目标已经走过的时间 - /// - public float LockTargetTime { get; set; } = 0; - - /// - /// 敌人属性 - /// - public EnemyRoleState EnemyRoleState { get; private set; } - /// /// 敌人属性 /// @@ -117,10 +63,6 @@ public partial class Enemy : Role public override void OnInit() { base.OnInit(); - - IsAi = true; - - StateController = AddComponent>(); AttackLayer = PhysicsLayer.Obstacle | PhysicsLayer.Player; EnemyLayer = PhysicsLayer.Player; @@ -130,28 +72,11 @@ public partial class Enemy : Role MaxHp = 20; Hp = 20; - - //注册Ai状态机 - StateController.Register(new AiNormalState()); - StateController.Register(new AiTailAfterState()); - StateController.Register(new AiFollowUpState()); - StateController.Register(new AiLeaveForState()); - StateController.Register(new AiSurroundState()); - StateController.Register(new AiFindAmmoState()); - StateController.Register(new AiAttackState()); - StateController.Register(new AiAstonishedState()); - StateController.Register(new AiNotifyState()); - - //默认状态 - StateController.ChangeStateInstant(AIStateEnum.AiNormal); - - //NavigationAgent2D.VelocityComputed += OnVelocityComputed; } protected override RoleState OnCreateRoleState() { - var roleState = new EnemyRoleState(); - EnemyRoleState = roleState; + var roleState = new RoleState(); var enemyBase = GetEnemyAttribute(ActivityBase.Id).Clone(); _enemyAttribute = enemyBase; @@ -161,10 +86,10 @@ public partial class Enemy : Role roleState.MoveSpeed = enemyBase.MoveSpeed; roleState.Acceleration = enemyBase.Acceleration; roleState.Friction = enemyBase.Friction; - roleState.ViewRange = enemyBase.ViewRange; - roleState.TailAfterViewRange = enemyBase.TailAfterViewRange; - roleState.BackViewRange = enemyBase.BackViewRange; - roleState.AttackInterval = enemyBase.AttackInterval; + ViewRange = enemyBase.ViewRange; + TailAfterViewRange = enemyBase.TailAfterViewRange; + BackViewRange = enemyBase.BackViewRange; + AttackInterval = enemyBase.AttackInterval; roleState.Gold = Mathf.Max(0, Utils.Random.RandomConfigRange(enemyBase.Gold)); return roleState; @@ -240,10 +165,10 @@ public partial class Enemy : Role MountPoint.SetLookAt(pos); } - if (EnemyRoleState.CanPickUpWeapon) + if (RoleState.CanPickUpWeapon) { //拾起武器操作 - EnemyPickUpWeapon(); + DoPickUpWeapon(); } } @@ -311,276 +236,6 @@ public partial class Enemy : Role } } - /// - /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器 - /// - public bool CheckUsableWeaponInUnclaimed() - { - foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons) - { - //判断是否能拾起武器, 条件: 相同的房间 - if (unclaimedWeapon.AffiliationArea == AffiliationArea) - { - if (!unclaimedWeapon.IsTotalAmmoEmpty()) - { - if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign)) - { - return true; - } - else - { - //判断是否可以移除该标记 - var enemy = unclaimedWeapon.GetSign(SignNames.AiFindWeaponSign); - if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 - { - unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); - return true; - } - else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 - { - unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign); - return true; - } - } - } - } - } - - return false; - } - - /// - /// 寻找可用的武器 - /// - public Weapon FindTargetWeapon() - { - Weapon target = null; - var position = Position; - foreach (var weapon in World.Weapon_UnclaimedWeapons) - { - //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间 - if (weapon.AffiliationArea == AffiliationArea) - { - //还有弹药 - if (!weapon.IsTotalAmmoEmpty()) - { - //查询是否有其他敌人标记要拾起该武器 - if (weapon.HasSign(SignNames.AiFindWeaponSign)) - { - var enemy = weapon.GetSign(SignNames.AiFindWeaponSign); - if (enemy == this) //就是自己标记的 - { - - } - else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁 - { - weapon.RemoveSign(SignNames.AiFindWeaponSign); - } - else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了 - { - weapon.RemoveSign(SignNames.AiFindWeaponSign); - } - else //放弃这把武器 - { - continue; - } - } - - if (target == null) //第一把武器 - { - target = weapon; - } - else if (target.Position.DistanceSquaredTo(position) > - weapon.Position.DistanceSquaredTo(position)) //距离更近 - { - target = weapon; - } - } - } - } - - return target; - } - - /// - /// 获取武器攻击范围 (最大距离值与最小距离的中间值) - /// - /// 从最小到最大距离的过渡量, 0 - 1, 默认 0.5 - public float GetWeaponRange(float weight = 0.5f) - { - if (WeaponPack.ActiveItem != null) - { - var attribute = WeaponPack.ActiveItem.Attribute; - return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight); - } - - return 0; - } - - /// - /// 返回目标点是否在视野范围内 - /// - public bool IsInViewRange(Vector2 target) - { - var isForward = IsPositionInForward(target); - if (isForward) - { - if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.ViewRange * EnemyRoleState.ViewRange) //没有超出视野半径 - { - return true; - } - } - - return false; - } - - /// - /// 返回目标点是否在跟随状态下的视野半径内 - /// - public bool IsInTailAfterViewRange(Vector2 target) - { - var isForward = IsPositionInForward(target); - if (isForward) - { - if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.TailAfterViewRange * EnemyRoleState.TailAfterViewRange) //没有超出视野半径 - { - return true; - } - } - - return false; - } - - /// - /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true - /// - public bool TestViewRayCast(Vector2 target) - { - ViewRay.Enabled = true; - ViewRay.TargetPosition = ViewRay.ToLocal(target); - ViewRay.ForceRaycastUpdate(); - return ViewRay.IsColliding(); - } - - /// - /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线 - /// - public void TestViewRayCastOver() - { - ViewRay.Enabled = false; - } - - /// - /// AI 拾起武器操作 - /// - private void EnemyPickUpWeapon() - { - //这几个状态不需要主动拾起武器操作 - var state = StateController.CurrState; - if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiNotify || state == AIStateEnum.AiAstonished || state == AIStateEnum.AiAttack) - { - return; - } - - //拾起地上的武器 - if (InteractiveItem is Weapon weapon) - { - if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起 - { - TriggerInteractive(); - return; - } - - //没弹药了 - if (weapon.IsTotalAmmoEmpty()) - { - return; - } - - var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id); - if (index != -1) //与武器背包中武器类型相同, 补充子弹 - { - if (!WeaponPack.GetItem(index).IsAmmoFull()) - { - TriggerInteractive(); - } - - return; - } - - // var index2 = Holster.FindWeapon((we, i) => - // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty()); - var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty()); - if (index2 != -1) //扔掉没子弹的武器 - { - ThrowWeapon(index2); - TriggerInteractive(); - return; - } - - // if (Holster.HasVacancy()) //有空位, 拾起武器 - // { - // TriggerInteractive(); - // return; - // } - } - } - - /// - /// 获取锁定目标的剩余时间 - /// - public float GetLockRemainderTime() - { - var weapon = WeaponPack.ActiveItem; - if (weapon == null) - { - return LockingTime - LockTargetTime; - } - return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime; - } - - public override void LookTargetPosition(Vector2 pos) - { - LookTarget = null; - base.LookTargetPosition(pos); - } - - /// - /// 执行移动操作 - /// - public void DoMove() - { - // //计算移动 - // NavigationAgent2D.MaxSpeed = EnemyRoleState.MoveSpeed; - // var nextPos = NavigationAgent2D.GetNextPathPosition(); - // NavigationAgent2D.Velocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed; - - AnimatedSprite.Play(AnimatorNames.Run); - //计算移动 - var nextPos = NavigationAgent2D.GetNextPathPosition(); - BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed; - } - - /// - /// 执行站立操作 - /// - public void DoIdle() - { - AnimatedSprite.Play(AnimatorNames.Idle); - BasisVelocity = Vector2.Zero; - } - - /// - /// 更新房间中标记的目标位置 - /// - public void UpdateMarkTargetPosition() - { - if (LookTarget != null) - { - AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position; - } - } - /// /// 从标记出生时调用, 预加载波不会调用 /// @@ -590,13 +245,4 @@ public partial class Enemy : Role StateController.Enable = false; this.CallDelay(0.7f, () => StateController.Enable = true); } - - // private void OnVelocityComputed(Vector2 velocity) - // { - // if (Mathf.Abs(velocity.X) >= 0.01f && Mathf.Abs(velocity.Y) >= 0.01f) - // { - // AnimatedSprite.Play(AnimatorNames.Run); - // BasisVelocity = velocity; - // } - // } } diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/EnemyRoleState.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/EnemyRoleState.cs deleted file mode 100644 index ef3618a2..00000000 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/EnemyRoleState.cs +++ /dev/null @@ -1,23 +0,0 @@ - -public class EnemyRoleState : RoleState -{ - /// - /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙 - /// - public float ViewRange = 250; - - /// - /// 发现玩家后跟随玩家的视野半径 - /// - public float TailAfterViewRange = 400; - - /// - /// 背后的视野半径, 单位像素 - /// - public float BackViewRange = 50; - - /// - /// 攻击间隔时间, 秒 - /// - public float AttackInterval = 0; -} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs b/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs index ed3d2717..495337cc 100644 --- a/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs +++ b/DungeonShooting_Godot/src/game/activity/role/enemy/NoWeaponEnemy.cs @@ -82,7 +82,7 @@ public partial class NoWeaponEnemy : Enemy { if (name == AnimatorNames.Attack) { - AttackTimer = EnemyRoleState.AttackInterval; + AttackTimer = AttackInterval; } } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/activity/shop/ShopBoss.cs b/DungeonShooting_Godot/src/game/activity/role/shop/ShopBoss.cs similarity index 83% rename from DungeonShooting_Godot/src/game/activity/shop/ShopBoss.cs rename to DungeonShooting_Godot/src/game/activity/role/shop/ShopBoss.cs index 1811b53a..b6153c30 100644 --- a/DungeonShooting_Godot/src/game/activity/shop/ShopBoss.cs +++ b/DungeonShooting_Godot/src/game/activity/role/shop/ShopBoss.cs @@ -5,7 +5,7 @@ using Godot; /// 商店老板 /// [Tool] -public partial class ShopBoss : Role +public partial class ShopBoss : AiRole { public override void OnCreateWithMark(RoomPreinstall roomPreinstall, ActivityMark activityMark) {