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