Newer
Older
DungeonShooting / DungeonShooting_Godot / src / game / activity / weapon / Weapon.cs
  1. using Godot;
  2. using System;
  3. using System.Collections.Generic;
  4. using Config;
  5.  
  6. /// <summary>
  7. /// 武器的基类
  8. /// </summary>
  9. public abstract partial class Weapon : ActivityObject
  10. {
  11. /// <summary>
  12. /// 开火回调事件
  13. /// </summary>
  14. public event Action<Weapon> FireEvent;
  15.  
  16. /// <summary>
  17. /// 武器属性数据
  18. /// </summary>
  19. public ExcelConfig.Weapon Attribute => _weaponAttribute;
  20. private ExcelConfig.Weapon _weaponAttribute;
  21. private ExcelConfig.Weapon _playerWeaponAttribute;
  22. private ExcelConfig.Weapon _aiWeaponAttribute;
  23.  
  24. /// <summary>
  25. /// 武器攻击的目标阵营
  26. /// </summary>
  27. public CampEnum TargetCamp { get; set; }
  28.  
  29. /// <summary>
  30. /// 该武器的拥有者
  31. /// </summary>
  32. public Role Master { get; private set; }
  33.  
  34. /// <summary>
  35. /// 当前弹夹弹药剩余量
  36. /// </summary>
  37. public int CurrAmmo { get; private set; }
  38.  
  39. /// <summary>
  40. /// 剩余弹药量(备用弹药)
  41. /// </summary>
  42. public int ResidueAmmo { get; private set; }
  43.  
  44. /// <summary>
  45. /// 武器管的开火点
  46. /// </summary>
  47. [Export, ExportFillNode]
  48. public Marker2D FirePoint { get; set; }
  49.  
  50. /// <summary>
  51. /// 弹壳抛出的点
  52. /// </summary>
  53. [Export, ExportFillNode]
  54. public Marker2D ShellPoint { get; set; }
  55.  
  56. /// <summary>
  57. /// 武器握把位置
  58. /// </summary>
  59. [Export, ExportFillNode]
  60. public Marker2D GripPoint { get; set; }
  61. /// <summary>
  62. /// 武器的当前散射半径
  63. /// </summary>
  64. public float CurrScatteringRange { get; private set; } = 0;
  65.  
  66. /// <summary>
  67. /// 是否在换弹中
  68. /// </summary>
  69. /// <value></value>
  70. public bool Reloading { get; private set; } = false;
  71.  
  72. /// <summary>
  73. /// 换弹进度 (从 0 到 1)
  74. /// </summary>
  75. public float ReloadProgress
  76. {
  77. get
  78. {
  79. if (!Reloading)
  80. {
  81. return 1;
  82. }
  83.  
  84. if (Attribute.AloneReload)
  85. {
  86. //总时间
  87. var total = Attribute.AloneReloadBeginIntervalTime + (Attribute.ReloadTime * Attribute.AmmoCapacity) + Attribute.AloneReloadFinishIntervalTime;
  88. //当前时间
  89. float current;
  90. if (_aloneReloadState == 1)
  91. {
  92. current = (Attribute.AloneReloadBeginIntervalTime - _reloadTimer) + Attribute.ReloadTime * CurrAmmo;
  93. }
  94. else if (_aloneReloadState == 2)
  95. {
  96. current = Attribute.AloneReloadBeginIntervalTime + (Attribute.ReloadTime * (CurrAmmo + (1 - _reloadTimer / Attribute.ReloadTime)));
  97. }
  98. else
  99. {
  100. current = Attribute.AloneReloadBeginIntervalTime + (Attribute.ReloadTime * CurrAmmo) + (Attribute.AloneReloadFinishIntervalTime - _reloadTimer);
  101. }
  102.  
  103. return current / total;
  104. }
  105.  
  106. return 1 - _reloadTimer / Attribute.ReloadTime;
  107. }
  108. }
  109.  
  110. /// <summary>
  111. /// 返回是否在蓄力中,
  112. /// 注意, 属性仅在 Attribute.LooseShoot == false 时有正确的返回值, 否则返回 false
  113. /// </summary>
  114. public bool IsCharging => _looseShootFlag;
  115.  
  116. /// <summary>
  117. /// 返回武器是否在武器袋中
  118. /// </summary>
  119. public bool IsInHolster => Master != null;
  120.  
  121. /// <summary>
  122. /// 返回是否真正使用该武器
  123. /// </summary>
  124. public bool IsActive => Master != null && Master.Holster.ActiveWeapon == this;
  125. /// <summary>
  126. /// 动画播放器
  127. /// </summary>
  128. [Export, ExportFillNode]
  129. public AnimationPlayer AnimationPlayer { get; set; }
  130.  
  131. /// <summary>
  132. /// 是否自动播放 SpriteFrames 的动画
  133. /// </summary>
  134. public bool IsAutoPlaySpriteFrames { get; set; } = true;
  135.  
  136. //--------------------------------------------------------------------------------------------
  137. //是否按下
  138. private bool _triggerFlag = false;
  139.  
  140. //扳机计时器
  141. private float _triggerTimer = 0;
  142.  
  143. //开火前延时时间
  144. private float _delayedTime = 0;
  145.  
  146. //开火间隙时间
  147. private float _fireInterval = 0;
  148.  
  149. //开火武器口角度
  150. private float _fireAngle = 0;
  151.  
  152. //攻击冷却计时
  153. private float _attackTimer = 0;
  154.  
  155. //攻击状态
  156. private bool _attackFlag = false;
  157. //多久没开火了
  158. private float _noAttackTime = 0;
  159.  
  160. //按下的时间
  161. private float _downTimer = 0;
  162.  
  163. //松开的时间
  164. private float _upTimer = 0;
  165.  
  166. //连发次数
  167. private float _continuousCount = 0;
  168.  
  169. //连发状态记录
  170. private bool _continuousShootFlag = false;
  171.  
  172. //松开扳机是否开火
  173. private bool _looseShootFlag = false;
  174.  
  175. //蓄力攻击时长
  176. private float _chargeTime = 0;
  177.  
  178. //是否需要重置武器数据
  179. private bool _dirtyFlag = false;
  180.  
  181. //当前后坐力导致的偏移长度
  182. private float _currBacklashLength = 0;
  183.  
  184. //临时存放动画精灵位置
  185. private Vector2 _tempAnimatedSpritePosition;
  186.  
  187. //换弹计时器
  188. private float _reloadTimer = 0;
  189. //单独换弹设置下的换弹状态, 0: 未换弹, 1: 装第一颗子弹之前, 2: 单独装弹中, 3: 单独装弹完成
  190. private byte _aloneReloadState = 0;
  191.  
  192. //单独换弹状态下是否强制结束换弹过程
  193. private bool _aloneReloadStop = false;
  194. //本次换弹已用时间
  195. private float _reloadUseTime = 0;
  196.  
  197. //是否播放过换弹完成音效
  198. private bool _playReloadFinishSoundFlag = false;
  199.  
  200. // ----------------------------------------------
  201. private uint _tempLayer;
  202.  
  203. private static bool _init = false;
  204. private static Dictionary<string, ExcelConfig.Weapon> _weaponAttributeMap =
  205. new Dictionary<string, ExcelConfig.Weapon>();
  206.  
  207. /// <summary>
  208. /// 初始化武器属性数据
  209. /// </summary>
  210. public static void InitWeaponAttribute()
  211. {
  212. if (_init)
  213. {
  214. return;
  215. }
  216.  
  217. _init = true;
  218. foreach (var weaponAttr in ExcelConfig.Weapon_List)
  219. {
  220. if (!string.IsNullOrEmpty(weaponAttr.WeaponId))
  221. {
  222. if (!_weaponAttributeMap.TryAdd(weaponAttr.WeaponId, weaponAttr))
  223. {
  224. GD.PrintErr("发现重复注册的武器属性: " + weaponAttr.Id);
  225. }
  226. }
  227. }
  228. }
  229. private static ExcelConfig.Weapon _GetWeaponAttribute(string itemId)
  230. {
  231. if (_weaponAttributeMap.TryGetValue(itemId, out var attr))
  232. {
  233. return attr;
  234. }
  235.  
  236. throw new Exception($"武器'{itemId}'没有在 Weapon 表中配置属性数据!");
  237. }
  238. public override void OnInit()
  239. {
  240. InitWeapon(_GetWeaponAttribute(ItemId));
  241. AnimatedSprite.AnimationFinished += OnAnimationFinished;
  242. }
  243.  
  244. /// <summary>
  245. /// 初始化武器属性
  246. /// </summary>
  247. public void InitWeapon(ExcelConfig.Weapon attribute)
  248. {
  249. _playerWeaponAttribute = attribute;
  250. _weaponAttribute = attribute;
  251. if (attribute.AiUseAttribute != null)
  252. {
  253. _aiWeaponAttribute = attribute.AiUseAttribute;
  254. }
  255. else
  256. {
  257. _aiWeaponAttribute = attribute;
  258. }
  259.  
  260. if (Attribute.AmmoCapacity > Attribute.MaxAmmoCapacity)
  261. {
  262. Attribute.AmmoCapacity = Attribute.MaxAmmoCapacity;
  263. GD.PrintErr("弹夹的容量不能超过弹药上限, 武器id: " + ItemId);
  264. }
  265. //弹药量
  266. CurrAmmo = Attribute.AmmoCapacity;
  267. //剩余弹药量
  268. ResidueAmmo = Mathf.Min(Attribute.StandbyAmmoCapacity + CurrAmmo, Attribute.MaxAmmoCapacity) - CurrAmmo;
  269. ThrowCollisionSize = attribute.ThrowCollisionSize.AsVector2();
  270. }
  271.  
  272. /// <summary>
  273. /// 单次开火时调用的函数
  274. /// </summary>
  275. protected abstract void OnFire();
  276.  
  277. /// <summary>
  278. /// 发射子弹时调用的函数, 每发射一枚子弹调用一次,
  279. /// 如果做霰弹武器效果, 一次开火发射5枚子弹, 则该函数调用5次
  280. /// </summary>
  281. /// <param name="fireRotation">开火时枪口旋转角度</param>
  282. protected abstract void OnShoot(float fireRotation);
  283. /// <summary>
  284. /// 当按下扳机时调用
  285. /// </summary>
  286. protected virtual void OnDownTrigger()
  287. {
  288. }
  289.  
  290. /// <summary>
  291. /// 当松开扳机时调用
  292. /// </summary>
  293. protected virtual void OnUpTrigger()
  294. {
  295. }
  296.  
  297. /// <summary>
  298. /// 开始蓄力时调用,
  299. /// 注意, 该函数仅在 Attribute.LooseShoot == false 时才能被调用
  300. /// </summary>
  301. protected virtual void OnStartCharge()
  302. {
  303. }
  304.  
  305. /// <summary>
  306. /// 当换弹时调用, 如果设置单独装弹, 则每装一次弹调用一次该函数
  307. /// </summary>
  308. protected virtual void OnReload()
  309. {
  310. }
  311.  
  312. /// <summary>
  313. /// 当开始换弹时调用
  314. /// </summary>
  315. protected virtual void OnBeginReload()
  316. {
  317. }
  318. /// <summary>
  319. /// 当换弹完成时调用
  320. /// </summary>
  321. protected virtual void OnReloadFinish()
  322. {
  323. }
  324.  
  325. /// <summary>
  326. /// 当武器被拾起时调用
  327. /// </summary>
  328. /// <param name="master">拾起该武器的角色</param>
  329. protected virtual void OnPickUp(Role master)
  330. {
  331. }
  332.  
  333. /// <summary>
  334. /// 当武器从武器袋中移除时调用
  335. /// </summary>
  336. protected virtual void OnRemove()
  337. {
  338. }
  339.  
  340. /// <summary>
  341. /// 当武器被激活时调用, 也就是使用当武器时调用
  342. /// </summary>
  343. protected virtual void OnActive()
  344. {
  345. }
  346.  
  347. /// <summary>
  348. /// 当武器被收起时调用
  349. /// </summary>
  350. protected virtual void OnConceal()
  351. {
  352. }
  353.  
  354. /// <summary>
  355. /// 射击时调用, 返回消耗弹药数量, 默认为1, 如果返回为 0, 则不消耗弹药
  356. /// </summary>
  357. protected virtual int UseAmmoCount()
  358. {
  359. return 1;
  360. }
  361.  
  362. public override void EnterTree()
  363. {
  364. base.EnterTree();
  365. //收集落在地上的武器
  366. if (IsInGround())
  367. {
  368. World.Weapon_UnclaimedWeapons.Add(this);
  369. }
  370. }
  371.  
  372. public override void ExitTree()
  373. {
  374. base.ExitTree();
  375. World.Weapon_UnclaimedWeapons.Remove(this);
  376. }
  377.  
  378. protected override void Process(float delta)
  379. {
  380. //未开火时间
  381. _noAttackTime += delta;
  382. //这把武器被扔在地上, 或者当前武器没有被使用
  383. if (Master == null || Master.Holster.ActiveWeapon != this)
  384. {
  385. //_triggerTimer
  386. _triggerTimer = _triggerTimer > 0 ? _triggerTimer - delta : 0;
  387. //攻击冷却计时
  388. _attackTimer = _attackTimer > 0 ? _attackTimer - delta : 0;
  389. //武器的当前散射半径
  390. CurrScatteringRange = Mathf.Max(CurrScatteringRange - Attribute.ScatteringRangeBackSpeed * delta,
  391. Attribute.StartScatteringRange);
  392. //松开扳机
  393. if (_triggerFlag || _downTimer > 0)
  394. {
  395. UpTrigger();
  396. _downTimer = 0;
  397. }
  398. _triggerFlag = false;
  399.  
  400. //重置数据
  401. if (_dirtyFlag)
  402. {
  403. _dirtyFlag = false;
  404. _aloneReloadState = 0;
  405. Reloading = false;
  406. _reloadTimer = 0;
  407. _reloadUseTime = 0;
  408. _attackFlag = false;
  409. _continuousCount = 0;
  410. _delayedTime = 0;
  411. _upTimer = 0;
  412. _looseShootFlag = false;
  413. _chargeTime = 0;
  414. }
  415. }
  416. else //正在使用中
  417. {
  418. _dirtyFlag = true;
  419. //换弹
  420. if (Reloading)
  421. {
  422. //换弹用时
  423. _reloadUseTime += delta;
  424. _reloadTimer -= delta;
  425.  
  426. if (Attribute.AloneReload) //单独装弹模式
  427. {
  428. switch (_aloneReloadState)
  429. {
  430. case 0:
  431. GD.PrintErr("AloneReload状态错误!");
  432. break;
  433. case 1: //装第一颗子弹之前
  434. {
  435. if (_reloadTimer <= 0)
  436. {
  437. //开始装第一颗子弹
  438. _aloneReloadState = 2;
  439. ReloadHandler();
  440. }
  441. _aloneReloadStop = false;
  442. }
  443. break;
  444. case 2: //单独装弹中
  445. {
  446. if (_reloadTimer <= 0)
  447. {
  448. ReloadSuccess();
  449. if (_aloneReloadStop || ResidueAmmo == 0 || CurrAmmo == Attribute.AmmoCapacity) //单独装弹完成
  450. {
  451. AloneReloadStateFinish();
  452. if (Attribute.AloneReloadFinishIntervalTime <= 0)
  453. {
  454. //换弹完成
  455. StopReloadState();
  456. ReloadFinishHandler();
  457. }
  458. else
  459. {
  460. _reloadTimer = Attribute.AloneReloadFinishIntervalTime;
  461. _aloneReloadState = 3;
  462. }
  463. }
  464. }
  465. }
  466. break;
  467. case 3: //单独装弹完成
  468. {
  469. //播放换弹完成音效
  470. if (!_playReloadFinishSoundFlag && Attribute.ReloadFinishSound != null && _reloadTimer <= Attribute.ReloadFinishSoundAdvanceTime)
  471. {
  472. _playReloadFinishSoundFlag = true;
  473. // GD.Print("播放换弹完成音效.");
  474. PlayReloadFinishSound();
  475. }
  476. if (_reloadTimer <= 0)
  477. {
  478. //换弹完成
  479. StopReloadState();
  480. ReloadFinishHandler();
  481. }
  482. _aloneReloadStop = false;
  483. }
  484. break;
  485. }
  486. }
  487. else //普通换弹模式
  488. {
  489. //播放换弹完成音效
  490. if (!_playReloadFinishSoundFlag && Attribute.ReloadFinishSound != null && _reloadTimer <= Attribute.ReloadFinishSoundAdvanceTime)
  491. {
  492. _playReloadFinishSoundFlag = true;
  493. // GD.Print("播放换弹完成音效.");
  494. PlayReloadFinishSound();
  495. }
  496.  
  497. if (_reloadTimer <= 0)
  498. {
  499. ReloadSuccess();
  500. }
  501. }
  502. }
  503.  
  504. // 攻击的计时器
  505. if (_attackTimer > 0)
  506. {
  507. _attackTimer -= delta;
  508. if (_attackTimer < 0)
  509. {
  510. _delayedTime += _attackTimer;
  511. _attackTimer = 0;
  512. //枪口默认角度
  513. RotationDegrees = -Attribute.DefaultAngle;
  514. }
  515. }
  516. else if (_delayedTime > 0) //攻击延时
  517. {
  518. _delayedTime -= delta;
  519. if (_attackTimer < 0)
  520. {
  521. _delayedTime = 0;
  522. }
  523. }
  524. //扳机判定
  525. if (_triggerFlag)
  526. {
  527. if (_looseShootFlag) //蓄力时长
  528. {
  529. _chargeTime += delta;
  530. }
  531.  
  532. _downTimer += delta;
  533. if (_upTimer > 0) //第一帧按下扳机
  534. {
  535. DownTrigger();
  536. _upTimer = 0;
  537. }
  538. }
  539. else
  540. {
  541. _upTimer += delta;
  542. if (_downTimer > 0) //第一帧松开扳机
  543. {
  544. UpTrigger();
  545. _downTimer = 0;
  546. }
  547. }
  548.  
  549. //连发判断
  550. if (!_looseShootFlag && _continuousCount > 0 && _delayedTime <= 0 && _attackTimer <= 0)
  551. {
  552. //连发开火
  553. TriggerFire();
  554. }
  555.  
  556. //散射值销退
  557. if (_noAttackTime >= Attribute.ScatteringRangeBackDelayTime)
  558. {
  559. CurrScatteringRange = Mathf.Max(CurrScatteringRange - Attribute.ScatteringRangeBackSpeed * delta,
  560. Attribute.StartScatteringRange);
  561. }
  562.  
  563. _triggerTimer = _triggerTimer > 0 ? _triggerTimer - delta : 0;
  564. _triggerFlag = false;
  565. _attackFlag = false;
  566. //武器身回归
  567. //Position = Position.MoveToward(Vector2.Zero, Attribute.BacklashRegressionSpeed * delta).Rotated(Rotation);
  568. _currBacklashLength = Mathf.MoveToward(_currBacklashLength, 0, Attribute.BacklashRegressionSpeed * delta);
  569. Position = new Vector2(_currBacklashLength, 0).Rotated(Rotation);
  570. if (_attackTimer > 0)
  571. {
  572. RotationDegrees = Mathf.Lerp(
  573. _fireAngle, -Attribute.DefaultAngle,
  574. Mathf.Clamp((_fireInterval - _attackTimer) * Attribute.UpliftAngleRestore / _fireInterval, 0, 1)
  575. );
  576. }
  577. }
  578. }
  579.  
  580. /// <summary>
  581. /// 返回武器是否在地上
  582. /// </summary>
  583. /// <returns></returns>
  584. public bool IsInGround()
  585. {
  586. return Master == null && GetParent() == GameApplication.Instance.World.NormalLayer;
  587. }
  588. /// <summary>
  589. /// 扳机函数, 调用即视为按下扳机
  590. /// </summary>
  591. public void Trigger()
  592. {
  593. //这一帧已经按过了, 不需要再按下
  594. if (_triggerFlag) return;
  595. //是否第一帧按下
  596. var justDown = _downTimer == 0;
  597. //是否能发射
  598. var flag = false;
  599. if (_continuousCount <= 0) //不能处于连发状态下
  600. {
  601. if (Attribute.ContinuousShoot) //自动射击
  602. {
  603. if (_triggerTimer > 0)
  604. {
  605. if (_continuousShootFlag)
  606. {
  607. flag = true;
  608. }
  609. }
  610. else
  611. {
  612. flag = true;
  613. if (_delayedTime <= 0 && _attackTimer <= 0)
  614. {
  615. _continuousShootFlag = true;
  616. }
  617. }
  618. }
  619. else //半自动
  620. {
  621. if (justDown && _triggerTimer <= 0 && _attackTimer <= 0)
  622. {
  623. flag = true;
  624. }
  625. }
  626. }
  627.  
  628. if (flag)
  629. {
  630. var fireFlag = true; //是否能开火
  631. if (Reloading) //换弹中
  632. {
  633. fireFlag = false;
  634. if (CurrAmmo > 0 && Attribute.AloneReload && Attribute.AloneReloadCanShoot)
  635. {
  636. //检查是否允许停止换弹
  637. if (_aloneReloadState == 2 || _aloneReloadState == 1)
  638. {
  639. //强制结束
  640. _aloneReloadStop = true;
  641. }
  642. }
  643. }
  644. else if (CurrAmmo <= 0) //子弹不够
  645. {
  646. fireFlag = false;
  647. if (justDown)
  648. {
  649. //第一帧按下, 触发换弹
  650. Reload();
  651. }
  652. }
  653.  
  654. if (fireFlag)
  655. {
  656. if (justDown)
  657. {
  658. //开火前延时
  659. if (!Attribute.LooseShoot)
  660. {
  661. _delayedTime = Attribute.DelayedTime;
  662. }
  663. //扳机按下间隔
  664. _triggerTimer = Attribute.TriggerInterval;
  665. //连发数量
  666. if (!Attribute.ContinuousShoot)
  667. {
  668. _continuousCount =
  669. Utils.RandomRangeInt(Attribute.MinContinuousCount, Attribute.MaxContinuousCount);
  670. }
  671. }
  672.  
  673. if (_delayedTime <= 0 && _attackTimer <= 0)
  674. {
  675. if (Attribute.LooseShoot) //松发开火
  676. {
  677. _looseShootFlag = true;
  678. OnStartCharge();
  679. }
  680. else
  681. {
  682. //开火
  683. TriggerFire();
  684. }
  685. }
  686.  
  687. _attackFlag = true;
  688. }
  689.  
  690. }
  691.  
  692. _triggerFlag = true;
  693. }
  694.  
  695. /// <summary>
  696. /// 返回是否按下扳机
  697. /// </summary>
  698. public bool IsPressTrigger()
  699. {
  700. return _triggerFlag;
  701. }
  702. /// <summary>
  703. /// 获取本次扳机按下的时长, 单位: 秒
  704. /// </summary>
  705. public float GetTriggerDownTime()
  706. {
  707. return _downTimer;
  708. }
  709.  
  710. /// <summary>
  711. /// 获取扳机蓄力时长, 计算按下扳机后从可以开火到当前一共经过了多长时间, 可用于计算蓄力攻击
  712. /// 注意, 该函数仅在 Attribute.LooseShoot == false 时有正确的返回值, 否则返回 0
  713. /// </summary>
  714. public float GetTriggerChargeTime()
  715. {
  716. return _chargeTime;
  717. }
  718. /// <summary>
  719. /// 获取延时射击倒计时, 单位: 秒
  720. /// </summary>
  721. public float GetDelayedAttackTime()
  722. {
  723. return _delayedTime;
  724. }
  725. /// <summary>
  726. /// 刚按下扳机
  727. /// </summary>
  728. private void DownTrigger()
  729. {
  730. OnDownTrigger();
  731. }
  732.  
  733. /// <summary>
  734. /// 刚松开扳机
  735. /// </summary>
  736. private void UpTrigger()
  737. {
  738. _continuousShootFlag = false;
  739. if (_delayedTime > 0)
  740. {
  741. _continuousCount = 0;
  742. }
  743.  
  744. //松发开火执行
  745. if (_looseShootFlag)
  746. {
  747. _looseShootFlag = false;
  748. if (_chargeTime >= Attribute.MinChargeTime) //判断蓄力是否够了
  749. {
  750. TriggerFire();
  751. }
  752. else //不能攻击
  753. {
  754. _continuousCount = 0;
  755. }
  756. _chargeTime = 0;
  757. }
  758.  
  759. OnUpTrigger();
  760. }
  761.  
  762. /// <summary>
  763. /// 触发开火
  764. /// </summary>
  765. private void TriggerFire()
  766. {
  767. _noAttackTime = 0;
  768. _continuousCount = _continuousCount > 0 ? _continuousCount - 1 : 0;
  769.  
  770. //减子弹数量
  771. if (_playerWeaponAttribute != _weaponAttribute) //Ai使用该武器, 有一定概率不消耗弹药
  772. {
  773. if (Utils.RandomRangeFloat(0, 1) < _weaponAttribute.AiAmmoConsumptionProbability) //触发消耗弹药
  774. {
  775. CurrAmmo -= UseAmmoCount();
  776. }
  777. }
  778. else
  779. {
  780. CurrAmmo -= UseAmmoCount();
  781. }
  782.  
  783. //开火间隙
  784. _fireInterval = 60 / Attribute.StartFiringSpeed;
  785. //攻击冷却
  786. _attackTimer += _fireInterval;
  787.  
  788. //播放开火动画
  789. if (IsAutoPlaySpriteFrames)
  790. {
  791. PlaySpriteAnimation(AnimatorNames.Fire);
  792. }
  793.  
  794. //播放射击音效
  795. PlayShootSound();
  796. //触发开火函数
  797. OnFire();
  798.  
  799. //播放上膛动画
  800. if (IsAutoPlaySpriteFrames)
  801. {
  802. if (Attribute.EquipSoundDelayTime <= 0)
  803. {
  804. PlaySpriteAnimation(AnimatorNames.Equip);
  805. }
  806. else
  807. {
  808. CallDelay(Attribute.EquipSoundDelayTime, PlaySpriteAnimation, AnimatorNames.Equip);
  809. }
  810. }
  811.  
  812. //播放上膛音效
  813. PlayEquipSound();
  814.  
  815. //开火发射的子弹数量
  816. var bulletCount = Utils.RandomRangeInt(Attribute.MaxFireBulletCount, Attribute.MinFireBulletCount);
  817. //武器口角度
  818. var angle = new Vector2(GameConfig.ScatteringDistance, CurrScatteringRange).Angle();
  819.  
  820. //先算武器口方向
  821. var tempRotation = Utils.RandomRangeFloat(-angle, angle);
  822. var tempAngle = Mathf.RadToDeg(tempRotation);
  823.  
  824. //开火时枪口角度
  825. var fireRotation = Mathf.DegToRad(Master.MountPoint.RealRotationDegrees) + tempRotation;
  826. //创建子弹
  827. for (int i = 0; i < bulletCount; i++)
  828. {
  829. //发射子弹
  830. OnShoot(fireRotation);
  831. }
  832.  
  833. //开火添加散射值
  834. CurrScatteringRange = Mathf.Min(CurrScatteringRange + Attribute.ScatteringRangeAddValue,
  835. Attribute.FinalScatteringRange);
  836. //武器的旋转角度
  837. tempAngle -= Attribute.UpliftAngle;
  838. RotationDegrees = tempAngle;
  839. _fireAngle = tempAngle;
  840. //武器身位置
  841. var max = Mathf.Abs(Mathf.Max(Attribute.MaxBacklash, Attribute.MinBacklash));
  842. _currBacklashLength = Mathf.Clamp(
  843. _currBacklashLength - Utils.RandomRangeFloat(Attribute.MinBacklash, Attribute.MaxBacklash),
  844. -max, max
  845. );
  846. Position = new Vector2(_currBacklashLength, 0).Rotated(Rotation);
  847.  
  848. if (FireEvent != null)
  849. {
  850. FireEvent(this);
  851. }
  852. }
  853.  
  854. /// <summary>
  855. /// 获取武器攻击的目标层级
  856. /// </summary>
  857. /// <returns></returns>
  858. public uint GetAttackLayer()
  859. {
  860. return Master != null ? Master.AttackLayer : Role.DefaultAttackLayer;
  861. }
  862. /// <summary>
  863. /// 返回弹药是否到达上限
  864. /// </summary>
  865. public bool IsAmmoFull()
  866. {
  867. return CurrAmmo + ResidueAmmo >= Attribute.MaxAmmoCapacity;
  868. }
  869.  
  870. /// <summary>
  871. /// 返回弹夹是否打空
  872. /// </summary>
  873. public bool IsAmmoEmpty()
  874. {
  875. return CurrAmmo == 0;
  876. }
  877. /// <summary>
  878. /// 返回是否弹药耗尽
  879. /// </summary>
  880. public bool IsTotalAmmoEmpty()
  881. {
  882. return CurrAmmo + ResidueAmmo == 0;
  883. }
  884.  
  885. /// <summary>
  886. /// 强制修改当前弹夹弹药量
  887. /// </summary>
  888. public void SetCurrAmmo(int count)
  889. {
  890. CurrAmmo = Mathf.Clamp(count, 0, Attribute.AmmoCapacity);
  891. }
  892.  
  893. /// <summary>
  894. /// 强制修改备用弹药量
  895. /// </summary>
  896. public void SetResidueAmmo(int count)
  897. {
  898. ResidueAmmo = Mathf.Clamp(count, 0, Attribute.MaxAmmoCapacity - CurrAmmo);
  899. }
  900. /// <summary>
  901. /// 强制修改弹药量, 优先改动备用弹药
  902. /// </summary>
  903. public void SetTotalAmmo(int total)
  904. {
  905. if (total < 0)
  906. {
  907. return;
  908. }
  909. var totalAmmo = CurrAmmo + ResidueAmmo;
  910. if (totalAmmo == total)
  911. {
  912. return;
  913. }
  914. if (total > totalAmmo) //弹药增加
  915. {
  916. ResidueAmmo = Mathf.Min(total - CurrAmmo, Attribute.MaxAmmoCapacity - CurrAmmo);
  917. }
  918. else //弹药减少
  919. {
  920. if (CurrAmmo < total)
  921. {
  922. ResidueAmmo = total - CurrAmmo;
  923. }
  924. else
  925. {
  926. CurrAmmo = total;
  927. ResidueAmmo = 0;
  928. }
  929. }
  930. }
  931.  
  932. /// <summary>
  933. /// 拾起的弹药数量, 如果到达容量上限, 则返回拾取完毕后剩余的弹药数量
  934. /// </summary>
  935. /// <param name="count">弹药数量</param>
  936. private int PickUpAmmo(int count)
  937. {
  938. var num = ResidueAmmo;
  939. ResidueAmmo = Mathf.Min(ResidueAmmo + count, Attribute.MaxAmmoCapacity - CurrAmmo);
  940. return count - ResidueAmmo + num;
  941. }
  942.  
  943. /// <summary>
  944. /// 触发换弹
  945. /// </summary>
  946. public void Reload()
  947. {
  948. if (CurrAmmo < Attribute.AmmoCapacity && ResidueAmmo > 0 && !Reloading)
  949. {
  950. Reloading = true;
  951. _playReloadFinishSoundFlag = false;
  952.  
  953. //播放开始换弹音效
  954. PlayBeginReloadSound();
  955. // GD.Print("开始换弹.");
  956. //第一次换弹
  957. OnBeginReload();
  958.  
  959. if (Attribute.AloneReload)
  960. {
  961. //单独换弹, 特殊处理
  962. AloneReloadHandler();
  963. }
  964. else
  965. {
  966. //普通换弹处理
  967. ReloadHandler();
  968. }
  969. }
  970. }
  971.  
  972. //播放换弹开始音效
  973. private void PlayBeginReloadSound()
  974. {
  975. if (Attribute.BeginReloadSound != null)
  976. {
  977. var position = GameApplication.Instance.ViewToGlobalPosition(GlobalPosition);
  978. if (Attribute.BeginReloadSoundDelayTime <= 0)
  979. {
  980. SoundManager.PlaySoundEffectPosition(Attribute.BeginReloadSound.Path, position, Attribute.BeginReloadSound.Volume);
  981. }
  982. else
  983. {
  984. SoundManager.PlaySoundEffectPositionDelay(Attribute.BeginReloadSound.Path, position, Attribute.BeginReloadSoundDelayTime, Attribute.BeginReloadSound.Volume);
  985. }
  986. }
  987. }
  988. //播放换弹音效
  989. private void PlayReloadSound()
  990. {
  991. if (Attribute.ReloadSound != null)
  992. {
  993. var position = GameApplication.Instance.ViewToGlobalPosition(GlobalPosition);
  994. if (Attribute.ReloadSoundDelayTime <= 0)
  995. {
  996. SoundManager.PlaySoundEffectPosition(Attribute.ReloadSound.Path, position, Attribute.ReloadSound.Volume);
  997. }
  998. else
  999. {
  1000. SoundManager.PlaySoundEffectPositionDelay(Attribute.ReloadSound.Path, position, Attribute.ReloadSoundDelayTime, Attribute.ReloadSound.Volume);
  1001. }
  1002. }
  1003. }
  1004. //播放换弹完成音效
  1005. private void PlayReloadFinishSound()
  1006. {
  1007. if (Attribute.ReloadFinishSound != null)
  1008. {
  1009. var position = GameApplication.Instance.ViewToGlobalPosition(GlobalPosition);
  1010. SoundManager.PlaySoundEffectPosition(Attribute.ReloadFinishSound.Path, position, Attribute.ReloadFinishSound.Volume);
  1011. }
  1012. }
  1013.  
  1014. //播放射击音效
  1015. private void PlayShootSound()
  1016. {
  1017. if (Attribute.ShootSound != null)
  1018. {
  1019. var position = GameApplication.Instance.ViewToGlobalPosition(GlobalPosition);
  1020. SoundManager.PlaySoundEffectPosition(Attribute.ShootSound.Path, position, Attribute.ShootSound.Volume);
  1021. }
  1022. }
  1023.  
  1024. //播放上膛音效
  1025. private void PlayEquipSound()
  1026. {
  1027. if (Attribute.EquipSound != null)
  1028. {
  1029. var position = GameApplication.Instance.ViewToGlobalPosition(GlobalPosition);
  1030. if (Attribute.EquipSoundDelayTime <= 0)
  1031. {
  1032. SoundManager.PlaySoundEffectPosition(Attribute.EquipSound.Path, position, Attribute.EquipSound.Volume);
  1033. }
  1034. else
  1035. {
  1036. SoundManager.PlaySoundEffectPositionDelay(Attribute.EquipSound.Path, position, Attribute.EquipSoundDelayTime, Attribute.EquipSound.Volume);
  1037. }
  1038. }
  1039. }
  1040.  
  1041. //单独换弹处理
  1042. private void AloneReloadHandler()
  1043. {
  1044. if (Attribute.AloneReloadBeginIntervalTime <= 0)
  1045. {
  1046. //开始装第一颗子弹
  1047. _aloneReloadState = 2;
  1048. ReloadHandler();
  1049. }
  1050. else
  1051. {
  1052. _aloneReloadState = 1;
  1053. _reloadTimer = Attribute.AloneReloadBeginIntervalTime;
  1054. }
  1055. }
  1056.  
  1057. //换弹处理逻辑
  1058. private void ReloadHandler()
  1059. {
  1060. _reloadTimer = Attribute.ReloadTime;
  1061. //播放换弹动画
  1062. if (IsAutoPlaySpriteFrames)
  1063. {
  1064. PlaySpriteAnimation(AnimatorNames.Reloading);
  1065. }
  1066. //播放换弹音效
  1067. PlayReloadSound();
  1068. OnReload();
  1069. // GD.Print("装弹.");
  1070. }
  1071. //换弹完成处理逻辑
  1072. private void ReloadFinishHandler()
  1073. {
  1074. // GD.Print("装弹完成.");
  1075. OnReloadFinish();
  1076. }
  1077.  
  1078. //单独装弹完成
  1079. private void AloneReloadStateFinish()
  1080. {
  1081. // GD.Print("单独装弹完成.");
  1082. }
  1083.  
  1084. //停止当前的换弹状态
  1085. private void StopReloadState()
  1086. {
  1087. _aloneReloadState = 0;
  1088. Reloading = false;
  1089. _reloadTimer = 0;
  1090. _reloadUseTime = 0;
  1091. }
  1092.  
  1093. /// <summary>
  1094. /// 换弹计时器时间到, 执行换弹操作
  1095. /// </summary>
  1096. private void ReloadSuccess()
  1097. {
  1098. if (Attribute.AloneReload) //单独装填
  1099. {
  1100. if (ResidueAmmo >= Attribute.AloneReloadCount) //剩余子弹充足
  1101. {
  1102. if (CurrAmmo + Attribute.AloneReloadCount <= Attribute.AmmoCapacity)
  1103. {
  1104. ResidueAmmo -= Attribute.AloneReloadCount;
  1105. CurrAmmo += Attribute.AloneReloadCount;
  1106. }
  1107. else //子弹满了
  1108. {
  1109. var num = Attribute.AmmoCapacity - CurrAmmo;
  1110. CurrAmmo = Attribute.AmmoCapacity;
  1111. ResidueAmmo -= num;
  1112. }
  1113. }
  1114. else if (ResidueAmmo != 0) //剩余子弹不足
  1115. {
  1116. if (ResidueAmmo + CurrAmmo <= Attribute.AmmoCapacity)
  1117. {
  1118. CurrAmmo += ResidueAmmo;
  1119. ResidueAmmo = 0;
  1120. }
  1121. else //子弹满了
  1122. {
  1123. var num = Attribute.AmmoCapacity - CurrAmmo;
  1124. CurrAmmo = Attribute.AmmoCapacity;
  1125. ResidueAmmo -= num;
  1126. }
  1127. }
  1128.  
  1129. if (!_aloneReloadStop && ResidueAmmo != 0 && CurrAmmo != Attribute.AmmoCapacity) //继续装弹
  1130. {
  1131. ReloadHandler();
  1132. }
  1133. }
  1134. else //换弹结束
  1135. {
  1136. if (CurrAmmo + ResidueAmmo >= Attribute.AmmoCapacity)
  1137. {
  1138. ResidueAmmo -= Attribute.AmmoCapacity - CurrAmmo;
  1139. CurrAmmo = Attribute.AmmoCapacity;
  1140. }
  1141. else
  1142. {
  1143. CurrAmmo += ResidueAmmo;
  1144. ResidueAmmo = 0;
  1145. }
  1146.  
  1147. StopReloadState();
  1148. ReloadFinishHandler();
  1149. }
  1150. }
  1151. //播放动画
  1152. private void PlaySpriteAnimation(string name)
  1153. {
  1154. var spriteFrames = AnimatedSprite.SpriteFrames;
  1155. if (spriteFrames != null && spriteFrames.HasAnimation(name))
  1156. {
  1157. AnimatedSprite.Play(name);
  1158. }
  1159. }
  1160.  
  1161. //帧动画播放结束
  1162. private void OnAnimationFinished()
  1163. {
  1164. // GD.Print("帧动画播放结束...");
  1165. AnimatedSprite.Play(AnimatorNames.Default);
  1166. }
  1167.  
  1168. public override CheckInteractiveResult CheckInteractive(ActivityObject master)
  1169. {
  1170. var result = new CheckInteractiveResult(this);
  1171.  
  1172. if (master is Role roleMaster) //碰到角色
  1173. {
  1174. if (Master == null)
  1175. {
  1176. var masterWeapon = roleMaster.Holster.ActiveWeapon;
  1177. //查找是否有同类型武器
  1178. var index = roleMaster.Holster.FindWeapon(ItemId);
  1179. if (index != -1) //如果有这个武器
  1180. {
  1181. if (CurrAmmo + ResidueAmmo != 0) //子弹不为空
  1182. {
  1183. var targetWeapon = roleMaster.Holster.GetWeapon(index);
  1184. if (!targetWeapon.IsAmmoFull()) //背包里面的武器子弹未满
  1185. {
  1186. //可以互动拾起弹药
  1187. result.CanInteractive = true;
  1188. result.Message = Attribute.Name;
  1189. result.ShowIcon = ResourcePath.resource_sprite_ui_icon_icon_bullet_png;
  1190. return result;
  1191. }
  1192. }
  1193. }
  1194. else //没有武器
  1195. {
  1196. if (roleMaster.Holster.CanPickupWeapon(this)) //能拾起武器
  1197. {
  1198. //可以互动, 拾起武器
  1199. result.CanInteractive = true;
  1200. result.Message = Attribute.Name;
  1201. result.ShowIcon = ResourcePath.resource_sprite_ui_icon_icon_pickup_png;
  1202. return result;
  1203. }
  1204. else if (masterWeapon != null && masterWeapon.Attribute.WeightType == Attribute.WeightType) //替换武器
  1205. {
  1206. //可以互动, 切换武器
  1207. result.CanInteractive = true;
  1208. result.Message = Attribute.Name;
  1209. result.ShowIcon = ResourcePath.resource_sprite_ui_icon_icon_replace_png;
  1210. return result;
  1211. }
  1212. }
  1213. }
  1214. }
  1215.  
  1216. return result;
  1217. }
  1218.  
  1219. public override void Interactive(ActivityObject master)
  1220. {
  1221. if (master is Role roleMaster) //与role互动
  1222. {
  1223. var holster = roleMaster.Holster;
  1224. //查找是否有同类型武器
  1225. var index = holster.FindWeapon(ItemId);
  1226. if (index != -1) //如果有这个武器
  1227. {
  1228. if (CurrAmmo + ResidueAmmo == 0) //没有子弹了
  1229. {
  1230. return;
  1231. }
  1232.  
  1233. var weapon = holster.GetWeapon(index);
  1234. //子弹上限
  1235. var maxCount = Attribute.MaxAmmoCapacity;
  1236. //是否捡到子弹
  1237. var flag = false;
  1238. if (ResidueAmmo > 0 && weapon.CurrAmmo + weapon.ResidueAmmo < maxCount)
  1239. {
  1240. var count = weapon.PickUpAmmo(ResidueAmmo);
  1241. if (count != ResidueAmmo)
  1242. {
  1243. ResidueAmmo = count;
  1244. flag = true;
  1245. }
  1246. }
  1247.  
  1248. if (CurrAmmo > 0 && weapon.CurrAmmo + weapon.ResidueAmmo < maxCount)
  1249. {
  1250. var count = weapon.PickUpAmmo(CurrAmmo);
  1251. if (count != CurrAmmo)
  1252. {
  1253. CurrAmmo = count;
  1254. flag = true;
  1255. }
  1256. }
  1257.  
  1258. //播放互动效果
  1259. if (flag)
  1260. {
  1261. Throw(GlobalPosition, 0, Utils.RandomRangeInt(20, 50), Vector2.Zero, Utils.RandomRangeInt(-180, 180));
  1262. }
  1263. }
  1264. else //没有武器
  1265. {
  1266. if (holster.PickupWeapon(this) == -1)
  1267. {
  1268. //替换武器
  1269. roleMaster.ThrowWeapon();
  1270. roleMaster.PickUpWeapon(this);
  1271. }
  1272. }
  1273. }
  1274. }
  1275.  
  1276. /// <summary>
  1277. /// 获取当前武器真实的旋转角度(弧度制), 由于武器旋转时加入了旋转吸附, 所以需要通过该函数来来知道当前武器的真实旋转角度
  1278. /// </summary>
  1279. public float GetRealGlobalRotation()
  1280. {
  1281. return Mathf.DegToRad(Master.MountPoint.RealRotationDegrees) + Rotation;
  1282. }
  1283.  
  1284. /// <summary>
  1285. /// 触发扔掉武器抛出的效果, 并不会管武器是否在武器袋中
  1286. /// </summary>
  1287. /// <param name="master">触发扔掉该武器的的角色</param>
  1288. public void ThrowWeapon(Role master)
  1289. {
  1290. ThrowWeapon(master, master.GlobalPosition);
  1291. }
  1292.  
  1293. /// <summary>
  1294. /// 触发扔掉武器抛出的效果, 并不会管武器是否在武器袋中
  1295. /// </summary>
  1296. /// <param name="master">触发扔掉该武器的的角色</param>
  1297. /// <param name="startPosition">投抛起始位置</param>
  1298. public void ThrowWeapon(Role master, Vector2 startPosition)
  1299. {
  1300. //阴影偏移
  1301. ShadowOffset = new Vector2(0, 2);
  1302.  
  1303. if (master.Face == FaceDirection.Left)
  1304. {
  1305. Scale *= new Vector2(1, -1);
  1306. }
  1307.  
  1308. var rotation = master.MountPoint.GlobalRotation;
  1309. GlobalRotation = rotation;
  1310. //继承role的移动速度
  1311. InheritVelocity(master);
  1312.  
  1313. startPosition -= GripPoint.Position.Rotated(rotation);
  1314. var startHeight = -master.MountPoint.Position.Y;
  1315. var velocity = new Vector2(20, 0).Rotated(rotation);
  1316. var yf = Utils.RandomRangeInt(50, 70);
  1317. Throw(startPosition, startHeight, yf, velocity, 0);
  1318. }
  1319.  
  1320. protected override void OnThrowStart()
  1321. {
  1322. //禁用碰撞
  1323. //Collision.Disabled = true;
  1324. AnimationPlayer.Play(AnimatorNames.Floodlight);
  1325. }
  1326.  
  1327. protected override void OnThrowOver()
  1328. {
  1329. //启用碰撞
  1330. //Collision.Disabled = false;
  1331. AnimationPlayer.Play(AnimatorNames.Floodlight);
  1332. }
  1333.  
  1334. /// <summary>
  1335. /// 触发拾起到 Holster, 这个函数由 Holster 对象调用
  1336. /// </summary>
  1337. public void PickUpWeapon(Role master)
  1338. {
  1339. Master = master;
  1340. if (master.IsAi)
  1341. {
  1342. _weaponAttribute = _aiWeaponAttribute;
  1343. }
  1344. else
  1345. {
  1346. _weaponAttribute = _playerWeaponAttribute;
  1347. }
  1348. //停止动画
  1349. AnimationPlayer.Stop();
  1350. //清除泛白效果
  1351. SetBlendSchedule(0);
  1352. ZIndex = 0;
  1353. //禁用碰撞
  1354. //Collision.Disabled = true;
  1355. //精灵位置
  1356. _tempAnimatedSpritePosition = AnimatedSprite.Position;
  1357. var position = GripPoint.Position;
  1358. AnimatedSprite.Position = new Vector2(-position.X, -position.Y);
  1359. //修改层级
  1360. _tempLayer = CollisionLayer;
  1361. CollisionLayer = PhysicsLayer.OnHand;
  1362. //清除 Ai 拾起标记
  1363. RemoveSign(SignNames.AiFindWeaponSign);
  1364. OnPickUp(master);
  1365. }
  1366.  
  1367. /// <summary>
  1368. /// 触发从 Holster 中移除, 这个函数由 Holster 对象调用
  1369. /// </summary>
  1370. public void RemoveAt()
  1371. {
  1372. Master = null;
  1373. CollisionLayer = _tempLayer;
  1374. _weaponAttribute = _playerWeaponAttribute;
  1375. AnimatedSprite.Position = _tempAnimatedSpritePosition;
  1376. //清除 Ai 拾起标记
  1377. RemoveSign(SignNames.AiFindWeaponSign);
  1378. OnRemove();
  1379. }
  1380.  
  1381. /// <summary>
  1382. /// 触发启用武器
  1383. /// </summary>
  1384. public void Active()
  1385. {
  1386. //调整阴影
  1387. ShadowOffset = new Vector2(0, Master.GlobalPosition.Y - GlobalPosition.Y);
  1388. //枪口默认抬起角度
  1389. RotationDegrees = -Attribute.DefaultAngle;
  1390. ShowShadowSprite();
  1391. OnActive();
  1392. }
  1393.  
  1394. /// <summary>
  1395. /// 触发收起武器
  1396. /// </summary>
  1397. public void Conceal()
  1398. {
  1399. HideShadowSprite();
  1400. OnConceal();
  1401. }
  1402.  
  1403. //-------------------------- ----- 子弹相关 -----------------------------
  1404.  
  1405. /// <summary>
  1406. /// 投抛弹壳的默认实现方式, shellId为弹壳id
  1407. /// </summary>
  1408. protected ActivityObject ThrowShell(string shellId)
  1409. {
  1410. var shellPosition = Master.MountPoint.Position + ShellPoint.Position;
  1411. var startPos = ShellPoint.GlobalPosition;
  1412. var startHeight = -shellPosition.Y;
  1413. startPos.Y += startHeight;
  1414. var direction = GlobalRotationDegrees + Utils.RandomRangeInt(-30, 30) + 180;
  1415. var verticalSpeed = Utils.RandomRangeInt(60, 120);
  1416. var velocity = new Vector2(Utils.RandomRangeInt(20, 60), 0).Rotated(direction * Mathf.Pi / 180);
  1417. var rotate = Utils.RandomRangeInt(-720, 720);
  1418. var shell = Create(shellId);
  1419. shell.Rotation = Master.MountPoint.RealRotation;
  1420. shell.InheritVelocity(Master);
  1421. shell.Throw(startPos, startHeight, verticalSpeed, velocity, rotate);
  1422. return shell;
  1423. }
  1424.  
  1425. /// <summary>
  1426. /// 发射子弹的默认实现方式, bulletId为子弹id
  1427. /// </summary>
  1428. protected Bullet ShootBullet(float fireRotation, string bulletId)
  1429. {
  1430. //创建子弹
  1431. var bullet = Create<Bullet>(bulletId);
  1432. bullet.Init(
  1433. this,
  1434. Utils.RandomRangeFloat(Attribute.BulletMinSpeed, Attribute.BulletMaxSpeed),
  1435. Utils.RandomRangeFloat(Attribute.BulletMinDistance, Attribute.BulletMaxDistance),
  1436. FirePoint.GlobalPosition,
  1437. fireRotation + Mathf.DegToRad(Utils.RandomRangeFloat(Attribute.BulletMinDeviationAngle, Attribute.BulletMaxDeviationAngle)),
  1438. GetAttackLayer()
  1439. );
  1440. bullet.PutDown(RoomLayerEnum.YSortLayer);
  1441. return bullet;
  1442. }
  1443. //-------------------------------- Ai相关 -----------------------------
  1444.  
  1445. /// <summary>
  1446. /// 获取 Ai 对于该武器的评分, 评分越高, 代表 Ai 会越优先选择该武器, 如果为 -1, 则表示 Ai 不会使用该武器
  1447. /// </summary>
  1448. public float GetAiScore()
  1449. {
  1450. return 1;
  1451. }
  1452. }