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