342 lines
11 KiB
C#
342 lines
11 KiB
C#
using System;
|
|
using System.Linq;
|
|
using BITFALL.Guns.States;
|
|
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<GunState> { }
|
|
public class BITGun : BITEquipBase<GunState>
|
|
{
|
|
//简单设置
|
|
[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)]
|
|
[SerializeField] internal AssetableGun assetable;
|
|
[Header(Constant.Header.Reference)]
|
|
|
|
// 内部变量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;
|
|
private static readonly int IsGrounded = Animator.StringToHash("IsGrounded");
|
|
private AssetableGun _gun=>item as AssetableGun;
|
|
private bool isHolstered;
|
|
|
|
#region 接口实现
|
|
public override string AddressablePath => assetable.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<Melee>();
|
|
}
|
|
|
|
private void OnReload(InputAction.CallbackContext obj)
|
|
{
|
|
if (obj.JustPressed() is false) return;
|
|
switch (CurrentState)
|
|
{
|
|
case Equip:
|
|
case Melee:
|
|
return;
|
|
}
|
|
TransitionState<Reload>();
|
|
}
|
|
|
|
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 / assetable.FireMode.FireRate;
|
|
fireInterval.Reset();
|
|
|
|
if (assetable.FireMode is BurstFireMode burstFireMode)
|
|
{
|
|
burstFireInterval.Interval = burstFireMode.BurstFireInterval;
|
|
burstFireInterval.Reset();
|
|
}
|
|
|
|
TransitionState<Equip>();
|
|
|
|
return UniTask.CompletedTask;
|
|
}
|
|
|
|
public override async UniTask ExitAsync()
|
|
{
|
|
TransitionState<Holster>();
|
|
inputActionGroup.allowInput.RemoveElement(this);
|
|
expectFiring.Reset();
|
|
|
|
cameraView.localPosition = default;
|
|
|
|
try
|
|
{
|
|
while (_health.IsAlive && isHolstered is false)
|
|
{
|
|
destroyCancellationToken.ThrowIfCancellationRequested();
|
|
await UniTask.NextFrame();
|
|
}
|
|
|
|
destroyCancellationToken.ThrowIfCancellationRequested();
|
|
|
|
await base.ExitAsync();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
}
|
|
|
|
public override void OnUpdate(float deltaTime)
|
|
{
|
|
UpdateState(deltaTime);
|
|
switch (assetable.FireMode)
|
|
{
|
|
case AutoFireMode:
|
|
break;
|
|
case SemiFireMode:
|
|
break;
|
|
case BurstFireMode when expectFiring.being:
|
|
expectFiring.shouldBe = fireAction.action.WasPressedThisFrame();
|
|
if(burstFireInterval.AllowUpdate)
|
|
{
|
|
Fire();
|
|
}
|
|
break;
|
|
}
|
|
|
|
animator.animator.SetBool(IsGrounded,_movement.IsGrounded);
|
|
animator.animator.SetFloat(BITConstant.Player.SqrMagnitude,_movement.LocomotionBasedVelocity.sqrMagnitude);
|
|
|
|
recoilSpring.Update(deltaTime,default);
|
|
|
|
locationAdditive.AddEuler(recoilSpring.value);
|
|
}
|
|
|
|
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(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 (assetable.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 (assetable.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)
|
|
{
|
|
switch (assetable.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<BITGun>{}
|
|
#endif
|
|
} |