Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / role / Role.cs
  1. using System.Collections.Generic;
  2. using Godot;
  3.  
  4. /// <summary>
  5. /// 角色基类
  6. /// </summary>
  7. public abstract partial class Role : ActivityObject
  8. {
  9. /// <summary>
  10. /// 是否是 Ai
  11. /// </summary>
  12. public bool IsAi { get; protected set; } = false;
  13. /// <summary>
  14. /// 默认攻击对象层级
  15. /// </summary>
  16. public const uint DefaultAttackLayer = PhysicsLayer.Player | PhysicsLayer.Enemy | PhysicsLayer.Wall | PhysicsLayer.Props;
  17. /// <summary>
  18. /// 伤害区域
  19. /// </summary>
  20. public Area2D HurtArea { get; private set; }
  21.  
  22. /// <summary>
  23. /// 移动速度
  24. /// </summary>
  25. public float MoveSpeed = 120f;
  26.  
  27. /// <summary>
  28. /// 所属阵营
  29. /// </summary>
  30. public CampEnum Camp;
  31.  
  32. /// <summary>
  33. /// 攻击目标的碰撞器所属层级, 数据源自于: PhysicsLayer
  34. /// </summary>
  35. public uint AttackLayer { get; set; } = PhysicsLayer.Wall;
  36.  
  37. /// <summary>
  38. /// 携带的道具包裹
  39. /// </summary>
  40. public List<object> PropsPack { get; } = new List<object>();
  41.  
  42. /// <summary>
  43. /// 角色携带的枪套
  44. /// </summary>
  45. public Holster Holster { get; private set; }
  46.  
  47. /// <summary>
  48. /// 武器挂载点
  49. /// </summary>
  50. public MountRotation MountPoint { get; private set; }
  51. /// <summary>
  52. /// 背后武器的挂载点
  53. /// </summary>
  54. public Marker2D BackMountPoint { get; private set; }
  55.  
  56. /// <summary>
  57. /// 互动碰撞区域
  58. /// </summary>
  59. public Area2D InteractiveArea { get; private set; }
  60. /// <summary>
  61. /// 脸的朝向
  62. /// </summary>
  63. public FaceDirection Face { get => _face; set => SetFace(value); }
  64. private FaceDirection _face;
  65.  
  66. /// <summary>
  67. /// 是否死亡
  68. /// </summary>
  69. public bool IsDie { get; private set; }
  70. /// <summary>
  71. /// 血量
  72. /// </summary>
  73. public int Hp
  74. {
  75. get => _hp;
  76. protected set
  77. {
  78. int temp = _hp;
  79. _hp = value;
  80. if (temp != _hp) OnChangeHp(_hp);
  81. }
  82. }
  83. private int _hp = 20;
  84.  
  85. /// <summary>
  86. /// 最大血量
  87. /// </summary>
  88. public int MaxHp
  89. {
  90. get => _maxHp;
  91. protected set
  92. {
  93. int temp = _maxHp;
  94. _maxHp = value;
  95. //护盾值改变
  96. if (temp != _maxHp) OnChangeMaxHp(_maxHp);
  97. }
  98. }
  99. private int _maxHp = 20;
  100. /// <summary>
  101. /// 当前护盾值
  102. /// </summary>
  103. public int Shield
  104. {
  105. get => _shield;
  106. protected set
  107. {
  108. int temp = _shield;
  109. _shield = value;
  110. //护盾被破坏
  111. if (temp > 0 && _shield <= 0) OnShieldDamage();
  112. //护盾值改变
  113. if (temp != _shield) OnChangeShield(_shield);
  114. }
  115. }
  116. private int _shield = 0;
  117.  
  118. /// <summary>
  119. /// 最大护盾值
  120. /// </summary>
  121. public int MaxShield
  122. {
  123. get => _maxShield;
  124. protected set
  125. {
  126. int temp = _maxShield;
  127. _maxShield = value;
  128. if (temp != _maxShield) OnChangeMaxShield(_maxShield);
  129. }
  130. }
  131. private int _maxShield = 0;
  132.  
  133. /// <summary>
  134. /// 当前角色所看向的对象, 也就是枪口指向的对象
  135. /// </summary>
  136. public ActivityObject LookTarget { get; set; }
  137.  
  138. //初始缩放
  139. private Vector2 _startScale;
  140. //所有角色碰撞的道具
  141. private readonly List<ActivityObject> _interactiveItemList = new List<ActivityObject>();
  142.  
  143. private CheckInteractiveResult _tempResultData;
  144.  
  145. /// <summary>
  146. /// 可以互动的道具
  147. /// </summary>
  148. public ActivityObject InteractiveItem { get; private set; }
  149.  
  150. /// <summary>
  151. /// 当血量改变时调用
  152. /// </summary>
  153. protected virtual void OnChangeHp(int hp)
  154. {
  155. }
  156.  
  157. /// <summary>
  158. /// 当最大血量改变时调用
  159. /// </summary>
  160. protected virtual void OnChangeMaxHp(int maxHp)
  161. {
  162. }
  163. /// <summary>
  164. /// 护盾值改变时调用
  165. /// </summary>
  166. protected virtual void OnChangeShield(int shield)
  167. {
  168. }
  169.  
  170. /// <summary>
  171. /// 最大护盾值改变时调用
  172. /// </summary>
  173. protected virtual void OnChangeMaxShield(int maxShield)
  174. {
  175. }
  176.  
  177. /// <summary>
  178. /// 当护盾被破坏时调用
  179. /// </summary>
  180. protected virtual void OnShieldDamage()
  181. {
  182. }
  183.  
  184. /// <summary>
  185. /// 当受伤时调用
  186. /// </summary>
  187. /// <param name="damage">受到的伤害</param>
  188. protected virtual void OnHit(int damage)
  189. {
  190. }
  191.  
  192. /// <summary>
  193. /// 当可互动的物体改变时调用, result 参数为 null 表示变为不可互动
  194. /// </summary>
  195. /// <param name="result">检测是否可互动时的返回值</param>
  196. protected virtual void ChangeInteractiveItem(CheckInteractiveResult result)
  197. {
  198. }
  199.  
  200. /// <summary>
  201. /// 死亡时调用
  202. /// </summary>
  203. protected virtual void OnDie()
  204. {
  205. }
  206.  
  207. public override void _Ready()
  208. {
  209. Holster = new Holster(this);
  210. _startScale = Scale;
  211. MountPoint = GetNode<MountRotation>("MountPoint");
  212. MountPoint.Master = this;
  213. BackMountPoint = GetNode<Marker2D>("BackMountPoint");
  214.  
  215. HurtArea = GetNode<Area2D>("HurtArea");
  216. HurtArea.CollisionLayer = CollisionLayer;
  217. HurtArea.CollisionMask = 0;
  218. Face = FaceDirection.Right;
  219.  
  220. //连接互动物体信号
  221. InteractiveArea = GetNode<Area2D>("InteractiveArea");
  222. InteractiveArea.BodyEntered += _OnPropsEnter;
  223. InteractiveArea.BodyExited += _OnPropsExit;
  224. }
  225.  
  226. protected override void Process(float delta)
  227. {
  228. //看向目标
  229. if (LookTarget != null)
  230. {
  231. Vector2 pos = LookTarget.GlobalPosition;
  232. //脸的朝向
  233. var gPos = GlobalPosition;
  234. if (pos.X > gPos.X && Face == FaceDirection.Left)
  235. {
  236. Face = FaceDirection.Right;
  237. }
  238. else if (pos.X < gPos.X && Face == FaceDirection.Right)
  239. {
  240. Face = FaceDirection.Left;
  241. }
  242. //枪口跟随目标
  243. MountPoint.SetLookAt(pos);
  244. }
  245. //检查可互动的道具
  246. bool findFlag = false;
  247. for (int i = 0; i < _interactiveItemList.Count; i++)
  248. {
  249. var item = _interactiveItemList[i];
  250. if (item == null)
  251. {
  252. _interactiveItemList.RemoveAt(i--);
  253. }
  254. else
  255. {
  256. //找到可互动的道具了
  257. if (!findFlag)
  258. {
  259. var result = item.CheckInteractive(this);
  260. if (result.CanInteractive) //可以互动
  261. {
  262. findFlag = true;
  263. if (InteractiveItem != item) //更改互动物体
  264. {
  265. InteractiveItem = item;
  266. ChangeInteractiveItem(result);
  267. }
  268. else if (result.ShowIcon != _tempResultData.ShowIcon) //切换状态
  269. {
  270. ChangeInteractiveItem(result);
  271. }
  272. }
  273. _tempResultData = result;
  274. }
  275. }
  276. }
  277. //没有可互动的道具
  278. if (!findFlag && InteractiveItem != null)
  279. {
  280. InteractiveItem = null;
  281. ChangeInteractiveItem(null);
  282. }
  283. }
  284.  
  285. /// <summary>
  286. /// 获取当前角色的中心点坐标
  287. /// </summary>
  288. public Vector2 GetCenterPosition()
  289. {
  290. return MountPoint.GlobalPosition;
  291. }
  292.  
  293. /// <summary>
  294. /// 使角色看向指定的坐标,
  295. /// 注意, 调用该函数会清空 LookTarget, 因为拥有 LookTarget 时也会每帧更新玩家视野位置
  296. /// </summary>
  297. /// <param name="pos"></param>
  298. public void LookTargetPosition(Vector2 pos)
  299. {
  300. LookTarget = null;
  301. //脸的朝向
  302. var gPos = GlobalPosition;
  303. if (pos.X > gPos.X && Face == FaceDirection.Left)
  304. {
  305. Face = FaceDirection.Right;
  306. }
  307. else if (pos.X < gPos.X && Face == FaceDirection.Right)
  308. {
  309. Face = FaceDirection.Left;
  310. }
  311. //枪口跟随目标
  312. MountPoint.SetLookAt(pos);
  313. }
  314. /// <summary>
  315. /// 判断指定坐标是否在角色视野方向
  316. /// </summary>
  317. public bool IsPositionInForward(Vector2 pos)
  318. {
  319. var gps = GlobalPosition;
  320. return (Face == FaceDirection.Left && pos.X <= gps.X) ||
  321. (Face == FaceDirection.Right && pos.X >= gps.X);
  322. }
  323.  
  324. /// <summary>
  325. /// 返回所有武器是否弹药都打光了
  326. /// </summary>
  327. public bool IsAllWeaponTotalAmmoEmpty()
  328. {
  329. foreach (var weaponSlot in Holster.SlotList)
  330. {
  331. if (weaponSlot.Weapon != null && !weaponSlot.Weapon.IsTotalAmmoEmpty())
  332. {
  333. return false;
  334. }
  335. }
  336.  
  337. return true;
  338. }
  339. /// <summary>
  340. /// 拾起一个武器, 返回是否成功拾取, 如果不想立刻切换到该武器, exchange 请传 false
  341. /// </summary>
  342. /// <param name="weapon">武器对象</param>
  343. /// <param name="exchange">是否立即切换到该武器, 默认 true </param>
  344. public virtual bool PickUpWeapon(Weapon weapon, bool exchange = true)
  345. {
  346. if (Holster.PickupWeapon(weapon, exchange) != -1)
  347. {
  348. //从可互动队列中移除
  349. _interactiveItemList.Remove(weapon);
  350. return true;
  351. }
  352.  
  353. return false;
  354. }
  355.  
  356. /// <summary>
  357. /// 切换到下一个武器
  358. /// </summary>
  359. public virtual void ExchangeNext()
  360. {
  361. Holster.ExchangeNext();
  362. }
  363.  
  364. /// <summary>
  365. /// 切换到上一个武器
  366. /// </summary>
  367. public virtual void ExchangePrev()
  368. {
  369. Holster.ExchangePrev();
  370. }
  371.  
  372. /// <summary>
  373. /// 扔掉当前使用的武器, 切换到上一个武器
  374. /// </summary>
  375. public virtual void ThrowWeapon()
  376. {
  377. ThrowWeapon(Holster.ActiveIndex);
  378. }
  379.  
  380. /// <summary>
  381. /// 扔掉指定位置的武器
  382. /// </summary>
  383. /// <param name="index">武器在武器袋中的位置</param>
  384. public virtual void ThrowWeapon(int index)
  385. {
  386. var weapon = Holster.GetWeapon(index);
  387. if (weapon == null)
  388. {
  389. return;
  390. }
  391.  
  392. var temp = new Vector2(weapon.Attribute.HoldPosition.X, 0);
  393. var pos = weapon.GlobalPosition + temp.Rotated(weapon.GlobalRotation);
  394. Holster.RemoveWeapon(index);
  395. //播放抛出效果
  396. weapon.ThrowWeapon(this, pos);
  397. }
  398.  
  399. /// <summary>
  400. /// 返回是否存在可互动的物体
  401. /// </summary>
  402. public bool HasInteractive()
  403. {
  404. return InteractiveItem != null;
  405. }
  406.  
  407. /// <summary>
  408. /// 触发与碰撞的物体互动, 并返回与其互动的物体
  409. /// </summary>
  410. public ActivityObject TriggerInteractive()
  411. {
  412. if (HasInteractive())
  413. {
  414. var item = InteractiveItem;
  415. item.Interactive(this);
  416. return item;
  417. }
  418.  
  419. return null;
  420. }
  421.  
  422. /// <summary>
  423. /// 触发换弹
  424. /// </summary>
  425. public virtual void Reload()
  426. {
  427. if (Holster.ActiveWeapon != null)
  428. {
  429. Holster.ActiveWeapon.Reload();
  430. }
  431. }
  432.  
  433. /// <summary>
  434. /// 触发攻击
  435. /// </summary>
  436. public virtual void Attack()
  437. {
  438. if (Holster.ActiveWeapon != null)
  439. {
  440. Holster.ActiveWeapon.Trigger();
  441. }
  442. }
  443.  
  444. /// <summary>
  445. /// 受到伤害, 如果是在碰撞信号处理函数中调用该函数, 请使用 CallDeferred 来延时调用, 否则很有可能导致报错
  446. /// </summary>
  447. /// <param name="damage">伤害的量</param>
  448. /// <param name="angle">角度</param>
  449. public virtual void Hurt(int damage, float angle)
  450. {
  451. OnHit(damage);
  452. if (Shield > 0)
  453. {
  454. Shield -= damage;
  455. }
  456. else
  457. {
  458. Hp -= damage;
  459. //播放血液效果
  460. // var packedScene = ResourceManager.Load<PackedScene>(ResourcePath.prefab_effect_Blood_tscn);
  461. // var blood = packedScene.Instance<Blood>();
  462. // blood.GlobalPosition = GlobalPosition;
  463. // blood.Rotation = angle;
  464. // GameApplication.Instance.Node3D.GetRoot().AddChild(blood);
  465. }
  466. //受伤特效
  467. PlayHitAnimation();
  468. //死亡判定
  469. if (Hp <= 0)
  470. {
  471. //死亡
  472. if (!IsDie)
  473. {
  474. IsDie = true;
  475. OnDie();
  476. }
  477. }
  478. }
  479.  
  480. /// <summary>
  481. /// 设置脸的朝向
  482. /// </summary>
  483. private void SetFace(FaceDirection face)
  484. {
  485. if (_face != face)
  486. {
  487. _face = face;
  488. if (face == FaceDirection.Right)
  489. {
  490. RotationDegrees = 0;
  491. Scale = _startScale;
  492. }
  493. else
  494. {
  495. RotationDegrees = 180;
  496. Scale = new Vector2(_startScale.X, -_startScale.Y);
  497. }
  498. }
  499. }
  500. /// <summary>
  501. /// 连接信号: InteractiveArea.area_entered
  502. /// 与物体碰撞
  503. /// </summary>
  504. private void _OnPropsEnter(Node2D other)
  505. {
  506. ActivityObject propObject = other.AsActivityObject();
  507. if (propObject != null)
  508. {
  509. if (!_interactiveItemList.Contains(propObject))
  510. {
  511. _interactiveItemList.Add(propObject);
  512. }
  513. }
  514. }
  515.  
  516. /// <summary>
  517. /// 连接信号: InteractiveArea.area_exited
  518. /// 物体离开碰撞区域
  519. /// </summary>
  520. private void _OnPropsExit(Node2D other)
  521. {
  522. ActivityObject propObject = other.AsActivityObject();
  523. if (propObject != null)
  524. {
  525. if (_interactiveItemList.Contains(propObject))
  526. {
  527. _interactiveItemList.Remove(propObject);
  528. }
  529. if (InteractiveItem == propObject)
  530. {
  531. InteractiveItem = null;
  532. ChangeInteractiveItem(null);
  533. }
  534. }
  535. }
  536. }