Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / role / enemy / Enemy.cs
@小李xl 小李xl on 15 Feb 2023 11 KB 协程嵌套bug修复, ai射击优化
  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. public partial class Enemy : Role
  21. {
  22.  
  23. /// <summary>
  24. /// 公共属性, 是否找到目标, 如果找到目标, 则所有敌人都会知道玩家的位置
  25. /// </summary>
  26. public static bool IsFindTarget { get; private set; }
  27.  
  28. /// <summary>
  29. /// 公共属性, 找到的目标的位置, 如果目标在视野内, 则一直更新
  30. /// </summary>
  31. public static Vector2 FindTargetPosition { get; private set; }
  32.  
  33. private static readonly List<Enemy> _enemies = new List<Enemy>();
  34.  
  35. /// <summary>
  36. /// 敌人身上的状态机控制器
  37. /// </summary>
  38. public StateController<Enemy, AiStateEnum> StateController { get; }
  39.  
  40. /// <summary>
  41. /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
  42. /// </summary>
  43. public float ViewRange { get; set; } = 250;
  44.  
  45. /// <summary>
  46. /// 发现玩家后的视野半径
  47. /// </summary>
  48. public float TailAfterViewRange { get; set; } = 400;
  49.  
  50. /// <summary>
  51. /// 背后的视野半径, 单位像素
  52. /// </summary>
  53. public float BackViewRange { get; set; } = 50;
  54.  
  55. /// <summary>
  56. /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙
  57. /// </summary>
  58. public RayCast2D ViewRay { get; }
  59.  
  60. /// <summary>
  61. /// 导航代理
  62. /// </summary>
  63. public NavigationAgent2D NavigationAgent2D { get; }
  64.  
  65. /// <summary>
  66. /// 导航代理中点
  67. /// </summary>
  68. public Marker2D NavigationPoint { get; }
  69.  
  70. //开火间隙时间
  71. private float _enemyAttackTimer = 0;
  72. //目标在视野内的时间
  73. private float _targetInViewTime = 0;
  74.  
  75. public Enemy() : base(ResourcePath.prefab_role_Enemy_tscn)
  76. {
  77. IsAi = true;
  78. StateController = AddComponent<StateController<Enemy, AiStateEnum>>();
  79.  
  80. AttackLayer = PhysicsLayer.Wall | PhysicsLayer.Props | PhysicsLayer.Player;
  81. Camp = CampEnum.Camp2;
  82.  
  83. MoveSpeed = 30;
  84.  
  85. Holster.SlotList[2].Enable = true;
  86. Holster.SlotList[3].Enable = true;
  87. MaxHp = 20;
  88. Hp = 20;
  89.  
  90. //视野射线
  91. ViewRay = GetNode<RayCast2D>("ViewRay");
  92. NavigationPoint = GetNode<Marker2D>("NavigationPoint");
  93. NavigationAgent2D = NavigationPoint.GetNode<NavigationAgent2D>("NavigationAgent2D");
  94.  
  95. //PathSign = new PathSign(this, PathSignLength, GameApplication.Instance.Node3D.Player);
  96.  
  97. //注册Ai状态机
  98. StateController.Register(new AiNormalState());
  99. StateController.Register(new AiProbeState());
  100. StateController.Register(new AiTailAfterState());
  101. StateController.Register(new AiFollowUpState());
  102. StateController.Register(new AiLeaveForState());
  103. StateController.Register(new AiSurroundState());
  104. StateController.Register(new AiFindAmmoState());
  105. }
  106.  
  107. public override void _Ready()
  108. {
  109. base._Ready();
  110. //默认状态
  111. StateController.ChangeState(AiStateEnum.AiNormal);
  112.  
  113. NavigationAgent2D.TargetPosition = GameApplication.Instance.RoomManager.Player.GlobalPosition;
  114. }
  115.  
  116. public override void _EnterTree()
  117. {
  118. if (!_enemies.Contains(this))
  119. {
  120. _enemies.Add(this);
  121. }
  122. }
  123.  
  124. public override void _ExitTree()
  125. {
  126. base._ExitTree();
  127. _enemies.Remove(this);
  128. }
  129.  
  130. protected override void OnDie()
  131. {
  132. //扔掉所有武器
  133. var weapons = Holster.GetAndClearWeapon();
  134. for (var i = 0; i < weapons.Length; i++)
  135. {
  136. weapons[i].ThrowWeapon(this);
  137. }
  138. Destroy();
  139. }
  140.  
  141. protected override void Process(float delta)
  142. {
  143. base.Process(delta);
  144. _enemyAttackTimer -= delta;
  145.  
  146. //目标在视野内的时间
  147. var currState = StateController.CurrState;
  148. if (currState == AiStateEnum.AiSurround || currState == AiStateEnum.AiFollowUp)
  149. {
  150. _targetInViewTime += delta;
  151. }
  152. else
  153. {
  154. _targetInViewTime = 0;
  155. }
  156.  
  157. EnemyPickUpWeapon();
  158. }
  159.  
  160. protected override void OnHit(int damage)
  161. {
  162. //受到伤害
  163. var state = StateController.CurrState;
  164. if (state == AiStateEnum.AiNormal || state == AiStateEnum.AiProbe || state == AiStateEnum.AiLeaveFor)
  165. {
  166. StateController.ChangeStateLate(AiStateEnum.AiTailAfter);
  167. }
  168. }
  169.  
  170. /// <summary>
  171. /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
  172. /// </summary>
  173. public bool CheckUsableWeaponInUnclaimed()
  174. {
  175. //如果存在有子弹的武器
  176. foreach (var unclaimedWeapon in Weapon.UnclaimedWeapons)
  177. {
  178. if (!unclaimedWeapon.IsTotalAmmoEmpty() && !unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
  179. {
  180. return true;
  181. }
  182. }
  183.  
  184. return false;
  185. }
  186.  
  187. /// <summary>
  188. /// 更新敌人视野
  189. /// </summary>
  190. public static void UpdateEnemiesView()
  191. {
  192. IsFindTarget = false;
  193. for (var i = 0; i < _enemies.Count; i++)
  194. {
  195. var enemy = _enemies[i];
  196. var state = enemy.StateController.CurrState;
  197. if (state == AiStateEnum.AiFollowUp || state == AiStateEnum.AiSurround) //目标在视野内
  198. {
  199. IsFindTarget = true;
  200. FindTargetPosition = Player.Current.GetCenterPosition();
  201. }
  202. }
  203. }
  204.  
  205. /// <summary>
  206. /// Ai触发的攻击
  207. /// </summary>
  208. public void EnemyAttack(float delta)
  209. {
  210. var weapon = Holster.ActiveWeapon;
  211. if (weapon != null)
  212. {
  213. if (weapon.IsTotalAmmoEmpty()) //当前武器弹药打空
  214. {
  215. //切换到有子弹的武器
  216. var index = Holster.FindWeapon((we, i) => !we.IsTotalAmmoEmpty());
  217. if (index != -1)
  218. {
  219. Holster.ExchangeByIndex(index);
  220. }
  221. else //所有子弹打光
  222. {
  223. }
  224. }
  225. else if (weapon.Reloading) //换弹中
  226. {
  227.  
  228. }
  229. else if (weapon.IsAmmoEmpty()) //弹夹已经打空
  230. {
  231. Reload();
  232. }
  233. else if (_targetInViewTime >= weapon.Attribute.AiTargetLockingTime) //正常射击
  234. {
  235. if (weapon.GetDelayedAttackTime() > 0)
  236. {
  237. Attack();
  238. }
  239. else
  240. {
  241. if (weapon.Attribute.ContinuousShoot) //连发
  242. {
  243. Attack();
  244. }
  245. else //单发
  246. {
  247. if (_enemyAttackTimer <= 0)
  248. {
  249. _enemyAttackTimer = 60f / weapon.Attribute.StartFiringSpeed;
  250. Attack();
  251. }
  252. }
  253. }
  254. }
  255. }
  256. }
  257.  
  258. /// <summary>
  259. /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
  260. /// </summary>
  261. /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param>
  262. public float GetWeaponRange(float weight = 0.5f)
  263. {
  264. if (Holster.ActiveWeapon != null)
  265. {
  266. var attribute = Holster.ActiveWeapon.Attribute;
  267. return Mathf.Lerp(attribute.MinDistance, attribute.MaxDistance, weight);
  268. }
  269.  
  270. return 0;
  271. }
  272.  
  273. /// <summary>
  274. /// 返回目标点是否在视野范围内
  275. /// </summary>
  276. public bool IsInViewRange(Vector2 target)
  277. {
  278. var isForward = IsPositionInForward(target);
  279. if (isForward)
  280. {
  281. if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径
  282. {
  283. return true;
  284. }
  285. }
  286.  
  287. return false;
  288. }
  289.  
  290. /// <summary>
  291. /// 返回目标点是否在跟随状态下的视野半径内
  292. /// </summary>
  293. public bool IsInTailAfterViewRange(Vector2 target)
  294. {
  295. var isForward = IsPositionInForward(target);
  296. if (isForward)
  297. {
  298. if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径
  299. {
  300. return true;
  301. }
  302. }
  303.  
  304. return false;
  305. }
  306.  
  307. /// <summary>
  308. /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null
  309. /// </summary>
  310. public bool TestViewRayCast(Vector2 target)
  311. {
  312. ViewRay.Enabled = true;
  313. ViewRay.TargetPosition = ViewRay.ToLocal(target);
  314. ViewRay.ForceRaycastUpdate();
  315. return ViewRay.IsColliding();
  316. }
  317.  
  318. /// <summary>
  319. /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
  320. /// </summary>
  321. public void TestViewRayCastOver()
  322. {
  323. ViewRay.Enabled = false;
  324. }
  325.  
  326. /// <summary>
  327. /// AI 拾起武器操作
  328. /// </summary>
  329. private void EnemyPickUpWeapon()
  330. {
  331. //这几个状态不需要主动拾起武器操作
  332. var state = StateController.CurrState;
  333. if (state == AiStateEnum.AiNormal)
  334. {
  335. return;
  336. }
  337. //拾起地上的武器
  338. if (InteractiveItem is Weapon weapon)
  339. {
  340. if (Holster.ActiveWeapon == null) //手上没有武器, 无论如何也要拾起
  341. {
  342. TriggerInteractive();
  343. return;
  344. }
  345.  
  346. //没弹药了
  347. if (weapon.IsTotalAmmoEmpty())
  348. {
  349. return;
  350. }
  351. var index = Holster.FindWeapon((we, i) => we.TypeId == weapon.TypeId);
  352. if (index != -1) //与武器袋中武器类型相同, 补充子弹
  353. {
  354. if (!Holster.GetWeapon(index).IsAmmoFull())
  355. {
  356. TriggerInteractive();
  357. }
  358.  
  359. return;
  360. }
  361.  
  362. // var index2 = Holster.FindWeapon((we, i) =>
  363. // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
  364. var index2 = Holster.FindWeapon((we, i) => we.IsTotalAmmoEmpty());
  365. if (index2 != -1) //扔掉没子弹的武器
  366. {
  367. ThrowWeapon(index2);
  368. TriggerInteractive();
  369. return;
  370. }
  371. // if (Holster.HasVacancy()) //有空位, 拾起武器
  372. // {
  373. // TriggerInteractive();
  374. // return;
  375. // }
  376. }
  377. }
  378.  
  379. }