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