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 UnityEditorInternal; using UnityEngine.InputSystem.Interactions; #if UNITY_EDITOR using UnityEditor; #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(); // 输入系统 [Header(Constant.Header.Input)] public InputActionReference fireAction; public InputActionReference aimAction; public InputActionReference reloadAction; public InputActionReference meleeAction; [Header(Constant.Header.HotFix)] [SerializeField] private Transform cameraView; // 引用组件 [Header(Constant.Header.Components)] [SerializeField] private LocationAdditive locationAdditive; // 引用预制体 [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; private static readonly int IsGrounded = Animator.StringToHash("IsGrounded"); private AssetableGun _gun=>item as AssetableGun; private bool isHolstered; 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); _movement.OnStateChanged += OnMovementStateChanged; _movement.OnCommand += OnMovementCommand; animator[0].onStateExit += (state) => { isHolstered = state is BITConstant.Player.Holster; }; } 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; } TransitionState(); } private void OnMovementStateChanged(IEntityMovementState arg1, IEntityMovementState arg2) { foreach (var x in StateDictionary.Values) { x.OnMovementStateChanged(arg1,arg2); } } 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(); _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 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; } animator.animator.SetBool(IsGrounded,_movement.IsGrounded); animator.animator.SetFloat(BITConstant.Player.SqrMagnitude,_movement.LocomotionBasedVelocity.sqrMagnitude); recoilSpring.Update(deltaTime,default); locationAdditive.AddEuler(recoilSpring.value); if(AnimationProperties.TryGetValue(BITConstant.Player.Aim, out var _aim)) { _equipService.Zoom.Allow = CurrentState is Aim; _equipService.Zoom.Value =Mathf.Lerp(0,_gun.InitialAimZoom, _aim); _equipService.AllowScope = _aim > 0.86f && _gun.IsScopeAim; } 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); } public override void AnimationEvent(string eventName) { if(IsEntered is false) return; CurrentState?.AnimationEvent(eventName); switch (eventName) { case "Melee": meleeService.Melee(new MeleeCommand { PlayerId = Entity.Id, Position = Transform.position, Force = Transform.forward * 128, Range = 1 }); break; } } public void Fire() { if (RequireBolt) return; //如果启用了指针则不开火 if(BITAppForUnity.AllowCursor) { return; } //播放射击动画 UnityEntity.Invoke(Constant.Animation.Play, BITConstant.Player.Fire); //调用BulletManager生成子弹 var _transform = transform; var rotation = _transform.rotation; BulletService.Spawn(new SpawnBullet { initiator = Entity.Id, pos = (_transform.position+rotation * bulletInitialOffset).Fix(), rot = rotation, forward = _transform.forward.Fix(), initialDamage = _gun.InitialDamage, InitialForce = _gun.InitialBulletForce, }); //开火模式逻辑判断 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.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)); } if (_gun.FireMode is SemiFireMode {RequireBoltAction:true}) { RequireBolt = true; } } private void OnFire(InputAction.CallbackContext context) { switch (_gun.FireMode) { case AutoFireMode : switch (context) { case {interaction:TapInteraction , started:true}: expectFiring.shouldBe = true; break; case {interaction:TapInteraction , performed:true}: case {interaction:HoldInteraction , canceled:true}: expectFiring.shouldBe = false; break; } break; case SemiFireMode: switch (context) { case { interaction: TapInteraction, started: true }: expectFiring.shouldBe = true; break; } break; } } private void OnAim(InputAction.CallbackContext context) { expectAiming.shouldBe = context.ReadValueAsButton(); } } #if UNITY_EDITOR [CustomEditor(typeof(BITGun))] public class BITGunInspector:BITInspector{} #endif }