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