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