using System; using System.Collections.Generic; using System.Linq; using Animancer; using BITFALL.Entities.Equipment; using BITFALL.Guns.Modify; using BITFALL.Guns.States; using BITFALL.Player.Equip; using BITFALL.Player.Movement; using UnityEngine; using BITKit; using BITKit.Animations; using BITKit.Entities; using BITKit.Entities.Melee; using BITKit.Sensors; using UnityEngine.InputSystem; using BITKit.StateMachine; using Cysharp.Threading.Tasks; using Unity.Mathematics; using UnityEngine.InputSystem.Interactions; using Random = UnityEngine.Random; #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; [Inject] protected IEquipService equipService; protected AnimancerComponent animancerComponent=>root.AnimancerComponent; public bool Enabled { get;set; } protected AnimancerState currentState; protected string[] AnimationKey; protected AnimancerState PlayAnimation(params string[] args) { return PlayAnimation(0,null, args); } protected AnimancerState PlayAnimation(int layer,Action OnEnd,params string[] args) { if (root.MotionMatchingService.TryMatch(out var obj, root.SearchKey.Union(args).ToArray()) is false) return null; switch (obj) { case IMotionMatchingClip clip: currentState = animancerComponent.Layers[layer].Play(clip.Clip, 0.1f); currentState.Events.OnEnd =OnEnd ?? (() => { currentState.Events.OnEnd = null; this.OnEnd(); }); break; case IMotionMatchingSequence sequence: break; case IMotionMatchingDualClip dualClip: currentState = animancerComponent.Layers[layer].Play(dualClip.Clip1, 0.1f); currentState.Events.OnEnd =OnEnd ?? (() => { currentState.Events.OnEnd = null; this.OnEnd(); }); if (root.AdditionalAnimancerComponent) { root.AdditionalAnimancerComponent.Stop(); var state2 = root.AdditionalAnimancerComponent.Play(dualClip.Clip2, 0.1f); } break; } return currentState; } protected void PlayAnimationVoid()=>PlayAnimation(); protected AnimancerState PlayAnimation() { return PlayAnimation(0,null,GetType().Name); } public virtual void Initialize() { root.Entity.Inject(this); AnimationKey = root.SearchKey.Append(GetType().Name).ToArray(); } public virtual void OnStateEntry(IState old) { PlayAnimation(); } 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) { } protected virtual void OnEnd() { } } [System.Serializable] public class GunStateMachine : MonoStateMachine { } public class BITGun : BITEquipBase { [RuntimeInitializeOnLoadMethod] private static void Reload() { _token =(uint)(uint.MaxValue * Random.value); } public static uint Token => _token += 1; private static uint _token; //简单设置 [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 tacticsAction; [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; [Inject] internal IEntityEquipmentContainer _equipmentContainer; internal ScriptableGun _gun=>item as ScriptableGun; private bool isSteadyAim; private readonly Optional _fixAimPosition = new(); public bool RequireBolt { get; set; } #region 接口实现 public override string AddressablePath => _gun.AddressablePath; #endregion private LinearMixerState walkState; private AnimancerState jumpState; public override void OnAwake() { base.OnAwake(); if (breathingAdditive.Allow) inputActionGroup.RegisterCallback(steadyAimAction, OnSteadyAim); BITAppForUnity.AllowCursor.AddListener(OnAllowCursor); destroyCancellationToken.Register(() => { BITAppForUnity.AllowCursor.RemoveListener(OnAllowCursor); }); } 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 when MotionMatchingService.TryMatch(out var jump, SearchKey.Append("Jump").ToArray()) && jump is IMotionMatchingClip jumpMotion : if (jumpState is { IsValid: true }) jumpState?.Stop(); jumpState = AnimancerComponent.Layers[4].Play(jumpMotion.Clip); break; case OnPlayerLandCommand when MotionMatchingService.TryMatch(out var land, SearchKey.Append("Land").ToArray()) && land is IMotionMatchingClip landMotion : if (jumpState is { IsValid: true }) jumpState?.Stop(); jumpState = AnimancerComponent.Layers[4].Play(landMotion.Clip, 0.1f); jumpState.Events.OnEnd = () => { jumpState.Events.OnEnd = null; jumpState.StartFade(0); }; break; } } private void OnMelee(InputAction.CallbackContext obj) { switch (CurrentState) { case Draw: case Melee: return; } TransitionState(); } private void OnReload(InputAction.CallbackContext obj) { if (obj.JustPressed() is false) return; switch (CurrentState) { case Draw: 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 }); inputActionGroup.RegisterCallback(fireAction, OnFire); inputActionGroup.RegisterCallback(aimAction, OnAim); inputActionGroup.RegisterCallback(reloadAction, OnReload); inputActionGroup.RegisterCallback(meleeAction, OnMelee); inputActionGroup.RegisterCallback(tacticsAction, OnTactics); TransitionState(); if (MotionMatchingService.TryMatch(out var walkMotion, SearchKey.Append(BITConstant.Player.Walk).ToArray()) && MotionMatchingService.TryMatch(out var movementMotion, SearchKey.Append(BITConstant.Player.Idle).ToArray())) { if (walkMotion is IMotionMatchingClip walkClip && movementMotion is IMotionMatchingClip movementClip) { walkState = new LinearMixerState { { movementClip.Clip, 0 }, { walkClip.Clip, 1 } }; AnimancerComponent.Layers[2].Play(walkState); //walkState = AnimancerComponent.Layers[2].Play(walkClip.Clip); } } _movement.OnStateChanged += OnMovementStateChanged; _movement.OnCommand += OnMovementCommand; AnimancerComponent.Layers[3].IsAdditive = true; AnimancerComponent.Layers[2].IsAdditive = true; AnimancerComponent.Layers[4].IsAdditive = true; } private void OnTactics(InputAction.CallbackContext obj) { if(obj is not {interaction:TapInteraction, performed: true}) return; if (_equipmentContainer.Equipment.TryGetValue(new EquipmentAsTactics(), out _) is false) return; TransitionState(); } 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(); inputActionGroup.UnRegisterCallback(fireAction, OnFire); inputActionGroup.UnRegisterCallback(aimAction, OnAim); inputActionGroup.UnRegisterCallback(reloadAction, OnReload); inputActionGroup.UnRegisterCallback(meleeAction, OnMelee); inputActionGroup.UnRegisterCallback(tacticsAction, OnTactics); _movement.OnStateChanged -= OnMovementStateChanged; _movement.OnCommand -= OnMovementCommand; } public override UniTask EntryAsync() { base.EntryAsync(); 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 { await base.ExitAsync(); _equipService.Zoom.Value = Mathf.MoveTowards(_equipService.Zoom.Value, 0, Time.deltaTime); _equipService.Zoom.Allow = false; destroyCancellationToken.ThrowIfCancellationRequested(); _equipService.Stable = 1; } 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) { base.OnUpdate(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; // } Fire(); } break; } _movement.ExecuteCommand(new PlayerFocusCommand() { Focus = recoilSpring.value.GetLength() > 0.1f, Sender = this }); var length = _movement.GroundVelocity.GetLength(); if (walkState is {IsValid:true}) { var value = Mathf.Clamp( length / _movement.ReferenceSpeed, 0, 1 ); walkState.Parameter = value; switch (CurrentState,_movement.CurrentState) { case (Movement,IPlayerWalkState): walkState.SetWeight(1 - _equipService.Aim); break; default: walkState.SetWeight(0); break; } } recoilSpring.Update(deltaTime,default); if (_gun.TryGetProperty(out var recoil) && locationAdditive) { //locationAdditive.AddEuler(Vector3.Scale(recoilSpring.value,recoil.ViewRecoilScale) ); locationAdditive.AddRotation(Quaternion.Euler(Vector3.Scale(recoilSpring.value,recoil.ViewRecoilScale))); } if (selfViewAdditive) { var positionWeight = recoilPositionWeight; var aim = _equipService.Aim; positionWeight *= Mathf.Lerp(2, 1, aim); //selfViewAdditive.AddEuler(recoilSpring.value); selfViewAdditive.AddRotation(Quaternion.Euler(recoilSpring.value)); selfViewAdditive.AddPosition( new Vector3( recoilSpring.value.y, -recoilSpring.value.x, recoilSpring.value.z ) * positionWeight ); } { var _aim = _equipService.Aim; _equipService.Zoom.Allow = CurrentState is Aim; _equipService.Zoom.Value =Mathf.Lerp(1,_gun.InitialAimZoom, _aim); _equipService.AllowScope = _aim > 0.5f && _gun.IsScopeAim; _equipService.Aim = _aim; 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, _fixAimPosition.Value, _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) ) switch (eventName) { case BITConstant.Player.EjectClip: break; case BITConstant.Player.InsertClip when clip.Remaining(); var itemDictionary = new Dictionary(); var allItems = _inventory.GetItems(); foreach (var x in allItems) { if(x.TryGetProperty(out var nextClip) is false)continue; if(nextClip.AddressablePath!=clip.AddressablePath)continue; dictionary.Add(x.Id,nextClip.Remaining); itemDictionary.Add(x.Id,x); } var reloadAmount = clip.Capacity - clip.Remaining; foreach (var x in dictionary) { var nextClipItem = itemDictionary[x.Key]; nextClipItem.TryGetProperty(out var nextClip); if (nextClip.Remaining >= reloadAmount) { clip.Remaining = clip.Capacity; nextClip.Remaining -= reloadAmount; _inventory.TrySetItem(nextClipItem); return; } clip.Remaining += nextClip.Remaining; _inventory.Remove(nextClipItem); } return; } } public void Fire() { if (RequireBolt) return; switch (CurrentState) { case States.Reload: case Draw: return; } AudioSensorService.MakeNoise(transform.position,UnityEntity.transform); var infiniteAmmo = Data.Get(BITConstant.Environment.sv_infinite_ammo) is 1; switch (Item.TryGetProperty(out var clip)) { case false: case true when infiniteAmmo: break; case true when clip.Remaining >0: clip.Remaining--; break; default: return; } //播放射击动画 //UnityEntity.Invoke(Constant.Animation.Play, BITConstant.Player.Fire); if (MotionMatchingService.TryMatch(out var motion, SearchKey.Append( CurrentState is Aim ? BITConstant.Player.AimFire : BITConstant.Player.Fire ).ToArray())) { if (motion is IMotionMatchingClip fireClip) { AnimancerComponent.Layers[3].Stop(); var state =AnimancerComponent.Layers[3].Play(fireClip.Clip); state.Events.OnEnd = () => { state.Events.OnEnd = null; }; } } if (_gun.BuckShot.Allow) { InternalAddRecoil(); InternalFire(_gun.BuckShot.Value); } 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(int count = 1) { var aim = _equipService.Aim; //调用BulletManager生成子弹 var _transform = transform; var rotation = _movement.ViewRotation; var position = _transform.position; //position.y = _transform.position.y; var fireRecoil = Vector2.one; if (_gun.TryGetProperty(out var spread)) { fireRecoil *= spread.Spread; } fireRecoil *= Mathf.Lerp(_gun.InitialHipFireSpread,1,aim); fireRecoil *= recoilSpring.value.GetLength(); var length = fireRecoil.GetLength(); for (var i = 0; i < count; i++) { float angle = i * 2 * Mathf.PI / count; float x = length * Mathf.Cos(angle); float y = length * Mathf.Sin(angle); Vector2 point = new Vector2(x, y); BulletService.Spawn(new SpawnBullet { Token = Token, Initiator = Entity.Id, //Position = (_transform.position+rotation * bulletInitialOffset), //Rotation = rotation, Position = position, Rotation = rotation, Forward = rotation * (Vector3.forward + Vector3.up * point.y + Vector3.right * point.x), InitialDamage = _gun.InitialDamage, StartSpeed = _gun.InitialBulletSpeed, InitialForce = _gun.InitialBulletForce, AssetableId = _gun.AddressableId }); } } 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; } if (Item.TryGetProperty(out var clip)) { if (clip.Remaining is 0) { 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(); } protected override void OnItemApplied(IBasicItem item) { base.OnItemApplied(item); // var aim = GetComponentInChildren(); // newSight.Allow = aim; // if (aim) // { // newSight.Value = aim.transform; // var initialSightPos = Transform.InverseTransformVector(initialSight.position); // var newSightPos = Transform.InverseTransformVector(newSight.Value.position); // _fixAimPosition.SetValueThenAllow( (newSightPos.y -initialSightPos.y) * Vector3.up); // } // else // { // _fixAimPosition.Clear(); // } } } // #if UNITY_EDITOR // [CustomEditor(typeof(BITGun))] // public class BITGunInspector:BITInspector{} // #endif }