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