Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / role / enemy / Enemy.cs
@小李xl 小李xl on 10 May 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.Collections.Generic;
  15. using Godot;
  16.  
  17. /// <summary>
  18. /// 基础敌人
  19. /// </summary>
  20. [RegisterActivity(ActivityIdPrefix.Enemy + "0001", ResourcePath.prefab_role_Enemy_tscn)]
  21. public partial class Enemy : Role
  22. {
  23. /// <summary>
  24. /// 公共属性, 是否找到目标, 如果找到目标, 则与目标同房间的所有敌人都会知道目标的位置
  25. /// </summary>
  26. public static bool IsFindTarget { get; private set; }
  27.  
  28. /// <summary>
  29. /// 公共属性, 在哪个区域找到的目标, 所有该区域下的敌人都会知道目标的位置
  30. /// </summary>
  31. public static HashSet<AffiliationArea> FindTargetAffiliationSet { get; } = new HashSet<AffiliationArea>();
  32. /// <summary>
  33. /// 公共属性, 找到的目标的位置, 如果目标在视野内, 则一直更新
  34. /// </summary>
  35. public static Vector2 FindTargetPosition { get; private set; }
  36.  
  37. /// <summary>
  38. /// 记录所有存活的敌人
  39. /// </summary>
  40. private static readonly List<Enemy> _enemieList = new List<Enemy>();
  41.  
  42. /// <summary>
  43. /// 敌人身上的状态机控制器
  44. /// </summary>
  45. public StateController<Enemy, AiStateEnum> StateController { get; private set; }
  46.  
  47. /// <summary>
  48. /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
  49. /// </summary>
  50. public float ViewRange { get; set; } = 250;
  51.  
  52. /// <summary>
  53. /// 发现玩家后的视野半径
  54. /// </summary>
  55. public float TailAfterViewRange { get; set; } = 400;
  56.  
  57. /// <summary>
  58. /// 背后的视野半径, 单位像素
  59. /// </summary>
  60. public float BackViewRange { get; set; } = 50;
  61.  
  62. /// <summary>
  63. /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙
  64. /// </summary>
  65. public RayCast2D ViewRay { get; private set; }
  66.  
  67. /// <summary>
  68. /// 导航代理
  69. /// </summary>
  70. public NavigationAgent2D NavigationAgent2D { get; private set; }
  71.  
  72. /// <summary>
  73. /// 导航代理中点
  74. /// </summary>
  75. public Marker2D NavigationPoint { get; private set; }
  76.  
  77. //开火间隙时间
  78. private float _enemyAttackTimer = 0;
  79. //目标在视野内的时间
  80. private float _targetInViewTime = 0;
  81.  
  82. public override void OnInit()
  83. {
  84. base.OnInit();
  85. IsAi = true;
  86. StateController = AddComponent<StateController<Enemy, AiStateEnum>>();
  87.  
  88. AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Props | PhysicsLayer.Player;
  89. Camp = CampEnum.Camp2;
  90.  
  91. MoveSpeed = 20;
  92.  
  93. MaxHp = 20;
  94. Hp = 20;
  95.  
  96. //视野射线
  97. ViewRay = GetNode<RayCast2D>("ViewRay");
  98. NavigationPoint = GetNode<Marker2D>("NavigationPoint");
  99. NavigationAgent2D = NavigationPoint.GetNode<NavigationAgent2D>("NavigationAgent2D");
  100.  
  101. //PathSign = new PathSign(this, PathSignLength, GameApplication.Instance.Node3D.Player);
  102.  
  103. //注册Ai状态机
  104. StateController.Register(new AiNormalState());
  105. StateController.Register(new AiProbeState());
  106. StateController.Register(new AiTailAfterState());
  107. StateController.Register(new AiFollowUpState());
  108. StateController.Register(new AiLeaveForState());
  109. StateController.Register(new AiSurroundState());
  110. StateController.Register(new AiFindAmmoState());
  111. //默认状态
  112. StateController.ChangeState(AiStateEnum.AiNormal);
  113. }
  114.  
  115. public override void _EnterTree()
  116. {
  117. if (!_enemieList.Contains(this))
  118. {
  119. _enemieList.Add(this);
  120. }
  121. }
  122.  
  123. public override void _ExitTree()
  124. {
  125. base._ExitTree();
  126. _enemieList.Remove(this);
  127. }
  128.  
  129. protected override void OnDie()
  130. {
  131. //扔掉所有武器
  132. var weapons = Holster.GetAndClearWeapon();
  133. for (var i = 0; i < weapons.Length; i++)
  134. {
  135. weapons[i].ThrowWeapon(this);
  136. }
  137.  
  138. var effPos = Position + new Vector2(0, -Altitude);
  139. //血液特效
  140. var blood = ResourceManager.LoadAndInstantiate<AutoDestroyEffect>(ResourcePath.prefab_effect_activityObject_EnemyBloodEffect_tscn);
  141. blood.Position = effPos - new Vector2(0, 12);
  142. blood.AddToActivityRoot(RoomLayerEnum.NormalLayer);
  143. //创建敌人碎片
  144. var count = Utils.RandomRangeInt(3, 6);
  145. for (var i = 0; i < count; i++)
  146. {
  147. var debris = Create(ActivityIdPrefix.Effect + "0001");
  148. debris.PutDown(effPos, RoomLayerEnum.NormalLayer);
  149. debris.InheritVelocity(this);
  150. }
  151. //派发敌人死亡信号
  152. EventManager.EmitEvent(EventEnum.OnEnemyDie, this);
  153. Destroy();
  154. }
  155.  
  156. protected override void Process(float delta)
  157. {
  158. base.Process(delta);
  159. _enemyAttackTimer -= delta;
  160.  
  161. //目标在视野内的时间
  162. var currState = StateController.CurrState;
  163. if (currState == AiStateEnum.AiSurround || currState == AiStateEnum.AiFollowUp)
  164. {
  165. _targetInViewTime += delta;
  166. }
  167. else
  168. {
  169. _targetInViewTime = 0;
  170. }
  171.  
  172. EnemyPickUpWeapon();
  173. }
  174.  
  175. protected override void OnHit(int damage)
  176. {
  177. //受到伤害
  178. var state = StateController.CurrState;
  179. if (state == AiStateEnum.AiNormal || state == AiStateEnum.AiProbe || state == AiStateEnum.AiLeaveFor)
  180. {
  181. StateController.ChangeStateLate(AiStateEnum.AiTailAfter);
  182. }
  183. }
  184.  
  185. /// <summary>
  186. /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
  187. /// </summary>
  188. public bool CheckUsableWeaponInUnclaimed()
  189. {
  190. foreach (var unclaimedWeapon in Weapon.UnclaimedWeapons)
  191. {
  192. //判断是否能拾起武器, 条件: 相同的房间
  193. if (unclaimedWeapon.Affiliation == Affiliation)
  194. {
  195. if (!unclaimedWeapon.IsTotalAmmoEmpty())
  196. {
  197. if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
  198. {
  199. return true;
  200. }
  201. else
  202. {
  203. //判断是否可以移除该标记
  204. var enemy = unclaimedWeapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  205. if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  206. {
  207. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  208. return true;
  209. }
  210. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  211. {
  212. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  213. return true;
  214. }
  215. }
  216. }
  217. }
  218. }
  219.  
  220. return false;
  221. }
  222. /// <summary>
  223. /// 寻找可用的武器
  224. /// </summary>
  225. public Weapon FindTargetWeapon()
  226. {
  227. Weapon target = null;
  228. var position = Position;
  229. foreach (var weapon in Weapon.UnclaimedWeapons)
  230. {
  231. //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间
  232. if (weapon.Affiliation == Affiliation)
  233. {
  234. //还有弹药
  235. if (!weapon.IsTotalAmmoEmpty())
  236. {
  237. //查询是否有其他敌人标记要拾起该武器
  238. if (weapon.HasSign(SignNames.AiFindWeaponSign))
  239. {
  240. var enemy = weapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  241. if (enemy == this) //就是自己标记的
  242. {
  243.  
  244. }
  245. else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  246. {
  247. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  248. }
  249. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  250. {
  251. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  252. }
  253. else //放弃这把武器
  254. {
  255. continue;
  256. }
  257. }
  258.  
  259. if (target == null) //第一把武器
  260. {
  261. target = weapon;
  262. }
  263. else if (target.Position.DistanceSquaredTo(position) >
  264. weapon.Position.DistanceSquaredTo(position)) //距离更近
  265. {
  266. target = weapon;
  267. }
  268. }
  269. }
  270. }
  271.  
  272. return target;
  273. }
  274.  
  275. /// <summary>
  276. /// 检查是否能切换到 AiStateEnum.AiLeaveFor 状态
  277. /// </summary>
  278. /// <returns></returns>
  279. public bool CanChangeLeaveFor()
  280. {
  281. if (!IsFindTarget)
  282. {
  283. return false;
  284. }
  285.  
  286. var currState = StateController.CurrState;
  287. if (currState == AiStateEnum.AiNormal || currState == AiStateEnum.AiProbe)
  288. {
  289. //判断是否在同一个房间内
  290. return FindTargetAffiliationSet.Contains(Affiliation);
  291. }
  292. return false;
  293. }
  294. /// <summary>
  295. /// 更新敌人视野
  296. /// </summary>
  297. public static void UpdateEnemiesView()
  298. {
  299. IsFindTarget = false;
  300. FindTargetAffiliationSet.Clear();
  301. for (var i = 0; i < _enemieList.Count; i++)
  302. {
  303. var enemy = _enemieList[i];
  304. var state = enemy.StateController.CurrState;
  305. if (state == AiStateEnum.AiFollowUp || state == AiStateEnum.AiSurround) //目标在视野内
  306. {
  307. if (!IsFindTarget)
  308. {
  309. IsFindTarget = true;
  310. FindTargetPosition = Player.Current.GetCenterPosition();
  311. FindTargetAffiliationSet.Add(Player.Current.Affiliation);
  312. }
  313. FindTargetAffiliationSet.Add(enemy.Affiliation);
  314. }
  315. }
  316. }
  317.  
  318. /// <summary>
  319. /// Ai触发的攻击
  320. /// </summary>
  321. public void EnemyAttack(float delta)
  322. {
  323. var weapon = Holster.ActiveWeapon;
  324. if (weapon != null)
  325. {
  326. if (weapon.IsTotalAmmoEmpty()) //当前武器弹药打空
  327. {
  328. //切换到有子弹的武器
  329. var index = Holster.FindWeapon((we, i) => !we.IsTotalAmmoEmpty());
  330. if (index != -1)
  331. {
  332. Holster.ExchangeByIndex(index);
  333. }
  334. else //所有子弹打光
  335. {
  336. }
  337. }
  338. else if (weapon.Reloading) //换弹中
  339. {
  340.  
  341. }
  342. else if (weapon.IsAmmoEmpty()) //弹夹已经打空
  343. {
  344. Reload();
  345. }
  346. else if (_targetInViewTime >= weapon.Attribute.AiTargetLockingTime) //正常射击
  347. {
  348. if (weapon.GetDelayedAttackTime() > 0)
  349. {
  350. Attack();
  351. }
  352. else
  353. {
  354. if (weapon.Attribute.ContinuousShoot) //连发
  355. {
  356. Attack();
  357. }
  358. else //单发
  359. {
  360. if (_enemyAttackTimer <= 0)
  361. {
  362. _enemyAttackTimer = 60f / weapon.Attribute.StartFiringSpeed;
  363. Attack();
  364. }
  365. }
  366. }
  367. }
  368. }
  369. }
  370.  
  371. /// <summary>
  372. /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
  373. /// </summary>
  374. /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param>
  375. public float GetWeaponRange(float weight = 0.5f)
  376. {
  377. if (Holster.ActiveWeapon != null)
  378. {
  379. var attribute = Holster.ActiveWeapon.Attribute;
  380. return Mathf.Lerp(attribute.MinDistance, attribute.MaxDistance, weight);
  381. }
  382.  
  383. return 0;
  384. }
  385.  
  386. /// <summary>
  387. /// 返回目标点是否在视野范围内
  388. /// </summary>
  389. public bool IsInViewRange(Vector2 target)
  390. {
  391. var isForward = IsPositionInForward(target);
  392. if (isForward)
  393. {
  394. if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径
  395. {
  396. return true;
  397. }
  398. }
  399.  
  400. return false;
  401. }
  402.  
  403. /// <summary>
  404. /// 返回目标点是否在跟随状态下的视野半径内
  405. /// </summary>
  406. public bool IsInTailAfterViewRange(Vector2 target)
  407. {
  408. var isForward = IsPositionInForward(target);
  409. if (isForward)
  410. {
  411. if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径
  412. {
  413. return true;
  414. }
  415. }
  416.  
  417. return false;
  418. }
  419.  
  420. /// <summary>
  421. /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null
  422. /// </summary>
  423. public bool TestViewRayCast(Vector2 target)
  424. {
  425. ViewRay.Enabled = true;
  426. ViewRay.TargetPosition = ViewRay.ToLocal(target);
  427. ViewRay.ForceRaycastUpdate();
  428. return ViewRay.IsColliding();
  429. }
  430.  
  431. /// <summary>
  432. /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
  433. /// </summary>
  434. public void TestViewRayCastOver()
  435. {
  436. ViewRay.Enabled = false;
  437. }
  438.  
  439. /// <summary>
  440. /// AI 拾起武器操作
  441. /// </summary>
  442. private void EnemyPickUpWeapon()
  443. {
  444. //这几个状态不需要主动拾起武器操作
  445. var state = StateController.CurrState;
  446. if (state == AiStateEnum.AiNormal)
  447. {
  448. return;
  449. }
  450. //拾起地上的武器
  451. if (InteractiveItem is Weapon weapon)
  452. {
  453. if (Holster.ActiveWeapon == null) //手上没有武器, 无论如何也要拾起
  454. {
  455. TriggerInteractive();
  456. return;
  457. }
  458.  
  459. //没弹药了
  460. if (weapon.IsTotalAmmoEmpty())
  461. {
  462. return;
  463. }
  464. var index = Holster.FindWeapon((we, i) => we.ItemId == weapon.ItemId);
  465. if (index != -1) //与武器袋中武器类型相同, 补充子弹
  466. {
  467. if (!Holster.GetWeapon(index).IsAmmoFull())
  468. {
  469. TriggerInteractive();
  470. }
  471.  
  472. return;
  473. }
  474.  
  475. // var index2 = Holster.FindWeapon((we, i) =>
  476. // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
  477. var index2 = Holster.FindWeapon((we, i) => we.IsTotalAmmoEmpty());
  478. if (index2 != -1) //扔掉没子弹的武器
  479. {
  480. ThrowWeapon(index2);
  481. TriggerInteractive();
  482. return;
  483. }
  484. // if (Holster.HasVacancy()) //有空位, 拾起武器
  485. // {
  486. // TriggerInteractive();
  487. // return;
  488. // }
  489. }
  490. }
  491.  
  492. }