Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / role / Role.cs
@小李xl 小李xl on 25 Jun 2023 19 KB 添加受到伤害后的无敌时间
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using Godot;
  4.  
  5. /// <summary>
  6. /// 角色基类
  7. /// </summary>
  8. public abstract partial class Role : ActivityObject
  9. {
  10. /// <summary>
  11. /// 是否是 Ai
  12. /// </summary>
  13. public bool IsAi { get; protected set; } = false;
  14. /// <summary>
  15. /// 默认攻击对象层级
  16. /// </summary>
  17. public const uint DefaultAttackLayer = PhysicsLayer.Player | PhysicsLayer.Enemy | PhysicsLayer.Wall | PhysicsLayer.Props;
  18. /// <summary>
  19. /// 伤害区域
  20. /// </summary>
  21. [Export, ExportFillNode]
  22. public Area2D HurtArea { get; set; }
  23.  
  24. /// <summary>
  25. /// 移动速度
  26. /// </summary>
  27. public float MoveSpeed = 120f;
  28.  
  29. /// <summary>
  30. /// 所属阵营
  31. /// </summary>
  32. public CampEnum Camp;
  33.  
  34. /// <summary>
  35. /// 攻击目标的碰撞器所属层级, 数据源自于: PhysicsLayer
  36. /// </summary>
  37. public uint AttackLayer { get; set; } = PhysicsLayer.Wall;
  38.  
  39. // /// <summary>
  40. // /// 携带的道具包裹
  41. // /// </summary>
  42. // public List<object> PropsPack { get; } = new List<object>();
  43.  
  44. /// <summary>
  45. /// 角色携带的武器袋
  46. /// </summary>
  47. public Holster Holster { get; private set; }
  48.  
  49. /// <summary>
  50. /// 武器挂载点
  51. /// </summary>
  52. [Export, ExportFillNode]
  53. public MountRotation MountPoint { get; set; }
  54. /// <summary>
  55. /// 背后武器的挂载点
  56. /// </summary>
  57. [Export, ExportFillNode]
  58. public Marker2D BackMountPoint { get; set; }
  59.  
  60. /// <summary>
  61. /// 互动碰撞区域
  62. /// </summary>
  63. [Export, ExportFillNode]
  64. public Area2D InteractiveArea { get; set; }
  65. /// <summary>
  66. /// 脸的朝向
  67. /// </summary>
  68. public FaceDirection Face { get => _face; set => SetFace(value); }
  69. private FaceDirection _face;
  70.  
  71. /// <summary>
  72. /// 是否死亡
  73. /// </summary>
  74. public bool IsDie { get; private set; }
  75. /// <summary>
  76. /// 血量
  77. /// </summary>
  78. public int Hp
  79. {
  80. get => _hp;
  81. protected set
  82. {
  83. int temp = _hp;
  84. _hp = value;
  85. if (temp != _hp) OnChangeHp(_hp);
  86. }
  87. }
  88. private int _hp = 20;
  89.  
  90. /// <summary>
  91. /// 最大血量
  92. /// </summary>
  93. public int MaxHp
  94. {
  95. get => _maxHp;
  96. protected set
  97. {
  98. int temp = _maxHp;
  99. _maxHp = value;
  100. //护盾值改变
  101. if (temp != _maxHp) OnChangeMaxHp(_maxHp);
  102. }
  103. }
  104. private int _maxHp = 20;
  105. /// <summary>
  106. /// 当前护盾值
  107. /// </summary>
  108. public int Shield
  109. {
  110. get => _shield;
  111. protected set
  112. {
  113. int temp = _shield;
  114. _shield = value;
  115. //护盾被破坏
  116. if (temp > 0 && _shield <= 0) OnShieldDestroy();
  117. //护盾值改变
  118. if (temp != _shield) OnChangeShield(_shield);
  119. }
  120. }
  121. private int _shield = 0;
  122.  
  123. /// <summary>
  124. /// 最大护盾值
  125. /// </summary>
  126. public int MaxShield
  127. {
  128. get => _maxShield;
  129. protected set
  130. {
  131. int temp = _maxShield;
  132. _maxShield = value;
  133. if (temp != _maxShield) OnChangeMaxShield(_maxShield);
  134. }
  135. }
  136. private int _maxShield = 0;
  137.  
  138. /// <summary>
  139. /// 单格护盾恢复时间
  140. /// </summary>
  141. private float ShieldRecoveryTime { get; set; } = 8;
  142. /// <summary>
  143. /// 无敌状态
  144. /// </summary>
  145. public bool Invincible
  146. {
  147. get => _invincible;
  148. set
  149. {
  150. if (_invincible != value)
  151. {
  152. if (value) //无敌状态
  153. {
  154. HurtArea.CollisionLayer = _currentLayer;
  155. _flashingInvincibleTimer = -1;
  156. _flashingInvincibleFlag = false;
  157. }
  158. else //正常状态
  159. {
  160. HurtArea.CollisionLayer = _currentLayer;
  161. SetBlendAlpha(1);
  162. }
  163. }
  164.  
  165. _invincible = value;
  166. }
  167. }
  168.  
  169. private bool _invincible = false;
  170. /// <summary>
  171. /// 当前角色所看向的对象, 也就是枪口指向的对象
  172. /// </summary>
  173. public ActivityObject LookTarget { get; set; }
  174.  
  175. //初始缩放
  176. private Vector2 _startScale;
  177. //所有角色碰撞的道具
  178. private readonly List<ActivityObject> _interactiveItemList = new List<ActivityObject>();
  179.  
  180. private CheckInteractiveResult _tempResultData;
  181. private uint _currentLayer;
  182. //闪烁计时器
  183. private float _flashingInvincibleTimer = -1;
  184. //闪烁状态
  185. private bool _flashingInvincibleFlag = false;
  186. //闪烁动画协程id
  187. private long _invincibleFlashingId = -1;
  188. //护盾恢复计时器
  189. private float _shieldRecoveryTimer = 0;
  190.  
  191. /// <summary>
  192. /// 可以互动的道具
  193. /// </summary>
  194. public ActivityObject InteractiveItem { get; private set; }
  195.  
  196. /// <summary>
  197. /// 当血量改变时调用
  198. /// </summary>
  199. protected virtual void OnChangeHp(int hp)
  200. {
  201. }
  202.  
  203. /// <summary>
  204. /// 当最大血量改变时调用
  205. /// </summary>
  206. protected virtual void OnChangeMaxHp(int maxHp)
  207. {
  208. }
  209. /// <summary>
  210. /// 护盾值改变时调用
  211. /// </summary>
  212. protected virtual void OnChangeShield(int shield)
  213. {
  214. }
  215.  
  216. /// <summary>
  217. /// 最大护盾值改变时调用
  218. /// </summary>
  219. protected virtual void OnChangeMaxShield(int maxShield)
  220. {
  221. }
  222.  
  223. /// <summary>
  224. /// 当护盾被破坏时调用
  225. /// </summary>
  226. protected virtual void OnShieldDestroy()
  227. {
  228. }
  229.  
  230. /// <summary>
  231. /// 当受伤时调用
  232. /// </summary>
  233. /// <param name="damage">受到的伤害</param>
  234. /// <param name="realHarm">是否受到真实伤害, 如果为false, 则表示该伤害被互动格挡掉了</param>
  235. protected virtual void OnHit(int damage, bool realHarm)
  236. {
  237. }
  238.  
  239. /// <summary>
  240. /// 受到伤害时调用, 用于改变受到的伤害值
  241. /// </summary>
  242. /// <param name="damage">受到的伤害</param>
  243. protected virtual int OnHandlerHurt(int damage)
  244. {
  245. return damage;
  246. }
  247. /// <summary>
  248. /// 当可互动的物体改变时调用, result 参数为 null 表示变为不可互动
  249. /// </summary>
  250. /// <param name="result">检测是否可互动时的返回值</param>
  251. protected virtual void ChangeInteractiveItem(CheckInteractiveResult result)
  252. {
  253. }
  254.  
  255. /// <summary>
  256. /// 死亡时调用
  257. /// </summary>
  258. protected virtual void OnDie()
  259. {
  260. }
  261.  
  262. public override void OnInit()
  263. {
  264. Holster = new Holster(this);
  265. _startScale = Scale;
  266. MountPoint.Master = this;
  267. HurtArea.CollisionLayer = CollisionLayer;
  268. HurtArea.CollisionMask = 0;
  269. _currentLayer = HurtArea.CollisionLayer;
  270. Face = FaceDirection.Right;
  271. //连接互动物体信号
  272. InteractiveArea.BodyEntered += _OnPropsEnter;
  273. InteractiveArea.BodyExited += _OnPropsExit;
  274. }
  275.  
  276. protected override void Process(float delta)
  277. {
  278. //看向目标
  279. if (LookTarget != null)
  280. {
  281. Vector2 pos = LookTarget.GlobalPosition;
  282. //脸的朝向
  283. var gPos = GlobalPosition;
  284. if (pos.X > gPos.X && Face == FaceDirection.Left)
  285. {
  286. Face = FaceDirection.Right;
  287. }
  288. else if (pos.X < gPos.X && Face == FaceDirection.Right)
  289. {
  290. Face = FaceDirection.Left;
  291. }
  292. //枪口跟随目标
  293. MountPoint.SetLookAt(pos);
  294. }
  295. //检查可互动的道具
  296. bool findFlag = false;
  297. for (int i = 0; i < _interactiveItemList.Count; i++)
  298. {
  299. var item = _interactiveItemList[i];
  300. if (item == null || item.IsDestroyed)
  301. {
  302. _interactiveItemList.RemoveAt(i--);
  303. }
  304. else
  305. {
  306. //找到可互动的道具了
  307. if (!findFlag)
  308. {
  309. var result = item.CheckInteractive(this);
  310. if (result.CanInteractive) //可以互动
  311. {
  312. findFlag = true;
  313. if (InteractiveItem != item) //更改互动物体
  314. {
  315. InteractiveItem = item;
  316. ChangeInteractiveItem(result);
  317. }
  318. else if (result.ShowIcon != _tempResultData.ShowIcon) //切换状态
  319. {
  320. ChangeInteractiveItem(result);
  321. }
  322. }
  323. _tempResultData = result;
  324. }
  325. }
  326. }
  327. //没有可互动的道具
  328. if (!findFlag && InteractiveItem != null)
  329. {
  330. InteractiveItem = null;
  331. ChangeInteractiveItem(null);
  332. }
  333.  
  334. //无敌状态, 播放闪烁动画
  335. if (Invincible)
  336. {
  337. _flashingInvincibleTimer -= delta;
  338. if (_flashingInvincibleTimer <= 0)
  339. {
  340. _flashingInvincibleTimer = 0.15f;
  341. if (_flashingInvincibleFlag)
  342. {
  343. _flashingInvincibleFlag = false;
  344. SetBlendAlpha(0.7f);
  345. }
  346. else
  347. {
  348. _flashingInvincibleFlag = true;
  349. SetBlendAlpha(0);
  350. }
  351. }
  352.  
  353. _shieldRecoveryTimer = 0;
  354. }
  355. else //恢复护盾
  356. {
  357. if (Shield < MaxShield)
  358. {
  359. _shieldRecoveryTimer += delta;
  360. if (_shieldRecoveryTimer >= ShieldRecoveryTime) //时间到, 恢复
  361. {
  362. Shield++;
  363. _shieldRecoveryTimer = 0;
  364. }
  365. }
  366. else
  367. {
  368. _shieldRecoveryTimer = 0;
  369. }
  370. }
  371. }
  372.  
  373. /// <summary>
  374. /// 当武器放到后背时调用, 用于设置武器位置和角度
  375. /// </summary>
  376. /// <param name="weapon">武器实例</param>
  377. /// <param name="index">放入武器袋的位置</param>
  378. public virtual void OnPutBackMount(Weapon weapon, int index)
  379. {
  380. if (index < 8)
  381. {
  382. if (index % 2 == 0)
  383. {
  384. weapon.Position = new Vector2(-4, 3);
  385. weapon.RotationDegrees = 90 - (index / 2f) * 20;
  386. weapon.Scale = new Vector2(-1, 1);
  387. }
  388. else
  389. {
  390. weapon.Position = new Vector2(4, 3);
  391. weapon.RotationDegrees = 270 + (index - 1) / 2f * 20;
  392. weapon.Scale = new Vector2(1, 1);
  393. }
  394. }
  395. else
  396. {
  397. weapon.Visible = false;
  398. }
  399. }
  400. protected override void OnAffiliationChange()
  401. {
  402. //身上的武器的所属区域也得跟着变
  403. Holster.ForEach((weapon, i) =>
  404. {
  405. if (AffiliationArea != null)
  406. {
  407. AffiliationArea.InsertItem(weapon);
  408. }
  409. else if (weapon.AffiliationArea != null)
  410. {
  411. weapon.AffiliationArea.RemoveItem(weapon);
  412. }
  413. });
  414. }
  415.  
  416. /// <summary>
  417. /// 获取当前角色的中心点坐标
  418. /// </summary>
  419. public Vector2 GetCenterPosition()
  420. {
  421. return MountPoint.GlobalPosition;
  422. }
  423.  
  424. /// <summary>
  425. /// 使角色看向指定的坐标,
  426. /// 注意, 调用该函数会清空 LookTarget, 因为拥有 LookTarget 时也会每帧更新玩家视野位置
  427. /// </summary>
  428. /// <param name="pos"></param>
  429. public void LookTargetPosition(Vector2 pos)
  430. {
  431. LookTarget = null;
  432. //脸的朝向
  433. var gPos = GlobalPosition;
  434. if (pos.X > gPos.X && Face == FaceDirection.Left)
  435. {
  436. Face = FaceDirection.Right;
  437. }
  438. else if (pos.X < gPos.X && Face == FaceDirection.Right)
  439. {
  440. Face = FaceDirection.Left;
  441. }
  442. //枪口跟随目标
  443. MountPoint.SetLookAt(pos);
  444. }
  445. /// <summary>
  446. /// 判断指定坐标是否在角色视野方向
  447. /// </summary>
  448. public bool IsPositionInForward(Vector2 pos)
  449. {
  450. var gps = GlobalPosition;
  451. return (Face == FaceDirection.Left && pos.X <= gps.X) ||
  452. (Face == FaceDirection.Right && pos.X >= gps.X);
  453. }
  454.  
  455. /// <summary>
  456. /// 返回所有武器是否弹药都打光了
  457. /// </summary>
  458. public bool IsAllWeaponTotalAmmoEmpty()
  459. {
  460. foreach (var weapon in Holster.Weapons)
  461. {
  462. if (weapon != null && !weapon.IsTotalAmmoEmpty())
  463. {
  464. return false;
  465. }
  466. }
  467.  
  468. return true;
  469. }
  470. /// <summary>
  471. /// 拾起一个武器, 返回是否成功拾取, 如果不想立刻切换到该武器, exchange 请传 false
  472. /// </summary>
  473. /// <param name="weapon">武器对象</param>
  474. /// <param name="exchange">是否立即切换到该武器, 默认 true </param>
  475. public virtual bool PickUpWeapon(Weapon weapon, bool exchange = true)
  476. {
  477. if (Holster.PickupWeapon(weapon, exchange) != -1)
  478. {
  479. //从可互动队列中移除
  480. _interactiveItemList.Remove(weapon);
  481. return true;
  482. }
  483.  
  484. return false;
  485. }
  486.  
  487. /// <summary>
  488. /// 切换到下一个武器
  489. /// </summary>
  490. public virtual void ExchangeNext()
  491. {
  492. Holster.ExchangeNext();
  493. }
  494.  
  495. /// <summary>
  496. /// 切换到上一个武器
  497. /// </summary>
  498. public virtual void ExchangePrev()
  499. {
  500. Holster.ExchangePrev();
  501. }
  502.  
  503. /// <summary>
  504. /// 扔掉当前使用的武器, 切换到上一个武器
  505. /// </summary>
  506. public virtual void ThrowWeapon()
  507. {
  508. ThrowWeapon(Holster.ActiveIndex);
  509. }
  510.  
  511. /// <summary>
  512. /// 扔掉指定位置的武器
  513. /// </summary>
  514. /// <param name="index">武器在武器袋中的位置</param>
  515. public virtual void ThrowWeapon(int index)
  516. {
  517. var weapon = Holster.GetWeapon(index);
  518. if (weapon == null)
  519. {
  520. return;
  521. }
  522.  
  523. var temp = weapon.AnimatedSprite.Position;
  524. if (Face == FaceDirection.Left)
  525. {
  526. temp.Y = -temp.Y;
  527. }
  528. //var pos = GlobalPosition + temp.Rotated(weapon.GlobalRotation);
  529. Holster.RemoveWeapon(index);
  530. //播放抛出效果
  531. weapon.ThrowWeapon(this, GlobalPosition);
  532. }
  533.  
  534. /// <summary>
  535. /// 返回是否存在可互动的物体
  536. /// </summary>
  537. public bool HasInteractive()
  538. {
  539. return InteractiveItem != null;
  540. }
  541.  
  542. /// <summary>
  543. /// 触发与碰撞的物体互动, 并返回与其互动的物体
  544. /// </summary>
  545. public ActivityObject TriggerInteractive()
  546. {
  547. if (HasInteractive())
  548. {
  549. var item = InteractiveItem;
  550. item.Interactive(this);
  551. return item;
  552. }
  553.  
  554. return null;
  555. }
  556.  
  557. /// <summary>
  558. /// 触发换弹
  559. /// </summary>
  560. public virtual void Reload()
  561. {
  562. if (Holster.ActiveWeapon != null)
  563. {
  564. Holster.ActiveWeapon.Reload();
  565. }
  566. }
  567.  
  568. /// <summary>
  569. /// 触发攻击
  570. /// </summary>
  571. public virtual void Attack()
  572. {
  573. if (Holster.ActiveWeapon != null)
  574. {
  575. Holster.ActiveWeapon.Trigger();
  576. }
  577. }
  578.  
  579. /// <summary>
  580. /// 受到伤害, 如果是在碰撞信号处理函数中调用该函数, 请使用 CallDeferred 来延时调用, 否则很有可能导致报错
  581. /// </summary>
  582. /// <param name="damage">伤害的量</param>
  583. /// <param name="angle">角度</param>
  584. public virtual void Hurt(int damage, float angle)
  585. {
  586. //受伤闪烁, 无敌状态
  587. if (Invincible)
  588. {
  589. return;
  590. }
  591. //计算真正受到的伤害
  592. damage = OnHandlerHurt(damage);
  593. if (damage <= 0)
  594. {
  595. return;
  596. }
  597.  
  598. var flag = Shield > 0;
  599. if (flag)
  600. {
  601. Shield -= damage;
  602. }
  603. else
  604. {
  605. Hp -= damage;
  606. //播放血液效果
  607. // var packedScene = ResourceManager.Load<PackedScene>(ResourcePath.prefab_effect_Blood_tscn);
  608. // var blood = packedScene.Instance<Blood>();
  609. // blood.GlobalPosition = GlobalPosition;
  610. // blood.Rotation = angle;
  611. // GameApplication.Instance.Node3D.GetRoot().AddChild(blood);
  612. }
  613. OnHit(damage, !flag);
  614. //受伤特效
  615. PlayHitAnimation();
  616. //死亡判定
  617. if (Hp <= 0)
  618. {
  619. //死亡
  620. if (!IsDie)
  621. {
  622. IsDie = true;
  623. OnDie();
  624. }
  625. }
  626. }
  627.  
  628. /// <summary>
  629. /// 播放无敌状态闪烁动画
  630. /// </summary>
  631. /// <param name="time">持续时间</param>
  632. public void PlayInvincibleFlashing(float time)
  633. {
  634. Invincible = true;
  635. if (_invincibleFlashingId >= 0) //上一个还没结束
  636. {
  637. StopCoroutine(_invincibleFlashingId);
  638. }
  639.  
  640. _invincibleFlashingId = StartCoroutine(RunInvincibleFlashing(time));
  641. }
  642.  
  643. /// <summary>
  644. /// 停止无敌状态闪烁动画
  645. /// </summary>
  646. public void StopInvincibleFlashing()
  647. {
  648. Invincible = false;
  649. if (_invincibleFlashingId >= 0)
  650. {
  651. StopCoroutine(_invincibleFlashingId);
  652. _invincibleFlashingId = -1;
  653. }
  654. }
  655.  
  656. private IEnumerator RunInvincibleFlashing(float time)
  657. {
  658. yield return new WaitForSeconds(time);
  659. _invincibleFlashingId = -1;
  660. Invincible = false;
  661. }
  662.  
  663. /// <summary>
  664. /// 设置脸的朝向
  665. /// </summary>
  666. private void SetFace(FaceDirection face)
  667. {
  668. if (_face != face)
  669. {
  670. _face = face;
  671. if (face == FaceDirection.Right)
  672. {
  673. RotationDegrees = 0;
  674. Scale = _startScale;
  675. }
  676. else
  677. {
  678. RotationDegrees = 180;
  679. Scale = new Vector2(_startScale.X, -_startScale.Y);
  680. }
  681. }
  682. }
  683. /// <summary>
  684. /// 连接信号: InteractiveArea.BodyEntered
  685. /// 与物体碰撞
  686. /// </summary>
  687. private void _OnPropsEnter(Node2D other)
  688. {
  689. if (other is ActivityObject propObject && !propObject.CollisionWithMask(PhysicsLayer.OnHand))
  690. {
  691. if (!_interactiveItemList.Contains(propObject))
  692. {
  693. _interactiveItemList.Add(propObject);
  694. }
  695. }
  696. }
  697.  
  698. /// <summary>
  699. /// 连接信号: InteractiveArea.BodyExited
  700. /// 物体离开碰撞区域
  701. /// </summary>
  702. private void _OnPropsExit(Node2D other)
  703. {
  704. if (other is ActivityObject propObject)
  705. {
  706. if (_interactiveItemList.Contains(propObject))
  707. {
  708. _interactiveItemList.Remove(propObject);
  709. }
  710. if (InteractiveItem == propObject)
  711. {
  712. InteractiveItem = null;
  713. ChangeInteractiveItem(null);
  714. }
  715. }
  716. }
  717. }