Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / activity / role / ai / AiRole.cs
@小李xl 小李xl on 10 Apr 2024 19 KB 继续完善Ai改变阵营逻辑
  1.  
  2. using System;
  3. using System.Collections.Generic;
  4. using AiState;
  5. using Godot;
  6.  
  7. /// <summary>
  8. /// Ai角色
  9. /// </summary>
  10. public abstract partial class AiRole : Role
  11. {
  12. /// <summary>
  13. /// 目标是否在视野内, 视野内不能有墙壁遮挡
  14. /// </summary>
  15. public bool TargetInView { get; private set; } = false;
  16.  
  17. /// <summary>
  18. /// 目标间是否有墙壁遮挡
  19. /// </summary>
  20. public bool TargetHasOcclusion { get; private set; } = false;
  21. /// <summary>
  22. /// 目标是否在视野范围内, 不会考虑是否被枪遮挡
  23. /// </summary>
  24. public bool TargetInViewRange { get; private set; } = false;
  25. /// <summary>
  26. /// 敌人身上的状态机控制器
  27. /// </summary>
  28. public StateController<AiRole, AIStateEnum> StateController { get; private set; }
  29. /// <summary>
  30. /// 视野检测射线, 朝玩家打射线, 检测是否碰到墙
  31. /// </summary>
  32. [Export, ExportFillNode]
  33. public RayCast2D ViewRay { get; set; }
  34.  
  35. /// <summary>
  36. /// 导航代理
  37. /// </summary>
  38. [Export, ExportFillNode]
  39. public NavigationAgent2D NavigationAgent2D { get; set; }
  40.  
  41. /// <summary>
  42. /// 导航代理中点
  43. /// </summary>
  44. [Export, ExportFillNode]
  45. public Marker2D NavigationPoint { get; set; }
  46.  
  47. /// <summary>
  48. /// 不通过武发射子弹的开火点
  49. /// </summary>
  50. [Export, ExportFillNode]
  51. public Marker2D FirePoint { get; set; }
  52. /// <summary>
  53. /// 视野区域
  54. /// </summary>
  55. [Export, ExportFillNode]
  56. public Area2D ViewArea { get; set; }
  57. /// <summary>
  58. /// 视野区域碰撞器形状
  59. /// </summary>
  60. [Export, ExportFillNode]
  61. public CollisionPolygon2D ViewAreaCollision { get; set; }
  62. /// <summary>
  63. /// 当前敌人所看向的对象, 也就是枪口指向的对象
  64. /// </summary>
  65. public ActivityObject LookTarget { get; set; }
  66.  
  67. /// <summary>
  68. /// 攻击锁定目标时间
  69. /// </summary>
  70. public float LockingTime { get; set; } = 1f;
  71. /// <summary>
  72. /// 锁定目标已经走过的时间
  73. /// </summary>
  74. public float LockTargetTime { get; set; } = 0;
  75.  
  76. /// <summary>
  77. /// 视野半径, 单位像素, 发现玩家后改视野范围可以穿墙
  78. /// </summary>
  79. public float ViewRange
  80. {
  81. get => _viewRange;
  82. set
  83. {
  84. if (Math.Abs(_viewRange - value) > 0.001f)
  85. {
  86. if (ViewAreaCollision != null)
  87. {
  88. ViewAreaCollision.Polygon = Utils.CreateSectorPolygon(0, value, ViewAngleRange, 4);
  89. }
  90. }
  91. _viewRange = value;
  92. }
  93. }
  94.  
  95. private float _viewRange = -1;
  96.  
  97. /// <summary>
  98. /// 默认视野半径
  99. /// </summary>
  100. public float DefaultViewRange { get; set; } = 250;
  101.  
  102. /// <summary>
  103. /// 发现玩家后跟随玩家的视野半径
  104. /// </summary>
  105. public float TailAfterViewRange { get; set; } = 400;
  106. /// <summary>
  107. /// 视野角度, 角度制
  108. /// </summary>
  109. public float ViewAngleRange { get; set; } = 150;
  110. /// <summary>
  111. /// 攻击间隔时间, 秒
  112. /// </summary>
  113. public float AttackInterval { get; set; } = 0;
  114.  
  115. /// <summary>
  116. /// 当前Ai是否有攻击欲望
  117. /// </summary>
  118. public bool HasAttackDesire { get; private set; } = true;
  119. /// <summary>
  120. /// 是否有移动欲望, 仅在 AiNormal 状态下有效, 其他状态都可以移动
  121. /// </summary>
  122. public bool HasMoveDesire { get; private set; } = true;
  123.  
  124. /// <summary>
  125. /// 临时存储攻击目标, 获取该值请调用 GetAttackTarget() 函数
  126. /// </summary>
  127. private Role _attackTarget = null;
  128. private HashSet<Role> _viewTargets = new HashSet<Role>();
  129.  
  130. public override void OnInit()
  131. {
  132. base.OnInit();
  133. IsAi = true;
  134. ViewArea.BodyEntered += OnViewAreaBodyEntered;
  135. ViewArea.BodyExited += OnViewAreaBodyExited;
  136. StateController = AddComponent<StateController<AiRole, AIStateEnum>>();
  137. //注册Ai状态机
  138. StateController.Register(new AiNormalState());
  139. StateController.Register(new AiTailAfterState());
  140. StateController.Register(new AiFollowUpState());
  141. StateController.Register(new AiLeaveForState());
  142. StateController.Register(new AiSurroundState());
  143. StateController.Register(new AiFindAmmoState());
  144. StateController.Register(new AiAttackState());
  145. StateController.Register(new AiAstonishedState());
  146. StateController.Register(new AiNotifyState());
  147. //默认状态
  148. StateController.ChangeStateInstant(AIStateEnum.AiNormal);
  149.  
  150. //NavigationAgent2D.VelocityComputed += OnVelocityComputed;
  151. }
  152.  
  153. protected override void Process(float delta)
  154. {
  155. base.Process(delta);
  156. if (LookTarget != null)
  157. {
  158. if (LookTarget.IsDestroyed)
  159. {
  160. LookTarget = null;
  161. TargetInViewRange = false;
  162. TargetHasOcclusion = false;
  163. TargetInView = false;
  164. }
  165. else
  166. {
  167. //判断目标是否被墙壁遮挡
  168. TargetHasOcclusion = TestViewRayCast(LookTarget.GetCenterPosition());
  169. TestViewRayCastOver();
  170.  
  171. if (LookTarget is Role role)
  172. {
  173. TargetInViewRange = _viewTargets.Contains(role);
  174. }
  175. else
  176. {
  177. TargetInViewRange = true;
  178. }
  179.  
  180. TargetInView = !TargetHasOcclusion && TargetInViewRange;
  181. }
  182. }
  183.  
  184. //更新视野范围
  185. switch (StateController.CurrState)
  186. {
  187. case AIStateEnum.AiNormal:
  188. case AIStateEnum.AiNotify:
  189. case AIStateEnum.AiAstonished:
  190. ViewRange = DefaultViewRange;
  191. break;
  192. default:
  193. ViewRange = TailAfterViewRange;
  194. break;
  195. }
  196. }
  197.  
  198. /// <summary>
  199. /// 获取攻击的目标对象, 当 HasAttackDesire 为 true 时才会调用
  200. /// </summary>
  201. /// <param name="perspective">上一次发现的角色在本次检测中是否开启视野透视</param>
  202. public Role GetAttackTarget(bool perspective = true)
  203. {
  204. //目标丢失
  205. if (_attackTarget == null || _attackTarget.IsDestroyed || !IsEnemy(_attackTarget))
  206. {
  207. _attackTarget = RefreshAttackTargets(_attackTarget);
  208. return _attackTarget;
  209. }
  210. if (!perspective)
  211. {
  212. //被墙阻挡
  213. if (TestViewRayCast(_attackTarget.GetCenterPosition()))
  214. {
  215. _attackTarget = RefreshAttackTargets(_attackTarget);
  216. TestViewRayCastOver();
  217. return _attackTarget;
  218. }
  219. else
  220. {
  221. TestViewRayCastOver();
  222. }
  223. }
  224. return _attackTarget;
  225. }
  226. /// <summary>
  227. /// 返回地上的武器是否有可以拾取的, 也包含没有被其他敌人标记的武器
  228. /// </summary>
  229. public bool CheckUsableWeaponInUnclaimed()
  230. {
  231. foreach (var unclaimedWeapon in World.Weapon_UnclaimedList)
  232. {
  233. //判断是否能拾起武器, 条件: 相同的房间
  234. if (unclaimedWeapon.AffiliationArea == AffiliationArea)
  235. {
  236. if (!unclaimedWeapon.IsTotalAmmoEmpty())
  237. {
  238. if (!unclaimedWeapon.HasSign(SignNames.AiFindWeaponSign))
  239. {
  240. return true;
  241. }
  242. else
  243. {
  244. //判断是否可以移除该标记
  245. var enemy = unclaimedWeapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  246. if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  247. {
  248. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  249. return true;
  250. }
  251. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  252. {
  253. unclaimedWeapon.RemoveSign(SignNames.AiFindWeaponSign);
  254. return true;
  255. }
  256. }
  257. }
  258. }
  259. }
  260.  
  261. return false;
  262. }
  263. /// <summary>
  264. /// 寻找可用的武器
  265. /// </summary>
  266. public Weapon FindTargetWeapon()
  267. {
  268. Weapon target = null;
  269. var position = Position;
  270. foreach (var weapon in World.Weapon_UnclaimedList)
  271. {
  272. //判断是否能拾起武器, 条件: 相同的房间, 或者当前房间目前没有战斗, 或者不在战斗房间
  273. if (weapon.AffiliationArea == AffiliationArea)
  274. {
  275. //还有弹药
  276. if (!weapon.IsTotalAmmoEmpty())
  277. {
  278. //查询是否有其他敌人标记要拾起该武器
  279. if (weapon.HasSign(SignNames.AiFindWeaponSign))
  280. {
  281. var enemy = weapon.GetSign<Enemy>(SignNames.AiFindWeaponSign);
  282. if (enemy == this) //就是自己标记的
  283. {
  284.  
  285. }
  286. else if (enemy == null || enemy.IsDestroyed) //标记当前武器的敌人已经被销毁
  287. {
  288. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  289. }
  290. else if (!enemy.IsAllWeaponTotalAmmoEmpty()) //标记当前武器的敌人已经有新的武器了
  291. {
  292. weapon.RemoveSign(SignNames.AiFindWeaponSign);
  293. }
  294. else //放弃这把武器
  295. {
  296. continue;
  297. }
  298. }
  299.  
  300. if (target == null) //第一把武器
  301. {
  302. target = weapon;
  303. }
  304. else if (target.Position.DistanceSquaredTo(position) >
  305. weapon.Position.DistanceSquaredTo(position)) //距离更近
  306. {
  307. target = weapon;
  308. }
  309. }
  310. }
  311. }
  312.  
  313. return target;
  314. }
  315.  
  316. /// <summary>
  317. /// 获取武器攻击范围 (最大距离值与最小距离的中间值)
  318. /// </summary>
  319. /// <param name="weight">从最小到最大距离的过渡量, 0 - 1, 默认 0.5</param>
  320. public float GetWeaponRange(float weight = 0.5f)
  321. {
  322. if (WeaponPack.ActiveItem != null)
  323. {
  324. var attribute = WeaponPack.ActiveItem.Attribute;
  325. return Mathf.Lerp(Utils.GetConfigRangeStart(attribute.Bullet.DistanceRange), Utils.GetConfigRangeEnd(attribute.Bullet.DistanceRange), weight);
  326. }
  327.  
  328. return 0;
  329. }
  330.  
  331. /// <summary>
  332. /// 调用视野检测, 如果被墙壁和其它物体遮挡, 则返回true
  333. /// </summary>
  334. public bool TestViewRayCast(Vector2 target)
  335. {
  336. ViewRay.Enabled = true;
  337. ViewRay.TargetPosition = ViewRay.ToLocal(target);
  338. ViewRay.ForceRaycastUpdate();
  339. return ViewRay.IsColliding();
  340. }
  341.  
  342. /// <summary>
  343. /// 调用视野检测完毕后, 需要调用 TestViewRayCastOver() 来关闭视野检测射线
  344. /// </summary>
  345. public void TestViewRayCastOver()
  346. {
  347. ViewRay.Enabled = false;
  348. }
  349.  
  350. /// <summary>
  351. /// AI 拾起武器操作
  352. /// </summary>
  353. public void DoPickUpWeapon()
  354. {
  355. //这几个状态不需要主动拾起武器操作
  356. var state = StateController.CurrState;
  357. if (state == AIStateEnum.AiNormal || state == AIStateEnum.AiNotify || state == AIStateEnum.AiAstonished || state == AIStateEnum.AiAttack)
  358. {
  359. return;
  360. }
  361. //拾起地上的武器
  362. if (InteractiveItem is Weapon weapon)
  363. {
  364. if (WeaponPack.ActiveItem == null) //手上没有武器, 无论如何也要拾起
  365. {
  366. TriggerInteractive();
  367. return;
  368. }
  369.  
  370. //没弹药了
  371. if (weapon.IsTotalAmmoEmpty())
  372. {
  373. return;
  374. }
  375. var index = WeaponPack.FindIndex((we, i) => we.ActivityBase.Id == weapon.ActivityBase.Id);
  376. if (index != -1) //与武器背包中武器类型相同, 补充子弹
  377. {
  378. if (!WeaponPack.GetItem(index).IsAmmoFull())
  379. {
  380. TriggerInteractive();
  381. }
  382.  
  383. return;
  384. }
  385.  
  386. // var index2 = Holster.FindWeapon((we, i) =>
  387. // we.Attribute.WeightType == weapon.Attribute.WeightType && we.IsTotalAmmoEmpty());
  388. var index2 = WeaponPack.FindIndex((we, i) => we.IsTotalAmmoEmpty());
  389. if (index2 != -1) //扔掉没子弹的武器
  390. {
  391. ThrowWeapon(index2);
  392. TriggerInteractive();
  393. return;
  394. }
  395. // if (Holster.HasVacancy()) //有空位, 拾起武器
  396. // {
  397. // TriggerInteractive();
  398. // return;
  399. // }
  400. }
  401. }
  402. /// <summary>
  403. /// 获取锁定目标的剩余时间
  404. /// </summary>
  405. public float GetLockRemainderTime()
  406. {
  407. var weapon = WeaponPack.ActiveItem;
  408. if (weapon == null)
  409. {
  410. return LockingTime - LockTargetTime;
  411. }
  412. return weapon.Attribute.AiAttackAttr.LockingTime - LockTargetTime;
  413. }
  414.  
  415. public override void LookTargetPosition(Vector2 pos)
  416. {
  417. LookTarget = null;
  418. base.LookTargetPosition(pos);
  419. }
  420. /// <summary>
  421. /// 执行移动操作
  422. /// </summary>
  423. public void DoMove()
  424. {
  425. // //计算移动
  426. // NavigationAgent2D.MaxSpeed = EnemyRoleState.MoveSpeed;
  427. // var nextPos = NavigationAgent2D.GetNextPathPosition();
  428. // NavigationAgent2D.Velocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
  429. AnimatedSprite.Play(AnimatorNames.Run);
  430. //计算移动
  431. var nextPos = NavigationAgent2D.GetNextPathPosition();
  432. BasisVelocity = (nextPos - Position - NavigationPoint.Position).Normalized() * RoleState.MoveSpeed;
  433. }
  434.  
  435. /// <summary>
  436. /// 执行站立操作
  437. /// </summary>
  438. public void DoIdle()
  439. {
  440. AnimatedSprite.Play(AnimatorNames.Idle);
  441. BasisVelocity = Vector2.Zero;
  442. }
  443. /// <summary>
  444. /// 更新房间中标记的目标位置
  445. /// </summary>
  446. public void UpdateMarkTargetPosition()
  447. {
  448. if (LookTarget != null)
  449. {
  450. AffiliationArea.RoomInfo.MarkTargetPosition[LookTarget.Id] = LookTarget.Position;
  451. }
  452. }
  453.  
  454. protected override void OnDie()
  455. {
  456. //扔掉所有武器
  457. ThrowAllWeapon();
  458. //创建金币
  459. Gold.CreateGold(Position, RoleState.Gold);
  460. //销毁
  461. Destroy();
  462. }
  463.  
  464. /// <summary>
  465. /// 设置Ai是否有攻击欲望
  466. /// </summary>
  467. public void SetAttackDesire(bool v)
  468. {
  469. if (v != HasAttackDesire)
  470. {
  471. HasAttackDesire = v;
  472. StateController.ChangeState(AIStateEnum.AiNormal);
  473. }
  474. }
  475.  
  476. /// <summary>
  477. /// 设置Ai是否有移动欲望
  478. /// </summary>
  479. public void SetMoveDesire(bool v)
  480. {
  481. HasMoveDesire = v;
  482. }
  483.  
  484. protected override void OnHit(ActivityObject target, int damage, float angle, bool realHarm)
  485. {
  486. //受到伤害
  487. var state = StateController.CurrState;
  488. if (state == AIStateEnum.AiNormal)
  489. {
  490. LookTarget = target;
  491. if (target is Role role)
  492. {
  493. _attackTarget = role;
  494. }
  495. //判断是否进入通知状态
  496. if (World.Role_InstanceList.FindIndex(role =>
  497. role is AiRole aiRole &&
  498. aiRole != this && !aiRole.IsDie && aiRole.AffiliationArea == AffiliationArea &&
  499. aiRole.StateController.CurrState == AIStateEnum.AiNormal) != -1)
  500. {
  501. //进入惊讶状态, 然后再进入通知状态
  502. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiNotify);
  503. }
  504. else
  505. {
  506. //进入惊讶状态, 然后再进入跟随状态
  507. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
  508. }
  509. }
  510. else if (state == AIStateEnum.AiLeaveFor)
  511. {
  512. LookTarget = target;
  513. if (target is Role role)
  514. {
  515. _attackTarget = role;
  516. }
  517. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiTailAfter);
  518. }
  519. else if (state == AIStateEnum.AiFindAmmo)
  520. {
  521. if (LookTarget == null)
  522. {
  523. LookTarget = target;
  524. if (target is Role role)
  525. {
  526. _attackTarget = role;
  527. }
  528. var findAmmo = (AiFindAmmoState)StateController.CurrStateBase;
  529. StateController.ChangeState(AIStateEnum.AiAstonished, AIStateEnum.AiFindAmmo, findAmmo.TargetWeapon);
  530. }
  531. }
  532. else if (state != AIStateEnum.AiAstonished && state != AIStateEnum.AiNotify)
  533. {
  534. if (TargetHasOcclusion || !TargetInView)
  535. {
  536. LookTarget = target;
  537. if (target is Role role)
  538. {
  539. _attackTarget = role;
  540. }
  541. }
  542. }
  543. }
  544. private void OnViewAreaBodyEntered(Node2D node)
  545. {
  546. if (node is Role role)
  547. {
  548. _viewTargets.Add(role);
  549. }
  550. }
  551.  
  552. private void OnViewAreaBodyExited(Node2D node)
  553. {
  554. if (node is Role role)
  555. {
  556. _viewTargets.Remove(role);
  557. }
  558. }
  559.  
  560. private Role RefreshAttackTargets(Role prevRole)
  561. {
  562. if (LookTarget is Role role && !role.IsDestroyed && IsEnemy(role))
  563. {
  564. if (!TestViewRayCast(role.GetCenterPosition()))
  565. {
  566. TestViewRayCastOver();
  567. return role;
  568. }
  569. }
  570. if (_viewTargets.Count == 0)
  571. {
  572. return null;
  573. }
  574. foreach (var attackTarget in _viewTargets)
  575. {
  576. if (prevRole != attackTarget && !attackTarget.IsDestroyed && IsEnemy(attackTarget))
  577. {
  578. if (!TestViewRayCast(attackTarget.GetCenterPosition()))
  579. {
  580. TestViewRayCastOver();
  581. return attackTarget;
  582. }
  583. }
  584. }
  585. TestViewRayCastOver();
  586. return null;
  587. }
  588. // private void OnVelocityComputed(Vector2 velocity)
  589. // {
  590. // if (Mathf.Abs(velocity.X) >= 0.01f && Mathf.Abs(velocity.Y) >= 0.01f)
  591. // {
  592. // AnimatedSprite.Play(AnimatorNames.Run);
  593. // BasisVelocity = velocity;
  594. // }
  595. // }
  596. }