Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / activity / role / enemy / Enemy.cs
  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 System.Collections.Generic;
  16. using Config;
  17. using EnemyState;
  18. using Godot;
  19.  
  20. /// <summary>
  21. /// 高级敌人,可以携带武器
  22. /// </summary>
  23. [Tool]
  24. public partial class Enemy : Role
  25. {
  26. /// <summary>
  27. /// 目标是否在视野内
  28. /// </summary>
  29. public bool TargetInView { get; set; } = true;
  30. /// <summary>
  31. /// 敌人身上的状态机控制器
  32. /// </summary>
  33. public StateController<Enemy, AIStateEnum> StateController { get; private set; }
  34. /// <summary>
  35. /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙
  36. /// </summary>
  37. [Export, ExportFillNode]
  38. public RayCast2D ViewRay { get; set; }
  39.  
  40. /// <summary>
  41. /// 导航代理
  42. /// </summary>
  43. [Export, ExportFillNode]
  44. public NavigationAgent2D NavigationAgent2D { get; set; }
  45.  
  46. /// <summary>
  47. /// 导航代理中点
  48. /// </summary>
  49. [Export, ExportFillNode]
  50. public Marker2D NavigationPoint { get; set; }
  51.  
  52. /// <summary>
  53. /// 不通过武发射子弹的开火点
  54. /// </summary>
  55. [Export, ExportFillNode]
  56. public Marker2D FirePoint { get; set; }
  57. /// <summary>
  58. /// 当前敌人所看向的对象, 也就是枪口指向的对象
  59. /// </summary>
  60. public ActivityObject LookTarget { get; set; }
  61.  
  62. /// <summary>
  63. /// 攻击锁定目标时间
  64. /// </summary>
  65. public float LockingTime { get; set; } = 1f;
  66. /// <summary>
  67. /// 锁定目标已经走过的时间
  68. /// </summary>
  69. public float LockTargetTime { get; set; } = 0;
  70. /// <summary>
  71. /// 敌人属性
  72. /// </summary>
  73. public EnemyRoleState EnemyRoleState { get; private set; }
  74.  
  75. /// <summary>
  76. /// 敌人属性
  77. /// </summary>
  78. private ExcelConfig.EnemyBase _enemyAttribute;
  79.  
  80. private static bool _init = false;
  81. private static Dictionary<string, ExcelConfig.EnemyBase> _enemyAttributeMap =
  82. new Dictionary<string, ExcelConfig.EnemyBase>();
  83. /// <summary>
  84. /// 初始化敌人属性数据
  85. /// </summary>
  86. public static void InitEnemyAttribute()
  87. {
  88. if (_init)
  89. {
  90. return;
  91. }
  92.  
  93. _init = true;
  94. foreach (var enemyAttr in ExcelConfig.EnemyBase_List)
  95. {
  96. if (enemyAttr.Activity != null)
  97. {
  98. if (!_enemyAttributeMap.TryAdd(enemyAttr.Activity.Id, enemyAttr))
  99. {
  100. Debug.LogError("发现重复注册的敌人属性: " + enemyAttr.Id);
  101. }
  102. }
  103. }
  104. }
  105.  
  106. /// <summary>
  107. /// 根据 ActivityBase.Id 获取对应敌人的属性数据
  108. /// </summary>
  109. public static ExcelConfig.EnemyBase GetEnemyAttribute(string itemId)
  110. {
  111. if (itemId == null)
  112. {
  113. return null;
  114. }
  115. if (_enemyAttributeMap.TryGetValue(itemId, out var attr))
  116. {
  117. return attr;
  118. }
  119.  
  120. throw new Exception($"敌人'{itemId}'没有在 EnemyBase 表中配置属性数据!");
  121. }
  122. public override void OnInit()
  123. {
  124. base.OnInit();
  125. IsAi = true;
  126. StateController = AddComponent<StateController<Enemy, AIStateEnum>>();
  127.  
  128. AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Player;
  129. EnemyLayer = PhysicsLayer.Player;
  130. Camp = CampEnum.Camp2;
  131.  
  132. RoleState.MoveSpeed = 20;
  133.  
  134. MaxHp = 20;
  135. Hp = 20;
  136.  
  137. //注册Ai状态机
  138. StateController.Register(new AiNormalState());
  139. StateController.Register(new AiTailAfterState());
  140. StateController.Register(new AiFollowUpState());
  141. StateController.Register(new AiLeaveForState());
  142. StateController.Register(new AiSurroundState());
  143. StateController.Register(new AiFindAmmoState());
  144. StateController.Register(new AiAttackState());
  145. StateController.Register(new AiAstonishedState());
  146. StateController.Register(new AiNotifyState());
  147. //默认状态
  148. StateController.ChangeStateInstant(AIStateEnum.AiNormal);
  149. }
  150.  
  151. protected override RoleState OnCreateRoleState()
  152. {
  153. var roleState = new EnemyRoleState();
  154. EnemyRoleState = roleState;
  155. var enemyBase = GetEnemyAttribute(ActivityBase.Id).Clone();
  156. _enemyAttribute = enemyBase;
  157.  
  158. MaxHp = enemyBase.Hp;
  159. Hp = enemyBase.Hp;
  160. roleState.CanPickUpWeapon = enemyBase.CanPickUpWeapon;
  161. roleState.MoveSpeed = enemyBase.MoveSpeed;
  162. roleState.Acceleration = enemyBase.Acceleration;
  163. roleState.Friction = enemyBase.Friction;
  164. roleState.ViewRange = enemyBase.ViewRange;
  165. roleState.TailAfterViewRange = enemyBase.TailAfterViewRange;
  166. roleState.BackViewRange = enemyBase.BackViewRange;
  167. roleState.Gold = Mathf.Max(0, Utils.Random.RandomConfigRange(enemyBase.Gold));
  168. return roleState;
  169. }
  170.  
  171. public override void EnterTree()
  172. {
  173. if (!World.Enemy_InstanceList.Contains(this))
  174. {
  175. World.Enemy_InstanceList.Add(this);
  176. }
  177. }
  178.  
  179. public override void ExitTree()
  180. {
  181. World.Enemy_InstanceList.Remove(this);
  182. }
  183.  
  184. protected override void OnDie()
  185. {
  186. //扔掉所有武器
  187. ThrowAllWeapon();
  188.  
  189. var effPos = Position + new Vector2(0, -Altitude);
  190. //血液特效
  191. var blood = ObjectManager.GetPoolItem<AutoDestroyParticles>(ResourcePath.prefab_effect_enemy_EnemyBlood0001_tscn);
  192. blood.Position = effPos - new Vector2(0, 12);
  193. blood.AddToActivityRoot(RoomLayerEnum.NormalLayer);
  194. blood.PlayEffect();
  195.  
  196. var realVelocity = GetRealVelocity();
  197. //创建敌人碎片
  198. var count = Utils.Random.RandomRangeInt(3, 6);
  199. for (var i = 0; i < count; i++)
  200. {
  201. var debris = Create(Ids.Id_enemy_dead0001);
  202. debris.PutDown(effPos, RoomLayerEnum.NormalLayer);
  203. debris.MoveController.AddForce(Velocity + realVelocity);
  204. }
  205. //创建金币
  206. CreateGold();
  207. //派发敌人死亡信号
  208. EventManager.EmitEvent(EventEnum.OnEnemyDie, this);
  209. Destroy();
  210. }
  211.  
  212. protected override void Process(float delta)
  213. {
  214. base.Process(delta);
  215. if (IsDie)
  216. {
  217. return;
  218. }
  219. //看向目标
  220. if (LookTarget != null && MountLookTarget)
  221. {
  222. var pos = LookTarget.Position;
  223. LookPosition = pos;
  224. //脸的朝向
  225. var gPos = Position;
  226. if (pos.X > gPos.X && Face == FaceDirection.Left)
  227. {
  228. Face = FaceDirection.Right;
  229. }
  230. else if (pos.X < gPos.X && Face == FaceDirection.Right)
  231. {
  232. Face = FaceDirection.Left;
  233. }
  234. //枪口跟随目标
  235. MountPoint.SetLookAt(pos);
  236. }
  237.  
  238. if (EnemyRoleState.CanPickUpWeapon)
  239. {
  240. //拾起武器操作
  241. EnemyPickUpWeapon();
  242. }
  243. }
  244.  
  245. /// <summary>
  246. /// 创建散落的金币
  247. /// </summary>
  248. protected void CreateGold()
  249. {
  250. var goldList = Utils.GetGoldList(RoleState.Gold);
  251. foreach (var id in goldList)
  252. {
  253. var o = ObjectManager.GetActivityObject<Gold>(id);
  254. o.Position = Position;
  255. o.Throw(0,
  256. Utils.Random.RandomRangeInt(50, 110),
  257. new Vector2(Utils.Random.RandomRangeInt(-20, 20), Utils.Random.RandomRangeInt(-20, 20)),
  258. 0
  259. );
  260. }
  261. }
  262.  
  263. public override bool IsAllWeaponTotalAmmoEmpty()
  264. {
  265. if (!_enemyAttribute.CanPickUpWeapon)
  266. {
  267. return false;
  268. }
  269. return base.IsAllWeaponTotalAmmoEmpty();
  270. }
  271.  
  272. protected override void OnHit(ActivityObject target, int damage, float angle, bool realHarm)
  273. {
  274. //受到伤害
  275. var state = StateController.CurrState;
  276. if (state == AIStateEnum.AiNormal)
  277. {
  278. LookTarget = target;
  279. //判断是否进入通知状态
  280. if (World.Enemy_InstanceList.FindIndex(enemy =>
  281. enemy != this && !enemy.IsDie && enemy.AffiliationArea == AffiliationArea &&
  282. enemy.StateController.CurrState == AIStateEnum.AiNormal) != -1)
  283. {
  284. //进入惊讶状态, 然后再进入通知状态
  285. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify);
  286. }
  287. else
  288. {
  289. //进入惊讶状态, 然后再进入跟随状态
  290. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
  291. }
  292. }
  293. else if (state == AIStateEnum.AiLeaveFor)
  294. {
  295. LookTarget = target;
  296. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
  297. }
  298. else if (state == AIStateEnum.AiFindAmmo)
  299. {
  300. if (LookTarget == null)
  301. {
  302. LookTarget = target;
  303. var findAmmo = (AiFindAmmoState)StateController.CurrStateBase;
  304. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFindAmmo, findAmmo.TargetWeapon);
  305. }
  306. }
  307. }
  308.  
  309. /// <summary>
  310. /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
  311. /// </summary>
  312. public bool CheckUsableWeaponInUnclaimed()
  313. {
  314. foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons)
  315. {
  316. //判断是否能拾起武器, 条件: 相同的房间
  317. if (unclaimedWeapon.AffiliationArea == AffiliationArea)
  318. {
  319. if (!unclaimedWeapon.IsTotalAmmoEmpty())
  320. {
  321. if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
  322. {
  323. return true;
  324. }
  325. else
  326. {
  327. //判断是否可以移除该标记
  328. var enemy = unclaimedWeapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  329. if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  330. {
  331. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  332. return true;
  333. }
  334. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  335. {
  336. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  337. return true;
  338. }
  339. }
  340. }
  341. }
  342. }
  343.  
  344. return false;
  345. }
  346. /// <summary>
  347. /// 寻找可用的武器
  348. /// </summary>
  349. public Weapon FindTargetWeapon()
  350. {
  351. Weapon target = null;
  352. var position = Position;
  353. foreach (var weapon in World.Weapon_UnclaimedWeapons)
  354. {
  355. //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间
  356. if (weapon.AffiliationArea == AffiliationArea)
  357. {
  358. //还有弹药
  359. if (!weapon.IsTotalAmmoEmpty())
  360. {
  361. //查询是否有其他敌人标记要拾起该武器
  362. if (weapon.HasSign(SignNames.AiFindWeaponSign))
  363. {
  364. var enemy = weapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  365. if (enemy == this) //就是自己标记的
  366. {
  367.  
  368. }
  369. else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  370. {
  371. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  372. }
  373. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  374. {
  375. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  376. }
  377. else //放弃这把武器
  378. {
  379. continue;
  380. }
  381. }
  382.  
  383. if (target == null) //第一把武器
  384. {
  385. target = weapon;
  386. }
  387. else if (target.Position.DistanceSquaredTo(position) >
  388. weapon.Position.DistanceSquaredTo(position)) //距离更近
  389. {
  390. target = weapon;
  391. }
  392. }
  393. }
  394. }
  395.  
  396. return target;
  397. }
  398.  
  399. /// <summary>
  400. /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
  401. /// </summary>
  402. /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param>
  403. public float GetWeaponRange(float weight = 0.5f)
  404. {
  405. if (WeaponPack.ActiveItem != null)
  406. {
  407. var attribute = WeaponPack.ActiveItem.Attribute;
  408. return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight);
  409. }
  410.  
  411. return 0;
  412. }
  413.  
  414. /// <summary>
  415. /// 返回目标点是否在视野范围内
  416. /// </summary>
  417. public bool IsInViewRange(Vector2 target)
  418. {
  419. var isForward = IsPositionInForward(target);
  420. if (isForward)
  421. {
  422. if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.ViewRange * EnemyRoleState.ViewRange) //没有超出视野半径
  423. {
  424. return true;
  425. }
  426. }
  427.  
  428. return false;
  429. }
  430.  
  431. /// <summary>
  432. /// 返回目标点是否在跟随状态下的视野半径内
  433. /// </summary>
  434. public bool IsInTailAfterViewRange(Vector2 target)
  435. {
  436. var isForward = IsPositionInForward(target);
  437. if (isForward)
  438. {
  439. if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.TailAfterViewRange * EnemyRoleState.TailAfterViewRange) //没有超出视野半径
  440. {
  441. return true;
  442. }
  443. }
  444.  
  445. return false;
  446. }
  447.  
  448. /// <summary>
  449. /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true
  450. /// </summary>
  451. public bool TestViewRayCast(Vector2 target)
  452. {
  453. ViewRay.Enabled = true;
  454. ViewRay.TargetPosition = ViewRay.ToLocal(target);
  455. ViewRay.ForceRaycastUpdate();
  456. return ViewRay.IsColliding();
  457. }
  458.  
  459. /// <summary>
  460. /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
  461. /// </summary>
  462. public void TestViewRayCastOver()
  463. {
  464. ViewRay.Enabled = false;
  465. }
  466.  
  467. /// <summary>
  468. /// AI 拾起武器操作
  469. /// </summary>
  470. private void EnemyPickUpWeapon()
  471. {
  472. //这几个状态不需要主动拾起武器操作
  473. var state = StateController.CurrState;
  474. if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiNotify || state == AIStateEnum.AiAstonished || state == AIStateEnum.AiAttack)
  475. {
  476. return;
  477. }
  478. //拾起地上的武器
  479. if (InteractiveItem is Weapon weapon)
  480. {
  481. if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起
  482. {
  483. TriggerInteractive();
  484. return;
  485. }
  486.  
  487. //没弹药了
  488. if (weapon.IsTotalAmmoEmpty())
  489. {
  490. return;
  491. }
  492. var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id);
  493. if (index != -1) //与武器背包中武器类型相同, 补充子弹
  494. {
  495. if (!WeaponPack.GetItem(index).IsAmmoFull())
  496. {
  497. TriggerInteractive();
  498. }
  499.  
  500. return;
  501. }
  502.  
  503. // var index2 = Holster.FindWeapon((we, i) =>
  504. // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
  505. var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty());
  506. if (index2 != -1) //扔掉没子弹的武器
  507. {
  508. ThrowWeapon(index2);
  509. TriggerInteractive();
  510. return;
  511. }
  512. // if (Holster.HasVacancy()) //有空位, 拾起武器
  513. // {
  514. // TriggerInteractive();
  515. // return;
  516. // }
  517. }
  518. }
  519. /// <summary>
  520. /// 获取锁定目标的剩余时间
  521. /// </summary>
  522. public float GetLockRemainderTime()
  523. {
  524. var weapon = WeaponPack.ActiveItem;
  525. if (weapon == null)
  526. {
  527. return LockingTime - LockTargetTime;
  528. }
  529. return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime;
  530. }
  531.  
  532. public override void LookTargetPosition(Vector2 pos)
  533. {
  534. LookTarget = null;
  535. base.LookTargetPosition(pos);
  536. }
  537. /// <summary>
  538. /// 执行移动操作
  539. /// </summary>
  540. public void DoMove()
  541. {
  542. AnimatedSprite.Play(AnimatorNames.Run);
  543. //计算移动
  544. var nextPos = NavigationAgent2D.GetNextPathPosition();
  545. BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
  546. }
  547.  
  548. /// <summary>
  549. /// 执行站立操作
  550. /// </summary>
  551. public void DoIdle()
  552. {
  553. AnimatedSprite.Play(AnimatorNames.Idle);
  554. BasisVelocity = Vector2.Zero;
  555. }
  556. /// <summary>
  557. /// 更新房间中标记的目标位置
  558. /// </summary>
  559. public void UpdateMarkTargetPosition()
  560. {
  561. if (LookTarget != null)
  562. {
  563. AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position;
  564. }
  565. }
  566.  
  567. /// <summary>
  568. /// 从标记出生时调用, 预加载波不会调用
  569. /// </summary>
  570. public virtual void OnBornFromMark()
  571. {
  572. //罚站 0.7 秒
  573. StateController.Enable = false;
  574. this.CallDelay(0.7f, () => StateController.Enable = true);
  575. }
  576. }