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