Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / activity / role / enemy / Enemy.cs
  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.Gold = Mathf.Max(0, Utils.Random.RandomConfigRange(enemyBase.Gold));
  158. return roleState;
  159. }
  160.  
  161. public override void EnterTree()
  162. {
  163. if (!World.Enemy_InstanceList.Contains(this))
  164. {
  165. World.Enemy_InstanceList.Add(this);
  166. }
  167. }
  168.  
  169. public override void ExitTree()
  170. {
  171. World.Enemy_InstanceList.Remove(this);
  172. }
  173.  
  174. protected override void OnDie()
  175. {
  176. //扔掉所有武器
  177. ThrowAllWeapon();
  178.  
  179. var effPos = Position + new Vector2(0, -Altitude);
  180. //血液特效
  181. var blood = ObjectManager.GetPoolItem<AutoDestroyParticles>(ResourcePath.prefab_effect_enemy_EnemyBlood0001_tscn);
  182. blood.Position = effPos - new Vector2(0, 12);
  183. blood.AddToActivityRoot(RoomLayerEnum.NormalLayer);
  184. blood.PlayEffect();
  185. var realVelocity = GetRealVelocity();
  186. //创建敌人碎片
  187. var count = Utils.Random.RandomRangeInt(3, 6);
  188. for (var i = 0; i < count; i++)
  189. {
  190. var debris = Create(Ids.Id_enemy_dead0001);
  191. debris.PutDown(effPos, RoomLayerEnum.NormalLayer);
  192. debris.MoveController.AddForce(Velocity + realVelocity);
  193. }
  194. //创建金币
  195. CreateGold();
  196. //派发敌人死亡信号
  197. EventManager.EmitEvent(EventEnum.OnEnemyDie, this);
  198. Destroy();
  199. }
  200.  
  201. protected override void Process(float delta)
  202. {
  203. base.Process(delta);
  204. if (IsDie)
  205. {
  206. return;
  207. }
  208. //看向目标
  209. if (LookTarget != null && MountLookTarget)
  210. {
  211. var pos = LookTarget.Position;
  212. LookPosition = pos;
  213. //脸的朝向
  214. var gPos = Position;
  215. if (pos.X > gPos.X && Face == FaceDirection.Left)
  216. {
  217. Face = FaceDirection.Right;
  218. }
  219. else if (pos.X < gPos.X && Face == FaceDirection.Right)
  220. {
  221. Face = FaceDirection.Left;
  222. }
  223. //枪口跟随目标
  224. MountPoint.SetLookAt(pos);
  225. }
  226.  
  227. if (EnemyRoleState.CanPickUpWeapon)
  228. {
  229. //拾起武器操作
  230. EnemyPickUpWeapon();
  231. }
  232. }
  233.  
  234. /// <summary>
  235. /// 创建散落的金币
  236. /// </summary>
  237. protected void CreateGold()
  238. {
  239. var goldList = Utils.GetGoldList(RoleState.Gold);
  240. foreach (var id in goldList)
  241. {
  242. var o = ObjectManager.GetActivityObject<Gold>(id);
  243. o.Position = Position;
  244. o.Throw(0,
  245. Utils.Random.RandomRangeInt(50, 110),
  246. new Vector2(Utils.Random.RandomRangeInt(-20, 20), Utils.Random.RandomRangeInt(-20, 20)),
  247. 0
  248. );
  249. }
  250. }
  251.  
  252. public override bool IsAllWeaponTotalAmmoEmpty()
  253. {
  254. if (!_enemyAttribute.CanPickUpWeapon)
  255. {
  256. return false;
  257. }
  258. return base.IsAllWeaponTotalAmmoEmpty();
  259. }
  260.  
  261. protected override void OnHit(ActivityObject target, int damage, float angle, bool realHarm)
  262. {
  263. //受到伤害
  264. var state = StateController.CurrState;
  265. if (state == AIStateEnum.AiNormal)
  266. {
  267. LookTarget = target;
  268. //判断是否进入通知状态
  269. if (World.Enemy_InstanceList.FindIndex(enemy =>
  270. enemy != this && !enemy.IsDie && enemy.AffiliationArea == AffiliationArea &&
  271. enemy.StateController.CurrState == AIStateEnum.AiNormal) != -1)
  272. {
  273. //进入惊讶状态, 然后再进入通知状态
  274. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify);
  275. }
  276. else
  277. {
  278. //进入惊讶状态, 然后再进入跟随状态
  279. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
  280. }
  281. }
  282. else if (state == AIStateEnum.AiLeaveFor)
  283. {
  284. LookTarget = target;
  285. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
  286. }
  287. else if (state == AIStateEnum.AiFindAmmo)
  288. {
  289. if (LookTarget == null)
  290. {
  291. LookTarget = target;
  292. var findAmmo = (AiFindAmmoState)StateController.CurrStateBase;
  293. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFindAmmo, findAmmo.TargetWeapon);
  294. }
  295. }
  296. }
  297.  
  298. /// <summary>
  299. /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
  300. /// </summary>
  301. public bool CheckUsableWeaponInUnclaimed()
  302. {
  303. foreach (var unclaimedWeapon in World.Weapon_UnclaimedWeapons)
  304. {
  305. //判断是否能拾起武器, 条件: 相同的房间
  306. if (unclaimedWeapon.AffiliationArea == AffiliationArea)
  307. {
  308. if (!unclaimedWeapon.IsTotalAmmoEmpty())
  309. {
  310. if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
  311. {
  312. return true;
  313. }
  314. else
  315. {
  316. //判断是否可以移除该标记
  317. var enemy = unclaimedWeapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  318. if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  319. {
  320. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  321. return true;
  322. }
  323. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  324. {
  325. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  326. return true;
  327. }
  328. }
  329. }
  330. }
  331. }
  332.  
  333. return false;
  334. }
  335. /// <summary>
  336. /// 寻找可用的武器
  337. /// </summary>
  338. public Weapon FindTargetWeapon()
  339. {
  340. Weapon target = null;
  341. var position = Position;
  342. foreach (var weapon in World.Weapon_UnclaimedWeapons)
  343. {
  344. //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间
  345. if (weapon.AffiliationArea == AffiliationArea)
  346. {
  347. //还有弹药
  348. if (!weapon.IsTotalAmmoEmpty())
  349. {
  350. //查询是否有其他敌人标记要拾起该武器
  351. if (weapon.HasSign(SignNames.AiFindWeaponSign))
  352. {
  353. var enemy = weapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  354. if (enemy == this) //就是自己标记的
  355. {
  356.  
  357. }
  358. else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  359. {
  360. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  361. }
  362. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  363. {
  364. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  365. }
  366. else //放弃这把武器
  367. {
  368. continue;
  369. }
  370. }
  371.  
  372. if (target == null) //第一把武器
  373. {
  374. target = weapon;
  375. }
  376. else if (target.Position.DistanceSquaredTo(position) >
  377. weapon.Position.DistanceSquaredTo(position)) //距离更近
  378. {
  379. target = weapon;
  380. }
  381. }
  382. }
  383. }
  384.  
  385. return target;
  386. }
  387.  
  388. /// <summary>
  389. /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
  390. /// </summary>
  391. /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param>
  392. public float GetWeaponRange(float weight = 0.5f)
  393. {
  394. if (WeaponPack.ActiveItem != null)
  395. {
  396. var attribute = WeaponPack.ActiveItem.Attribute;
  397. return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight);
  398. }
  399.  
  400. return 0;
  401. }
  402.  
  403. /// <summary>
  404. /// 返回目标点是否在视野范围内
  405. /// </summary>
  406. public bool IsInViewRange(Vector2 target)
  407. {
  408. var isForward = IsPositionInForward(target);
  409. if (isForward)
  410. {
  411. if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.ViewRange * EnemyRoleState.ViewRange) //没有超出视野半径
  412. {
  413. return true;
  414. }
  415. }
  416.  
  417. return false;
  418. }
  419.  
  420. /// <summary>
  421. /// 返回目标点是否在跟随状态下的视野半径内
  422. /// </summary>
  423. public bool IsInTailAfterViewRange(Vector2 target)
  424. {
  425. var isForward = IsPositionInForward(target);
  426. if (isForward)
  427. {
  428. if (GlobalPosition.DistanceSquaredTo(target) <= EnemyRoleState.TailAfterViewRange * EnemyRoleState.TailAfterViewRange) //没有超出视野半径
  429. {
  430. return true;
  431. }
  432. }
  433.  
  434. return false;
  435. }
  436.  
  437. /// <summary>
  438. /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true
  439. /// </summary>
  440. public bool TestViewRayCast(Vector2 target)
  441. {
  442. ViewRay.Enabled = true;
  443. ViewRay.TargetPosition = ViewRay.ToLocal(target);
  444. ViewRay.ForceRaycastUpdate();
  445. return ViewRay.IsColliding();
  446. }
  447.  
  448. /// <summary>
  449. /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
  450. /// </summary>
  451. public void TestViewRayCastOver()
  452. {
  453. ViewRay.Enabled = false;
  454. }
  455.  
  456. /// <summary>
  457. /// AI 拾起武器操作
  458. /// </summary>
  459. private void EnemyPickUpWeapon()
  460. {
  461. //这几个状态不需要主动拾起武器操作
  462. var state = StateController.CurrState;
  463. if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiNotify || state == AIStateEnum.AiAstonished || state == AIStateEnum.AiAttack)
  464. {
  465. return;
  466. }
  467. //拾起地上的武器
  468. if (InteractiveItem is Weapon weapon)
  469. {
  470. if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起
  471. {
  472. TriggerInteractive();
  473. return;
  474. }
  475.  
  476. //没弹药了
  477. if (weapon.IsTotalAmmoEmpty())
  478. {
  479. return;
  480. }
  481. var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id);
  482. if (index != -1) //与武器背包中武器类型相同, 补充子弹
  483. {
  484. if (!WeaponPack.GetItem(index).IsAmmoFull())
  485. {
  486. TriggerInteractive();
  487. }
  488.  
  489. return;
  490. }
  491.  
  492. // var index2 = Holster.FindWeapon((we, i) =>
  493. // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
  494. var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty());
  495. if (index2 != -1) //扔掉没子弹的武器
  496. {
  497. ThrowWeapon(index2);
  498. TriggerInteractive();
  499. return;
  500. }
  501. // if (Holster.HasVacancy()) //有空位, 拾起武器
  502. // {
  503. // TriggerInteractive();
  504. // return;
  505. // }
  506. }
  507. }
  508. /// <summary>
  509. /// 获取锁定目标的剩余时间
  510. /// </summary>
  511. public float GetLockRemainderTime()
  512. {
  513. var weapon = WeaponPack.ActiveItem;
  514. if (weapon == null)
  515. {
  516. return LockingTime - LockTargetTime;
  517. }
  518. return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime;
  519. }
  520.  
  521. public override void LookTargetPosition(Vector2 pos)
  522. {
  523. LookTarget = null;
  524. base.LookTargetPosition(pos);
  525. }
  526. /// <summary>
  527. /// 执行移动操作
  528. /// </summary>
  529. public void DoMove()
  530. {
  531. // //计算移动
  532. // NavigationAgent2D.MaxSpeed = EnemyRoleState.MoveSpeed;
  533. // var nextPos = NavigationAgent2D.GetNextPathPosition();
  534. // NavigationAgent2D.Velocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
  535. AnimatedSprite.Play(AnimatorNames.Run);
  536. //计算移动
  537. var nextPos = NavigationAgent2D.GetNextPathPosition();
  538. BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
  539. }
  540.  
  541. /// <summary>
  542. /// 执行站立操作
  543. /// </summary>
  544. public void DoIdle()
  545. {
  546. AnimatedSprite.Play(AnimatorNames.Idle);
  547. BasisVelocity = Vector2.Zero;
  548. }
  549. /// <summary>
  550. /// 更新房间中标记的目标位置
  551. /// </summary>
  552. public void UpdateMarkTargetPosition()
  553. {
  554. if (LookTarget != null)
  555. {
  556. AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position;
  557. }
  558. }
  559.  
  560. /// <summary>
  561. /// 从标记出生时调用, 预加载波不会调用
  562. /// </summary>
  563. public virtual void OnBornFromMark()
  564. {
  565. //罚站 0.7 秒
  566. StateController.Enable = false;
  567. this.CallDelay(0.7f, () => StateController.Enable = true);
  568. }
  569.  
  570. // private void OnVelocityComputed(Vector2 velocity)
  571. // {
  572. // if (Mathf.Abs(velocity.X) >= 0.01f && Mathf.Abs(velocity.Y) >= 0.01f)
  573. // {
  574. // AnimatedSprite.Play(AnimatorNames.Run);
  575. // BasisVelocity = velocity;
  576. // }
  577. // }
  578. }