Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / activity / role / enemy / AdvancedEnemy.cs
@小李xl 小李xl on 15 Nov 2023 17 KB 普通ai攻击状态
  1. #region 基础敌人设计思路
  2. /*
  3. 敌人有三种状态:
  4. 状态1: 未发现玩家, 视野不可穿墙, 该状态下敌人移动比较规律, 移动速度较慢, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3, 该房间的敌人不能再回到状态1
  5. 状态2: 发现有玩家, 但不知道在哪, 视野不可穿墙, 该情况下敌人移动速度明显加快, 移动不规律, 一旦玩家进入视野或者听到玩家枪声, 立刻切换至状态3
  6. 状态3: 明确知道玩家的位置, 视野允许穿墙, 移动速度与状态2一致, 进入该状态时, 敌人之间会相互告知玩家所在位置, 并朝着玩家位置开火,
  7. 如果有墙格挡, 则有一定概率继续开火, 一旦玩家立刻敌人视野超哥一段时间, 敌人自动切换为状态2
  8.  
  9. 敌人状态1只存在于少数房间内, 比如特殊房间, 大部分情况下敌人应该是状态2, 或者玩家进入房间时就被敌人发现
  10. */
  11. #endregion
  12.  
  13.  
  14. using System;
  15. using AdvancedState;
  16. using Godot;
  17.  
  18. /// <summary>
  19. /// 高级敌人,可以携带武器
  20. /// </summary>
  21. [Tool]
  22. public partial class AdvancedEnemy : AdvancedRole
  23. {
  24. /// <summary>
  25. /// 目标是否在视野内
  26. /// </summary>
  27. public bool TargetInView { get; set; } = true;
  28. /// <summary>
  29. /// 敌人身上的状态机控制器
  30. /// </summary>
  31. public StateController<AdvancedEnemy, AIAdvancedStateEnum> StateController { get; private set; }
  32.  
  33. /// <summary>
  34. /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
  35. /// </summary>
  36. public float ViewRange { get; set; } = 250;
  37.  
  38. /// <summary>
  39. /// 发现玩家后的视野半径
  40. /// </summary>
  41. public float TailAfterViewRange { get; set; } = 400;
  42.  
  43. /// <summary>
  44. /// 背后的视野半径, 单位像素
  45. /// </summary>
  46. public float BackViewRange { get; set; } = 50;
  47.  
  48. /// <summary>
  49. /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙
  50. /// </summary>
  51. [Export, ExportFillNode]
  52. public RayCast2D ViewRay { get; private set; }
  53.  
  54. /// <summary>
  55. /// 导航代理
  56. /// </summary>
  57. [Export, ExportFillNode]
  58. public NavigationAgent2D NavigationAgent2D { get; private set; }
  59.  
  60. /// <summary>
  61. /// 导航代理中点
  62. /// </summary>
  63. [Export, ExportFillNode]
  64. public Marker2D NavigationPoint { get; private set; }
  65.  
  66. /// <summary>
  67. /// Ai攻击状态, 调用 Attack() 函数后会刷新
  68. /// </summary>
  69. public AiAttackEnum AttackState { get; private set; }
  70. /// <summary>
  71. /// 当前敌人所看向的对象, 也就是枪口指向的对象
  72. /// </summary>
  73. public ActivityObject LookTarget { get; set; }
  74. //锁定目标时间
  75. private float _lockTargetTime = 0;
  76.  
  77. public override void OnInit()
  78. {
  79. base.OnInit();
  80. IsAi = true;
  81. StateController = AddComponent<StateController<AdvancedEnemy, AIAdvancedStateEnum>>();
  82.  
  83. AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Player;
  84. EnemyLayer = PhysicsLayer.Player;
  85. Camp = CampEnum.Camp2;
  86.  
  87. RoleState.MoveSpeed = 20;
  88.  
  89. MaxHp = 20;
  90. Hp = 20;
  91.  
  92. //PathSign = new PathSign(this, PathSignLength, GameApplication.Instance.Node3D.Player);
  93.  
  94. //注册Ai状态机
  95. StateController.Register(new AiNormalState());
  96. //StateController.Register(new AiProbeState());
  97. StateController.Register(new AiTailAfterState());
  98. StateController.Register(new AiFollowUpState());
  99. StateController.Register(new AiLeaveForState());
  100. StateController.Register(new AiSurroundState());
  101. StateController.Register(new AiFindAmmoState());
  102. //默认状态
  103. StateController.ChangeStateInstant(AIAdvancedStateEnum.AiNormal);
  104. }
  105.  
  106. public override void EnterTree()
  107. {
  108. if (!World.Enemy_InstanceList.Contains(this))
  109. {
  110. World.Enemy_InstanceList.Add(this);
  111. }
  112. }
  113.  
  114. public override void ExitTree()
  115. {
  116. World.Enemy_InstanceList.Remove(this);
  117. }
  118.  
  119. protected override void OnDie()
  120. {
  121. //扔掉所有武器
  122. var weapons = WeaponPack.GetAndClearItem();
  123. for (var i = 0; i < weapons.Length; i++)
  124. {
  125. weapons[i].ThrowWeapon(this);
  126. }
  127.  
  128. var effPos = Position + new Vector2(0, -Altitude);
  129. //血液特效
  130. var blood = ObjectManager.GetPoolItem<AutoDestroyParticles>(ResourcePath.prefab_effect_enemy_EnemyBloodEffect_tscn);
  131. blood.Position = effPos - new Vector2(0, 12);
  132. blood.AddToActivityRoot(RoomLayerEnum.NormalLayer);
  133. blood.PlayEffect();
  134.  
  135. //创建敌人碎片
  136. var count = Utils.Random.RandomRangeInt(3, 6);
  137. for (var i = 0; i < count; i++)
  138. {
  139. var debris = Create(Ids.Id_effect0001);
  140. debris.PutDown(effPos, RoomLayerEnum.NormalLayer);
  141. debris.InheritVelocity(this);
  142. }
  143. //派发敌人死亡信号
  144. EventManager.EmitEvent(EventEnum.OnEnemyDie, this);
  145. Destroy();
  146. }
  147.  
  148. protected override void Process(float delta)
  149. {
  150. base.Process(delta);
  151. if (IsDie)
  152. {
  153. return;
  154. }
  155. //看向目标
  156. if (LookTarget != null && MountLookTarget)
  157. {
  158. var pos = LookTarget.Position;
  159. LookPosition = pos;
  160. //脸的朝向
  161. var gPos = Position;
  162. if (pos.X > gPos.X && Face == FaceDirection.Left)
  163. {
  164. Face = FaceDirection.Right;
  165. }
  166. else if (pos.X < gPos.X && Face == FaceDirection.Right)
  167. {
  168. Face = FaceDirection.Left;
  169. }
  170. //枪口跟随目标
  171. MountPoint.SetLookAt(pos);
  172. }
  173. //目标在视野内的时间
  174. var currState = StateController.CurrState;
  175. if (currState == AIAdvancedStateEnum.AiSurround || currState == AIAdvancedStateEnum.AiFollowUp)
  176. {
  177. var weapon = WeaponPack.ActiveItem;
  178. if (weapon != null)
  179. {
  180. if (weapon.GetBeLoadedStateState() >= 2 && !weapon.IsAttackIntervalTime()) //必须在可以开火时记录时间
  181. {
  182. _lockTargetTime += delta;
  183. }
  184. else
  185. {
  186. _lockTargetTime = 0;
  187. }
  188. if (AttackState == AiAttackEnum.LockingTime) //锁定玩家状态
  189. {
  190. var aiLockRemainderTime = GetLockRemainderTime();
  191. MountLookTarget = aiLockRemainderTime >= weapon.Attribute.AiAttackAttr.LockAngleTime;
  192. //更新瞄准辅助线
  193. if (weapon.Attribute.AiAttackAttr.ShowSubline)
  194. {
  195. if (SubLine == null)
  196. {
  197. InitSubLine();
  198. }
  199. else
  200. {
  201. SubLine.Enable = true;
  202. }
  203.  
  204. //播放警告删掉动画
  205. if (!SubLine.IsPlayWarnAnimation && aiLockRemainderTime <= 0.5f)
  206. {
  207. SubLine.PlayWarnAnimation(0.5f);
  208. }
  209. }
  210. }
  211. else
  212. {
  213. //关闭辅助线
  214. if (SubLine != null)
  215. {
  216. SubLine.Enable = false;
  217. }
  218. if (AttackState == AiAttackEnum.Attack || AttackState == AiAttackEnum.AttackInterval)
  219. {
  220. if (weapon.Attribute.AiAttackAttr.AttackLockAngle) //开火时锁定枪口角度
  221. {
  222. //连发状态锁定角度
  223. MountLookTarget = !(weapon.GetContinuousCount() > 0 || weapon.GetAttackTimer() > 0);
  224. }
  225. else
  226. {
  227. MountLookTarget = true;
  228. }
  229. }
  230. else
  231. {
  232. MountLookTarget = true;
  233. }
  234. }
  235. }
  236. else
  237. {
  238. MountLookTarget = true;
  239. _lockTargetTime = 0;
  240. }
  241. }
  242. else
  243. {
  244. MountLookTarget = true;
  245. _lockTargetTime = 0;
  246. }
  247.  
  248. //拾起武器操作
  249. EnemyPickUpWeapon();
  250. }
  251.  
  252. protected override void OnHit(int damage, bool realHarm)
  253. {
  254. //受到伤害
  255. var state = StateController.CurrState;
  256. if (state == AIAdvancedStateEnum.AiNormal || state == AIAdvancedStateEnum.AiLeaveFor) //|| state == AiStateEnum.AiProbe
  257. {
  258. StateController.ChangeState(AIAdvancedStateEnum.AiTailAfter);
  259. }
  260. }
  261.  
  262. /// <summary>
  263. /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
  264. /// </summary>
  265. public bool CheckUsableWeaponInUnclaimed()
  266. {
  267. foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons)
  268. {
  269. //判断是否能拾起武器, 条件: 相同的房间
  270. if (unclaimedWeapon.AffiliationArea == AffiliationArea)
  271. {
  272. if (!unclaimedWeapon.IsTotalAmmoEmpty())
  273. {
  274. if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
  275. {
  276. return true;
  277. }
  278. else
  279. {
  280. //判断是否可以移除该标记
  281. var enemy = unclaimedWeapon.GetSign<AdvancedEnemy>(SignNames.AiFindWeaponSign);
  282. if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  283. {
  284. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  285. return true;
  286. }
  287. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  288. {
  289. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  290. return true;
  291. }
  292. }
  293. }
  294. }
  295. }
  296.  
  297. return false;
  298. }
  299. /// <summary>
  300. /// 寻找可用的武器
  301. /// </summary>
  302. public Weapon FindTargetWeapon()
  303. {
  304. Weapon target = null;
  305. var position = Position;
  306. foreach (var weapon in World.Weapon_UnclaimedWeapons)
  307. {
  308. //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间
  309. if (weapon.AffiliationArea == AffiliationArea)
  310. {
  311. //还有弹药
  312. if (!weapon.IsTotalAmmoEmpty())
  313. {
  314. //查询是否有其他敌人标记要拾起该武器
  315. if (weapon.HasSign(SignNames.AiFindWeaponSign))
  316. {
  317. var enemy = weapon.GetSign<AdvancedEnemy>(SignNames.AiFindWeaponSign);
  318. if (enemy == this) //就是自己标记的
  319. {
  320.  
  321. }
  322. else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  323. {
  324. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  325. }
  326. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  327. {
  328. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  329. }
  330. else //放弃这把武器
  331. {
  332. continue;
  333. }
  334. }
  335.  
  336. if (target == null) //第一把武器
  337. {
  338. target = weapon;
  339. }
  340. else if (target.Position.DistanceSquaredTo(position) >
  341. weapon.Position.DistanceSquaredTo(position)) //距离更近
  342. {
  343. target = weapon;
  344. }
  345. }
  346. }
  347. }
  348.  
  349. return target;
  350. }
  351.  
  352. /// <summary>
  353. /// 检查是否能切换到 AiStateEnum.AiLeaveFor 状态
  354. /// </summary>
  355. public bool CanChangeLeaveFor()
  356. {
  357. if (!World.Enemy_IsFindTarget)
  358. {
  359. return false;
  360. }
  361.  
  362. var currState = StateController.CurrState;
  363. if (currState == AIAdvancedStateEnum.AiNormal)// || currState == AiStateEnum.AiProbe)
  364. {
  365. //判断是否在同一个房间内
  366. return World.Enemy_FindTargetAffiliationSet.Contains(AffiliationArea);
  367. }
  368. return false;
  369. }
  370. /// <summary>
  371. /// 敌人发动攻击
  372. /// </summary>
  373. public virtual void EnemyAttack()
  374. {
  375. var weapon = WeaponPack.ActiveItem;
  376. if (weapon != null)
  377. {
  378. AttackState = weapon.AiTriggerAttackState();
  379. }
  380. else //没有武器
  381. {
  382. AttackState = AiAttackEnum.NoWeapon;
  383. }
  384. }
  385.  
  386. /// <summary>
  387. /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
  388. /// </summary>
  389. /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param>
  390. public float GetWeaponRange(float weight = 0.5f)
  391. {
  392. if (WeaponPack.ActiveItem != null)
  393. {
  394. var attribute = WeaponPack.ActiveItem.Attribute;
  395. return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight);
  396. }
  397.  
  398. return 0;
  399. }
  400.  
  401. /// <summary>
  402. /// 返回目标点是否在视野范围内
  403. /// </summary>
  404. public bool IsInViewRange(Vector2 target)
  405. {
  406. var isForward = IsPositionInForward(target);
  407. if (isForward)
  408. {
  409. if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径
  410. {
  411. return true;
  412. }
  413. }
  414.  
  415. return false;
  416. }
  417.  
  418. /// <summary>
  419. /// 返回目标点是否在跟随状态下的视野半径内
  420. /// </summary>
  421. public bool IsInTailAfterViewRange(Vector2 target)
  422. {
  423. var isForward = IsPositionInForward(target);
  424. if (isForward)
  425. {
  426. if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径
  427. {
  428. return true;
  429. }
  430. }
  431.  
  432. return false;
  433. }
  434.  
  435. /// <summary>
  436. /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null
  437. /// </summary>
  438. public bool TestViewRayCast(Vector2 target)
  439. {
  440. ViewRay.Enabled = true;
  441. ViewRay.TargetPosition = ViewRay.ToLocal(target);
  442. ViewRay.ForceRaycastUpdate();
  443. return ViewRay.IsColliding();
  444. }
  445.  
  446. /// <summary>
  447. /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
  448. /// </summary>
  449. public void TestViewRayCastOver()
  450. {
  451. ViewRay.Enabled = false;
  452. }
  453.  
  454. /// <summary>
  455. /// AI 拾起武器操作
  456. /// </summary>
  457. private void EnemyPickUpWeapon()
  458. {
  459. //这几个状态不需要主动拾起武器操作
  460. var state = StateController.CurrState;
  461. if (state == AIAdvancedStateEnum.AiNormal)
  462. {
  463. return;
  464. }
  465. //拾起地上的武器
  466. if (InteractiveItem is Weapon weapon)
  467. {
  468. if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起
  469. {
  470. TriggerInteractive();
  471. return;
  472. }
  473.  
  474. //没弹药了
  475. if (weapon.IsTotalAmmoEmpty())
  476. {
  477. return;
  478. }
  479. var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id);
  480. if (index != -1) //与武器背包中武器类型相同, 补充子弹
  481. {
  482. if (!WeaponPack.GetItem(index).IsAmmoFull())
  483. {
  484. TriggerInteractive();
  485. }
  486.  
  487. return;
  488. }
  489.  
  490. // var index2 = Holster.FindWeapon((we, i) =>
  491. // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
  492. var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty());
  493. if (index2 != -1) //扔掉没子弹的武器
  494. {
  495. ThrowWeapon(index2);
  496. TriggerInteractive();
  497. return;
  498. }
  499. // if (Holster.HasVacancy()) //有空位, 拾起武器
  500. // {
  501. // TriggerInteractive();
  502. // return;
  503. // }
  504. }
  505. }
  506.  
  507. /// <summary>
  508. /// 获取锁定目标的时间
  509. /// </summary>
  510. public float GetLockTime()
  511. {
  512. return _lockTargetTime;
  513. }
  514. /// <summary>
  515. /// 获取锁定目标的剩余时间
  516. /// </summary>
  517. public float GetLockRemainderTime()
  518. {
  519. var weapon = WeaponPack.ActiveItem;
  520. if (weapon == null)
  521. {
  522. return 0;
  523. }
  524. return weapon.Attribute.AiAttackAttr.LockingTime - _lockTargetTime;
  525. }
  526.  
  527. /// <summary>
  528. /// 强制设置锁定目标时间
  529. /// </summary>
  530. public void SetLockTargetTime(float time)
  531. {
  532. _lockTargetTime = time;
  533. }
  534.  
  535. public override void LookTargetPosition(Vector2 pos)
  536. {
  537. LookTarget = null;
  538. base.LookTargetPosition(pos);
  539. }
  540. }