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