835 lines
30 KiB
C#
835 lines
30 KiB
C#
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<GunState> { }
|
|
public class BITGun : BITEquipBase<GunState>
|
|
{
|
|
[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<Transform> newSight;
|
|
|
|
[Header(nameof(Optional<GameObject>))]
|
|
[SerializeField] private Optional<LocationAdditive> 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<bool> expectFiring;
|
|
public ExpectState<bool> 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<Vector3> _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<Melee>();
|
|
}
|
|
|
|
private void OnReload(InputAction.CallbackContext obj)
|
|
{
|
|
if (obj.JustPressed() is false) return;
|
|
switch (CurrentState)
|
|
{
|
|
case Draw:
|
|
case Melee:
|
|
return;
|
|
}
|
|
|
|
if (Item?.TryGetProperty<IClip>(out var clip) is false)
|
|
{
|
|
_uxPopup?.Popup("<color=yellow>机匣损坏或不存在</color>");
|
|
return;
|
|
}
|
|
if (_inventory.TryGetItem(IsClipReady, out var clipItem) is false)
|
|
{
|
|
_uxPopup?.Popup("<color=yellow>没有兼容的弹夹</color>");
|
|
return;
|
|
}
|
|
TransitionState<Reload>();
|
|
}
|
|
|
|
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<Draw>();
|
|
|
|
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<Tactics>();
|
|
}
|
|
|
|
private bool IsClipReady(IBasicItem clip)
|
|
{
|
|
if(Item.TryGetProperty<IClip>(out var _clip) is false) return false;
|
|
if(clip.AddressablePath != _clip.AddressablePath) return false;
|
|
if(clip.TryGetProperty<IClip>(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<Draw>();
|
|
|
|
return UniTask.CompletedTask;
|
|
}
|
|
|
|
public override async UniTask ExitAsync()
|
|
{
|
|
TransitionState<Holster>();
|
|
|
|
_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<IRecoil>(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<IClip>(out var clip)
|
|
&& _inventory.TryGetItem(IsClipReady, out var clipItem)
|
|
)
|
|
switch (eventName)
|
|
{
|
|
case BITConstant.Player.EjectClip:
|
|
|
|
break;
|
|
case BITConstant.Player.InsertClip when clip.Remaining<clip.Capacity:
|
|
var dictionary = new Dictionary<int, int>();
|
|
var itemDictionary = new Dictionary<int, IBasicItem>();
|
|
var allItems = _inventory.GetItems();
|
|
foreach (var x in allItems)
|
|
{
|
|
if(x.TryGetProperty<IClip>(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<IClip>(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<int>(BITConstant.Environment.sv_infinite_ammo) is 1;
|
|
switch (Item.TryGetProperty<IClip>(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<ISpread>(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<IRecoil>(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<IClip>(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<GunControllerAimComponent>();
|
|
// 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<BITGun>{}
|
|
// #endif
|
|
} |