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