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