Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / 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.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. protected override void PhysicsProcess(float delta)
  139. {
  140. base.PhysicsProcess(delta);
  141. _enemyAttackTimer -= delta;
  142.  
  143. EnemyPickUpWeapon();
  144. }
  145.  
  146. protected override void OnHit(int damage)
  147. {
  148. //受到伤害
  149. var state = StateController.CurrState;
  150. if (state == AiStateEnum.AiNormal || state == AiStateEnum.AiProbe || state == AiStateEnum.AiLeaveFor)
  151. {
  152. StateController.ChangeStateLate(AiStateEnum.AiTailAfter);
  153. }
  154. }
  155.  
  156. /// <summary>
  157. /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
  158. /// </summary>
  159. public bool CheckUsableWeaponInUnclaimed()
  160. {
  161. //如果存在有子弹的武器
  162. foreach (var unclaimedWeapon in Weapon.UnclaimedWeapons)
  163. {
  164. if (!unclaimedWeapon.IsTotalAmmoEmpty() && !unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
  165. {
  166. return true;
  167. }
  168. }
  169.  
  170. return false;
  171. }
  172.  
  173. /// <summary>
  174. /// 更新敌人视野
  175. /// </summary>
  176. public static void UpdateEnemiesView()
  177. {
  178. IsFindTarget = false;
  179. for (var i = 0; i < _enemies.Count; i++)
  180. {
  181. var enemy = _enemies[i];
  182. var state = enemy.StateController.CurrState;
  183. if (state == AiStateEnum.AiFollowUp || state == AiStateEnum.AiSurround) //目标在视野内
  184. {
  185. IsFindTarget = true;
  186. FindTargetPosition = Player.Current.GetCenterPosition();
  187. }
  188. }
  189. }
  190.  
  191. /// <summary>
  192. /// Ai触发的攻击
  193. /// </summary>
  194. public void EnemyAttack()
  195. {
  196. var weapon = Holster.ActiveWeapon;
  197. if (weapon != null)
  198. {
  199. if (weapon.IsTotalAmmoEmpty()) //当前武器弹药打空
  200. {
  201. //切换到有子弹的武器
  202. var index = Holster.FindWeapon((we, i) => !we.IsTotalAmmoEmpty());
  203. if (index != -1)
  204. {
  205. Holster.ExchangeByIndex(index);
  206. }
  207. else //所有子弹打光
  208. {
  209. }
  210. }
  211. else if (weapon.Reloading) //换弹中
  212. {
  213.  
  214. }
  215. else if (weapon.IsAmmoEmpty()) //弹夹已经打空
  216. {
  217. Reload();
  218. }
  219. else //正常射击
  220. {
  221. if (weapon.Attribute.ContinuousShoot) //连发
  222. {
  223. Attack();
  224. }
  225. else //单发
  226. {
  227. if (_enemyAttackTimer <= 0)
  228. {
  229. _enemyAttackTimer = 60f / weapon.Attribute.StartFiringSpeed + Utils.RandRange(0, 0.06f);
  230. Attack();
  231. }
  232. }
  233. }
  234. }
  235. }
  236.  
  237. /// <summary>
  238. /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
  239. /// </summary>
  240. /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param>
  241. public float GetWeaponRange(float weight = 0.5f)
  242. {
  243. if (Holster.ActiveWeapon != null)
  244. {
  245. var attribute = Holster.ActiveWeapon.Attribute;
  246. return Mathf.Lerp(attribute.MinDistance, attribute.MaxDistance, weight);
  247. }
  248.  
  249. return 0;
  250. }
  251.  
  252. /// <summary>
  253. /// 返回目标点是否在视野范围内
  254. /// </summary>
  255. public bool IsInViewRange(Vector2 target)
  256. {
  257. var isForward = IsPositionInForward(target);
  258. if (isForward)
  259. {
  260. if (GlobalPosition.DistanceSquaredTo(target) <= ViewRange * ViewRange) //没有超出视野半径
  261. {
  262. return true;
  263. }
  264. }
  265.  
  266. return false;
  267. }
  268.  
  269. /// <summary>
  270. /// 返回目标点是否在跟随状态下的视野半径内
  271. /// </summary>
  272. public bool IsInTailAfterViewRange(Vector2 target)
  273. {
  274. var isForward = IsPositionInForward(target);
  275. if (isForward)
  276. {
  277. if (GlobalPosition.DistanceSquaredTo(target) <= TailAfterViewRange * TailAfterViewRange) //没有超出视野半径
  278. {
  279. return true;
  280. }
  281. }
  282.  
  283. return false;
  284. }
  285.  
  286. /// <summary>
  287. /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回被挡住视野的物体对象, 视野无阻则返回 null
  288. /// </summary>
  289. public bool TestViewRayCast(Vector2 target)
  290. {
  291. ViewRay.Enabled = true;
  292. ViewRay.CastTo = ViewRay.ToLocal(target);
  293. ViewRay.ForceRaycastUpdate();
  294. return ViewRay.IsColliding();
  295. }
  296.  
  297. /// <summary>
  298. /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
  299. /// </summary>
  300. public void TestViewRayCastOver()
  301. {
  302. ViewRay.Enabled = false;
  303. }
  304.  
  305. /// <summary>
  306. /// AI 拾起武器操作
  307. /// </summary>
  308. private void EnemyPickUpWeapon()
  309. {
  310. //这几个状态不需要主动拾起武器操作
  311. var state = StateController.CurrState;
  312. if (state == AiStateEnum.AiNormal)
  313. {
  314. return;
  315. }
  316. //拾起地上的武器
  317. if (InteractiveItem is Weapon weapon)
  318. {
  319. if (Holster.ActiveWeapon == null) //手上没有武器, 无论如何也要拾起
  320. {
  321. TriggerInteractive();
  322. return;
  323. }
  324.  
  325. //没弹药了
  326. if (weapon.IsTotalAmmoEmpty())
  327. {
  328. return;
  329. }
  330. var index = Holster.FindWeapon((we, i) => we.TypeId == weapon.TypeId);
  331. if (index != -1) //与武器袋中武器类型相同, 补充子弹
  332. {
  333. if (!Holster.GetWeapon(index).IsAmmoFull())
  334. {
  335. TriggerInteractive();
  336. }
  337.  
  338. return;
  339. }
  340.  
  341. var index2 = Holster.FindWeapon((we, i) =>
  342. we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
  343. if (index2 != -1) //扔掉没子弹的武器
  344. {
  345. ThrowWeapon(index2);
  346. TriggerInteractive();
  347. return;
  348. }
  349. if (Holster.HasVacancy()) //有空位, 拾起武器
  350. {
  351. TriggerInteractive();
  352. return;
  353. }
  354. }
  355. }
  356.  
  357. }