using System; using System.Linq; using BITFALL.Guns.States; using BITFALL.Player.Equip; using BITFALL.Player.Movement; using UnityEngine; using BITKit; using BITKit.Entities; using BITKit.Entities.Melee; using UnityEngine.InputSystem; using BITKit.StateMachine; using Cysharp.Threading.Tasks; using Unity.Mathematics; using UnityEngine.InputSystem.Interactions; #if UNITY_EDITOR #endif namespace BITFALL.Guns { public interface IGunState : IState { } [System.Serializable] public abstract class GunState : IGunState { public BITGun root; [Inject] protected IEntityMovement _entityMovement; public bool Enabled { get;set; } public virtual void Initialize() { root.Entity.Inject(this); _entityMovement.OnStateChanged += OnMovementStateChanged; } public virtual void OnStateEntry(IState old) { } public virtual void OnStateExit(IState old, IState newState) { } public virtual void OnStateUpdate(float deltaTime) { } public virtual void OnMovementStateChanged(IEntityMovementState old, IEntityMovementState newState) { } public virtual void AnimationEvent(string name) { } } [System.Serializable] public class GunStateMachine : MonoStateMachine { } public class BITGun : BITEquipBase { //简单设置 [Header(Constant.Header.Settings)] [SerializeField] private Vector3 bulletInitialOffset; [SerializeField] private SpringEulerAngle recoilSpring=new(); [SerializeField] private float recoilPositionWeight=1; [Header(Constant.Header.Gameobjects)] [SerializeField] private Transform initialSight; [SerializeField] private Optional newSight; [Header(nameof(Optional))] [SerializeField] private Optional breathingAdditive; // 输入系统 [Header(Constant.Header.Input)] [SerializeField] internal InputActionReference fireAction; [SerializeField] internal InputActionReference aimAction; [SerializeField] internal InputActionReference reloadAction; [SerializeField] internal InputActionReference meleeAction; [SerializeField] internal InputActionReference steadyAimAction; [SerializeField] internal InputActionReference inspectAction; [Header(Constant.Header.HotFix)] [SerializeField] private Transform cameraView; // 引用组件 [Header(Constant.Header.Components)] [SerializeField] private LocationAdditive locationAdditive; [SerializeField] private LocationAdditive selfViewAdditive; // 引用预制体 [Header(Constant.Header.Prefabs)] // 内部变量burst [Header(Constant.Header.InternalVariables)] public ExpectState expectFiring; public ExpectState expectAiming; internal readonly IntervalUpdate fireInterval = new(0.32f); internal readonly IntervalUpdate burstFireInterval = new(0.1f); internal int burstFireCount; [Inject] private IEntityMovement _movement; [Inject] private IPlayerMovement _playerMovement; [Inject] private IHealth _health; [Inject] private IEquipService _equipService; internal AssetableGun _gun=>item as AssetableGun; private bool isHolstered; private bool isSteadyAim; private readonly DoubleBuffer cacheRounds = new(); public bool RequireBolt { get; set; } #region 接口实现 public override string AddressablePath => _gun.AddressablePath; #endregion public override void OnAwake() { base.OnAwake(); inputActionGroup.RegisterCallback(fireAction, OnFire); inputActionGroup.RegisterCallback(aimAction, OnAim); inputActionGroup.RegisterCallback(reloadAction, OnReload); inputActionGroup.RegisterCallback(meleeAction, OnMelee); if (inspectAction is not null) inputActionGroup.RegisterCallback(inspectAction, OnInspect); if (breathingAdditive.Allow) inputActionGroup.RegisterCallback(steadyAimAction, OnSteadyAim); _movement.OnStateChanged += OnMovementStateChanged; //_movement.OnCommand += OnMovementCommand; animator[0].onStateExit += (state) => { isHolstered = state is BITConstant.Player.Holster; }; BITAppForUnity.AllowCursor.AddListener(OnAllowCursor); destroyCancellationToken.Register(() => { BITAppForUnity.AllowCursor.RemoveListener(OnAllowCursor); }); } private void OnInspect(InputAction.CallbackContext obj) { switch (obj) { case {interaction:PressInteraction, performed: true} when CurrentState is Movement: animator.Play(BITConstant.Player.Inspect); break; } } private void OnAllowCursor(bool allow) { if (!allow) return; expectFiring.Reset(); expectAiming.Reset(); } private void OnSteadyAim(InputAction.CallbackContext obj) { switch (obj) { case { interaction: PressInteraction, performed: true } when CurrentState is Aim: isSteadyAim = true; break; case { interaction: PressInteraction, canceled: true }: isSteadyAim = false; break; } } // private void OnMovementCommand(object obj) // { // switch (obj) // { // case OnPlayerJumpCommand: // animator.Play("Jump"); // break; // } // } private void OnMelee(InputAction.CallbackContext obj) { switch (CurrentState) { case Equip: case Melee: return; } TransitionState(); } private void OnReload(InputAction.CallbackContext obj) { if (obj.JustPressed() is false) return; switch (CurrentState) { case Equip: case Melee: return; } if (Item?.TryGetProperty(out var clip) is false) { _uxPopup?.Popup("机匣损坏或不存在"); return; } if (_inventory.TryGetItem(IsClipReady, out var clipItem) is false) { _uxPopup?.Popup("没有兼容的弹夹"); return; } TransitionState(); } public override void Entry() { base.Entry(); _movement.ExecuteCommand(new PlayerLimitMoveSpeedCommand() { Id = BITHash.Player.Equip, Limit = true, Speed = _gun.InitialMovementSpeed }); } private bool IsClipReady(IBasicItem clip) { if(Item.TryGetProperty(out var _clip) is false) return false; if(clip.AddressablePath != _clip.AddressablePath) return false; if(clip.TryGetProperty(out var _clipItem) is false) return false; return _clipItem.Remaining > 0; } private void OnMovementStateChanged(IEntityMovementState arg1, IEntityMovementState arg2) { if (IsEntered is false) return; foreach (var x in StateDictionary.Values) { x.OnMovementStateChanged(arg1,arg2); } } public override void Exit() { base.Exit(); if (Item.TryGetProperty(out var clip) && cacheRounds.TryGetRelease(out var rounds)) { clip.Remaining = rounds; } cacheRounds.Clear(); } public override UniTask EntryAsync() { base.EntryAsync(); isHolstered = false; inputActionGroup.allowInput.AddElement(this); expectFiring.Reset(); Enabled = true; fireInterval.Interval = 1f / _gun.FireMode.FireRate; fireInterval.Reset(); if (_gun.FireMode is BurstFireMode burstFireMode) { burstFireInterval.Interval = burstFireMode.BurstFireInterval; burstFireInterval.Reset(); } TransitionState(); return UniTask.CompletedTask; } public override async UniTask ExitAsync() { TransitionState(); _movement.ExecuteCommand(new PlayerFocusCommand() { Focus = false, Sender = this }); _equipService.AllowAttack = false; inputActionGroup.allowInput.RemoveElement(this); expectFiring.Reset(); cameraView.localPosition = default; try { while (_health.IsAlive && isHolstered is false) { destroyCancellationToken.ThrowIfCancellationRequested(); _equipService.Zoom.Value = Mathf.MoveTowards(_equipService.Zoom.Value,0,Time.deltaTime); await UniTask.NextFrame(); } _equipService.Zoom.Allow = false; destroyCancellationToken.ThrowIfCancellationRequested(); _equipService.Stable = 1; await base.ExitAsync(); } catch (OperationCanceledException) { } } public override void Exited() { base.Exited(); _movement.ExecuteCommand(new PlayerLimitMoveSpeedCommand() { Id = BITHash.Player.Equip, Limit = false, Speed = _gun.InitialMovementSpeed }); } public override void OnUpdate(float deltaTime) { UpdateState(deltaTime); switch (_gun.FireMode) { case AutoFireMode: break; case SemiFireMode: break; case BurstFireMode when expectFiring.being: expectFiring.shouldBe = fireAction.action.WasPressedThisFrame(); if(burstFireInterval.AllowUpdate) { switch (AnimationProperties.TryGetValue(BITConstant.Player.AllowFire, out var allowFire)) { case false: case true when allowFire >0.9f: Fire(); break; } } break; } _movement.ExecuteCommand(new PlayerFocusCommand() { Focus = recoilSpring.value.GetLength() > 0.1f, Sender = this }); animator.animator.SetBool((int)BITHash.Player.IsGrounded,_movement.IsGrounded); animator.animator.SetBool((int)BITHash.Player.IsCrouched,_movement.CurrentState is IPlayerCrouchState or IPlayerSlideState); animator.animator.SetBool((int)BITHash.Player.Cancel, recoilSpring.value.GetLength()>0.1f); animator.animator.SetFloat((int)BITHash.Player.Aim,_equipService.Zoom.Value); animator.animator.SetFloat(BITConstant.Player.SqrMagnitude,_movement.LocomotionBasedVelocity.sqrMagnitude); recoilSpring.Update(deltaTime,default); if (_gun.TryGetProperty(out var recoil)) { locationAdditive.AddEuler(Vector3.Scale(recoilSpring.value,recoil.ViewRecoilScale) ); } if (selfViewAdditive) { var positionWeight = recoilPositionWeight; if (AnimationProperties.TryGetValue(BITConstant.Player.Aim, out var aim)) { positionWeight *= Mathf.Lerp(2, 1, aim); } selfViewAdditive.AddEuler(recoilSpring.value); selfViewAdditive.AddPosition( new Vector3( recoilSpring.value.y, -recoilSpring.value.x, recoilSpring.value.z ) * positionWeight ); } if(AnimationProperties.TryGetValue(BITConstant.Player.Aim, out var _aim)) { _equipService.Zoom.Allow = CurrentState is Aim; _equipService.Zoom.Value =Mathf.Lerp(1,_gun.InitialAimZoom, _aim); _equipService.AllowScope = _aim > 0.86f && _gun.IsScopeAim; if (breathingAdditive.Allow) { var breatheFrequency = 0.5f; // 呼吸的频率,值越大呼吸越快 var breatheAmplitude = 0.005f; // 呼吸的幅度,即准星上下移动的最大距离 if (_playerMovement.Stamina <= 8) { breatheFrequency = 8; breatheAmplitude = 0.1f; } var breatheOffset = Mathf.Sin(Time.time * breatheFrequency) * breatheAmplitude; if (isSteadyAim) { if (_playerMovement.Stamina > 0) { _playerMovement.Stamina -= 16 * deltaTime; breatheOffset *= 0.16f; } else { isSteadyAim = false; } } else { breatheOffset *= _aim; } _playerMovement.AddViewEuler(new float2(breatheOffset, breatheOffset)); } if (newSight.Allow) { transform.localPosition=Vector3.Lerp(default, newSight.Value.position-initialSight.position, _aim); } else { transform.localPosition = default; } } if (AnimationProperties.TryGetValue(BITConstant.Player.Stable, out var stable)) { _equipService.Stable = stable; } if(AnimationProperties.TryGetValue(BITConstant.Player.AllowFire, out var _allowFire)) { _equipService.AllowAttack = _allowFire > 0.9f; } AllowRendering.SetDisableElements(64564,_equipService.AllowScope); expectAiming.shouldBe = inputActionGroup.GetAction(aimAction).IsPressed(); } public override void AnimationEvent(string eventName) { if (IsEntered is false) return; base.AnimationEvent(eventName); CurrentState?.AnimationEvent(eventName); if (Item.TryGetProperty(out var clip) && _inventory.TryGetItem(IsClipReady, out var clipItem) && clipItem.TryGetProperty(out var nextClip) ) switch (eventName) { case BITConstant.Player.EjectClip: if(clip.Remaining <= 0) break; cacheRounds.Release(clip.Remaining); clip.Remaining = 0; break; case BITConstant.Player.InsertClip: cacheRounds.TryGetRelease(out var current); var newRound = nextClip.Remaining; var swapClipClone = nextClip.Clone().As(); var swapClip = clipItem.Clone().As(); swapClipClone.Remaining = current; if (swapClip.TrySetProperty(swapClipClone) is false || _inventory.TrySetItem(swapClip) is false) { _uxPopup?.Popup("弹匣回收失败"); clip.Remaining = current; break; } else { clip.Remaining = newRound; } // var newRound = Mathf.Clamp(current + nextClip.Remaining, 0,int.MaxValue); // // newRound = nextClip.Remaining; // // var newClipItem = clipItem.Clone() as IBasicItem; // nextClip = nextClip.Clone().As(); // // if(newRound>clip.Capacity) // { // newRound = clip.Capacity; // nextClip.Remaining = current + nextClip.Remaining - clip.Capacity; // } // else // { // nextClip.Remaining = 0; // } // if(newRound == 0) // { // _uxPopup?.Popup("空弹匣"); // break; // } // // newClipItem!.TrySetProperty(nextClip); // // if (_inventory.TrySetItem(newClipItem)) // { // clip.Remaining = newRound; // } // else // { // clip.Remaining = current; // _uxPopup?.Popup("弹匣回收失败"); // } break; } } public void Fire() { if (RequireBolt) return; if (Item.TryGetProperty(out var clip)) { if (clip.Remaining <= 0) { return; } else { clip.Remaining--; } } //播放射击动画 UnityEntity.Invoke(Constant.Animation.Play, BITConstant.Player.Fire); if (_gun.BuckShot.Allow) { InternalAddRecoil(); for (int i = 0; i < _gun.BuckShot.Value; i++) { InternalFire(); } } else { InternalFire(); InternalAddRecoil(); } //开火模式逻辑判断 switch (_gun.FireMode) { case AutoFireMode: break; case SemiFireMode: //如果是半自动开火,则取消射击 expectFiring.Reset(); break; case BurstFireMode burstFireMode: burstFireCount++; expectFiring.being = true; if(burstFireCount > burstFireMode.BurstRound) { burstFireCount = 0; expectFiring.Reset(); } break; } if (_gun.FireMode is SemiFireMode {RequireBoltAction:true}) { RequireBolt = true; } } private void InternalFire() { //调用BulletManager生成子弹 var _transform = transform; var rotation =_movement.ViewRotation; var position = _movement.Position; position.y = _transform.position.y; var random = UnityEngine.Random.insideUnitCircle; if (_gun.TryGetProperty(out var spread)) { random *= spread.Spread; } if(AnimationProperties.TryGetValue(BITConstant.Player.Aim, out var aim)) { random *= Mathf.Lerp(_gun.InitialHipFireSpread,1,aim); } random *= recoilSpring.value.GetLength(); BulletService.Spawn(new SpawnBullet { Initiator = Entity.Id, //Position = (_transform.position+rotation * bulletInitialOffset), //Rotation = rotation, Position = position, Rotation = rotation, Forward = rotation * (Vector3.forward + Vector3.up * random.y + Vector3.right * random.x), InitialDamage = _gun.InitialDamage, StartSpeed = _gun.InitialBulletSpeed, InitialForce = _gun.InitialBulletForce, }); } private void InternalAddRecoil() { if (_gun.TryGetProperty(out var _recoil)) { var _newRecoil = new Vector3 { x = _recoil.Recoil.x, y = _recoil.Recoil.y.Random(), z = _recoil.Recoil.z.Random() }; recoilSpring.value = _newRecoil; _playerMovement.AddViewEuler(new float2(_newRecoil.x,_newRecoil.y)); } } private void OnFire(InputAction.CallbackContext context) { //如果启用了指针则不开火 if(BITAppForUnity.AllowCursor) { expectFiring.Reset(); return; } switch (_gun.FireMode) { case AutoFireMode : switch (context) { case {interaction:PressInteraction , started:true}: expectFiring.shouldBe = true; break; case {interaction:PressInteraction , canceled:true}: expectFiring.shouldBe = false; break; } break; case SemiFireMode: switch (context) { case { interaction: PressInteraction, started: true } when fireInterval.AllowUpdateWithoutReset && RequireBolt is false: expectFiring.shouldBe = true; break; } break; } } private void OnAim(InputAction.CallbackContext context) { //如果启用了指针则不开火 if(BITAppForUnity.AllowCursor) { expectAiming.Reset(); return; } //expectAiming.shouldBe = context.ReadValueAsButton(); } } // #if UNITY_EDITOR // [CustomEditor(typeof(BITGun))] // public class BITGunInspector:BITInspector{} // #endif }