894 lines
25 KiB
C#
894 lines
25 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Permissions;
|
|
using System.Timers;
|
|
using BITFALL.Entities.Player.Movement.States;
|
|
using BITFALL.Player.Equip;
|
|
using BITFALL.Player.Movement;
|
|
using BITFALL.Props;
|
|
using BITFALL.Scene;
|
|
using BITKit;
|
|
using BITKit.Entities;
|
|
using BITKit.Entities.Movement;
|
|
using BITKit.Entities.Physics;
|
|
using BITKit.Entities.Player;
|
|
using BITKit.PlayerCamera;
|
|
using BITKit.Sensors;
|
|
using BITKit.UX;
|
|
using Cysharp.Threading.Tasks;
|
|
using Lightbug.CharacterControllerPro.Core;
|
|
using Unity.Mathematics;
|
|
using Unity.SharpZipLib.GZip;
|
|
using UnityEditor.TerrainTools;
|
|
using UnityEngine;
|
|
using UnityEngine.AI;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.InputSystem.Interactions;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace BITFALL.Entities.Player.Movement
|
|
{
|
|
[CustomType(typeof(IEntityMovement))]
|
|
[CustomType(typeof(IPlayerMovement))]
|
|
[CustomType(typeof(ISensorTarget))]
|
|
[CustomType(typeof(PlayerCharacterController))]
|
|
public class PlayerCharacterController : StateBasedBehavior<IEntityMovementState>
|
|
,IEntityMovement
|
|
,IPlayerMovement
|
|
,IEntityBinaryComponent
|
|
,ISensorTarget
|
|
{
|
|
[Header(Constant.Header.Settings)]
|
|
[SerializeField] internal Vector3 initialCameraPosition = new(0,0.11f,0.27f);
|
|
[SerializeField] internal float initialSpeed;
|
|
|
|
[Header(Constant.Header.Components)]
|
|
[SerializeField] private CharacterActor actor;
|
|
[SerializeField] private LocationAdditive locationAdditive;
|
|
[SerializeField] private LocationAdditive centerAdditive;
|
|
|
|
[Header(Constant.Header.Gameobjects)]
|
|
[SerializeField] private Transform fpvPoint;
|
|
[SerializeField] private Transform parachuteModel;
|
|
|
|
[Header(Constant.Header.Input)]
|
|
[SerializeField] private InputActionReference movementAction;
|
|
[SerializeField] private InputActionReference viewAction;
|
|
[SerializeField] private InputActionReference jumpAction;
|
|
[SerializeField] private InputActionReference crouchAction;
|
|
[SerializeField] private InputActionReference runAction;
|
|
[SerializeField] private InputActionReference crawlAction;
|
|
|
|
|
|
[Header(Constant.Header.Providers)]
|
|
[SerializeReference, SubclassSelector] internal IClosePoint vaultPoint;
|
|
[SerializeReference,SubclassSelector] internal IClosePoint climbClosePoint;
|
|
[SerializeReference, SubclassSelector] internal IClosePoint edgeClimbPoint;
|
|
[SerializeReference, SubclassSelector] private IProvider adsProvider;
|
|
|
|
public InputActionReference MovementAction => movementAction;
|
|
public InputActionReference ViewAction => viewAction;
|
|
public InputActionReference JumpAction => jumpAction;
|
|
public InputActionReference CrouchAction => crouchAction;
|
|
public InputActionReference RunAction => runAction;
|
|
|
|
|
|
public Vector3 Position
|
|
{
|
|
get => actor.Position;
|
|
set=>actor.Position = value;
|
|
}
|
|
|
|
public Quaternion Rotation
|
|
{
|
|
get => actor.Rotation;
|
|
set => actor.Rotation = value;
|
|
}
|
|
|
|
public Vector3 Size => actor.ColliderComponent.BoundsSize;
|
|
|
|
public float ReferenceSpeed
|
|
{
|
|
get =>limitSpeed.Allow ? Mathf.Min(limitSpeed.Value,referenceSpeed):referenceSpeed;
|
|
set=>referenceSpeed = value;
|
|
}
|
|
private float referenceSpeed = 2.5f;
|
|
public Vector3 Forward => actor.Forward;
|
|
public Vector3 ViewForward { get; set; }
|
|
public Vector3 ViewCenter { get; private set; }
|
|
public Vector3 FocusPoint { get; internal set; }
|
|
public Quaternion ViewRotation { get; set; }
|
|
public Vector3 LocomotionBasedVelocity { get;internal set; }
|
|
public Vector3 Velocity => actor.Velocity;
|
|
public Vector3 GroundVelocity => new Vector3(Velocity.x, 0, Velocity.z);
|
|
public Vector3 AngularVelocity { get;private set; }
|
|
public bool IsGrounded => actor.IsGrounded;
|
|
|
|
public Vector3 MovementInput { get; private set; }
|
|
public Vector2 LookInput{ get; private set; }
|
|
public Vector2 LookInputDelta { get; private set; }
|
|
public ExpectState<bool> ExpectRun;
|
|
public ExpectState<bool> ExpectJump;
|
|
public ExpectState<bool> ExpectParachute;
|
|
public ExpectState<bool> ExpectCrouch;
|
|
public ExpectState<bool> ExpectSprint;
|
|
public bool RequestClimb { get;internal set; }
|
|
public ExpectState<Vector3> ExpectClimb;
|
|
public ExpectState<Vector3> CurrentCameraPosition;
|
|
public OffMeshLink OffMeshLink { get; private set; }
|
|
|
|
internal readonly ValidHandle allowMovement = new();
|
|
internal readonly ValidHandle allowRun = new();
|
|
internal readonly ValidHandle pauseRun = new();
|
|
internal readonly ValidHandle allowFocus = new();
|
|
internal IOptional<float> limitSpeed =>new ReadOnlyOptional<float>(limitSpeedDictionary.Count is not 0,
|
|
limitSpeedDictionary.Count is 0 ? 0 :
|
|
limitSpeedDictionary.Values.Min());
|
|
internal IOptional<float> limitSensitive => new ReadOnlyOptional<float>(limitSensitiveSpeedDictionary.Count is not 0,
|
|
limitSensitiveSpeedDictionary.Count is 0 ? 0 :
|
|
limitSensitiveSpeedDictionary.Values.Min() * 0.5f);
|
|
|
|
private readonly Dictionary<int,float> limitSpeedDictionary = new();
|
|
private readonly Dictionary<int,float> limitSensitiveSpeedDictionary = new();
|
|
|
|
private bool isDead;
|
|
private bool isUnStable;
|
|
internal bool topBlocked;
|
|
private Vector3 keepVelocity;
|
|
private readonly DoubleBuffer<Vector3> gravityDamage = new();
|
|
private readonly DoubleBuffer<float> gravityDamping = new();
|
|
private Vector3 cacheGravity;
|
|
|
|
internal IntervalUpdate landFreeze = new(0.5f);
|
|
|
|
[Inject]
|
|
private IEntityPhysics _physics;
|
|
[Inject]
|
|
private IHealth _health;
|
|
[Inject]
|
|
internal InputActionGroup inputActionGroup;
|
|
[Inject]
|
|
private IKnockdown _knockdown;
|
|
[Inject]
|
|
private IEntityOverride _override;
|
|
[Inject]
|
|
private IPlayerCameraService _playerCameraService;
|
|
[Inject]
|
|
private IUXPopup _uxPopup;
|
|
|
|
|
|
public override void OnAwake()
|
|
{
|
|
base.OnAwake();
|
|
|
|
_health.OnSetAlive += OnSetAlive;
|
|
_physics.OnSetPhysics += OnSetPhysics;
|
|
LookInput = MathV.TransientRotationAxis(transform.rotation.eulerAngles);
|
|
|
|
parachuteModel.gameObject.SetActive(false);
|
|
|
|
_override.OnOverride += OnOverride;
|
|
|
|
}
|
|
|
|
private void OnOverride(bool obj)
|
|
{
|
|
actor.UseRootMotion = obj;
|
|
if (obj is false)
|
|
{
|
|
locationAdditive.SetGlobalPosition(default);
|
|
}
|
|
}
|
|
|
|
public override void OnStart()
|
|
{
|
|
base.OnStart();
|
|
foreach (var x in GetComponentsInChildren<Collider>(true))
|
|
{
|
|
actor.PhysicsComponent.IgnoreCollision(x.transform,true);
|
|
}
|
|
TransitionState<Walk>();
|
|
|
|
|
|
inputActionGroup.RegisterCallback(movementAction, OnMovement);
|
|
inputActionGroup.RegisterCallback(viewAction, OnView);
|
|
inputActionGroup.RegisterCallback(jumpAction, OnJump);
|
|
inputActionGroup.RegisterCallback(crouchAction, OnCrouch);
|
|
inputActionGroup.RegisterCallback(runAction, OnRun);
|
|
inputActionGroup.RegisterCallback(crawlAction, OnCrawl);
|
|
}
|
|
|
|
private void OnCrawl(InputAction.CallbackContext obj)
|
|
{
|
|
if(actor.IsStable is false) return;
|
|
if (obj is not { interaction: PressInteraction, performed: true }) return;
|
|
switch (CurrentState)
|
|
{
|
|
case IPlayerCrouchState:
|
|
case IPlayerRunState:
|
|
case IPlayerSprintState:
|
|
case IPlayerWalkState:
|
|
TransitionState<Crawl>();
|
|
break;
|
|
case IPlayerCrawlState when topBlocked is false:
|
|
ExpectCrouch.Reset();
|
|
TransitionState<Walk>();
|
|
break;
|
|
}
|
|
}
|
|
private void OnSetPhysics(bool obj)
|
|
{
|
|
if (obj is false) return;
|
|
_physics.Velocity = keepVelocity;
|
|
//_physics.Velocity = default;
|
|
actor.Velocity = Vector3.zero;
|
|
}
|
|
|
|
private void OnSetAlive(bool obj)
|
|
{
|
|
allowMovement.SetElements(123,obj);
|
|
allowRun.SetElements(123,obj);
|
|
if (obj)
|
|
{
|
|
if (!isDead) return;
|
|
//actor.Position = _physics.Center;
|
|
isDead = false;
|
|
}
|
|
else
|
|
{
|
|
isDead = true;
|
|
}
|
|
Enabled = true;
|
|
TransitionState<Walk>();
|
|
}
|
|
public void SyncMovement(Vector3 velocity, Vector3 position, Quaternion rotation, bool isGrounded)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
public void OnMovement(Vector3 relativeVector)
|
|
{
|
|
MovementInput = relativeVector;
|
|
}
|
|
public void OnMovement(InputAction.CallbackContext context)
|
|
{
|
|
MovementInput = context.ReadValue<Vector2>();
|
|
MovementInput = new Vector3(MovementInput.x,0,MovementInput.y);
|
|
|
|
if (_playerCameraService.IsCameraActivated)
|
|
{
|
|
if (MovementInput.z > 0)
|
|
{
|
|
if (inputActionGroup.GetAction(runAction).IsPressed())
|
|
{
|
|
ExpectRun.shouldBe = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ExpectRun.Reset();
|
|
ExpectSprint.Reset();
|
|
}
|
|
}
|
|
else if (MovementInput == default && inputActionGroup.GetAction(runAction).IsPressed() is false)
|
|
{
|
|
ExpectRun.Reset();
|
|
ExpectSprint.Reset();
|
|
}
|
|
else if (MovementInput == default)
|
|
{
|
|
ExpectRun.Reset();
|
|
ExpectSprint.Reset();
|
|
}else if (MovementInput.z > 0 && inputActionGroup.GetAction(runAction).IsPressed())
|
|
{
|
|
ExpectRun.shouldBe = true;
|
|
}
|
|
}
|
|
|
|
public async void ExecuteCommand<T>(T command=default)
|
|
{
|
|
foreach (var x in StateDictionary.Values)
|
|
{
|
|
x.ExecuteCommand<T>(command);
|
|
}
|
|
switch (command)
|
|
{
|
|
case PlayerEnableRunCommand enableRunCommand:
|
|
allowRun.RemoveDisableElements(enableRunCommand.Lock);
|
|
ExpectRun.Reset();
|
|
ExpectSprint.Reset();
|
|
break;
|
|
case PlayerDisableRunCommand disableRunCommand:
|
|
allowRun.AddDisableElements(disableRunCommand.Lock);
|
|
break;
|
|
case PlayerCancelRunCommand:
|
|
ExpectRun.Reset();
|
|
ExpectSprint.Reset();
|
|
break;
|
|
case PlayerChangeVelocityCommand changeVelocityCommand:
|
|
actor.Velocity = Vector3.Lerp(actor.Velocity, changeVelocityCommand.Velocity, 5 * Time.deltaTime);
|
|
break;
|
|
case PlayerAddGravityDampingCommand addGravityDampingCommand:
|
|
gravityDamping.Release(addGravityDampingCommand.Damping);
|
|
break;
|
|
case PlayerPauseRunCommand pauseRunCommand:
|
|
pauseRun.SetElements(123,pauseRunCommand.Pause);
|
|
break;
|
|
case PlayerFocusCommand focusCommand:
|
|
allowFocus.SetElements(focusCommand.Sender,focusCommand.Focus);
|
|
break;
|
|
case PlayerLimitMoveSpeedCommand limitMoveSpeedCommand:
|
|
if (limitMoveSpeedCommand.Limit)
|
|
{
|
|
limitSpeedDictionary.Set(limitMoveSpeedCommand.Id,limitMoveSpeedCommand.Speed);
|
|
}
|
|
else
|
|
{
|
|
limitSpeedDictionary.TryRemove(limitMoveSpeedCommand.Id);
|
|
}
|
|
break;
|
|
case PlayerLimitSensitivityCommand limitSensitivityCommand:
|
|
if (limitSensitivityCommand.Limit)
|
|
{
|
|
limitSensitiveSpeedDictionary.Set(limitSensitivityCommand.Id,limitSensitivityCommand.Sensitivity);
|
|
}
|
|
else
|
|
{
|
|
limitSensitiveSpeedDictionary.TryRemove(limitSensitivityCommand.Id);
|
|
}
|
|
break;
|
|
case PlayerDisableMovementCommand disableMovementCommand:
|
|
allowMovement.SetDisableElements(disableMovementCommand.LockFile,disableMovementCommand.Disable);
|
|
if (string.IsNullOrEmpty(disableMovementCommand.Source) is false && disableMovementCommand.Disable)
|
|
{
|
|
_uxPopup.Popup(disableMovementCommand.Source,disableMovementCommand.Duration);
|
|
}
|
|
if (disableMovementCommand.Duration is not 0)
|
|
{
|
|
await UniTask.Delay(TimeSpan.FromSeconds(disableMovementCommand.Duration));
|
|
if (destroyCancellationToken.IsCancellationRequested) return;
|
|
allowMovement.SetDisableElements(disableMovementCommand.LockFile,false);
|
|
}
|
|
break;
|
|
}
|
|
OnCommand?.Invoke(command);
|
|
}
|
|
public event Action<object> OnCommand;
|
|
|
|
public void OnView(InputAction.CallbackContext context)
|
|
{
|
|
if (BITAppForUnity.AllowCursor) return;
|
|
if (_override.IsOvering) return;
|
|
|
|
var playerConfig = PlayerConfig.Singleton;
|
|
var ads = adsProvider.Get<float>();
|
|
if (ads is 0) ads = 1;
|
|
var raw = context.ReadValue<Vector2>() *
|
|
(limitSensitive.Allow ? Mathf.Min( playerConfig.Sensitivity,limitSensitive.Value): playerConfig.Sensitivity)
|
|
* playerConfig.M_Yaw * ads;
|
|
LookInputDelta +=raw;
|
|
var lookInput = LookInput;
|
|
lookInput.x -= raw.y;
|
|
lookInput.y += raw.x;
|
|
lookInput.x = Mathf.Clamp(lookInput.x, -80, 80);
|
|
LookInput = lookInput;
|
|
|
|
var rotation = Quaternion.Euler(LookInput);
|
|
if (LimitViewAngle is not 0)
|
|
{
|
|
float angleDifference = Quaternion.Angle(rotation, FpvRotation);
|
|
|
|
// 如果角度差大于最大允许角度差,进行限制
|
|
if (angleDifference > LimitViewAngle)
|
|
{
|
|
//将当前旋转限制在最大允许角度差的范围内
|
|
rotation = Quaternion.Lerp(
|
|
rotation,
|
|
Quaternion.RotateTowards(FpvRotation,rotation , LimitViewAngle),
|
|
5 * Time.deltaTime
|
|
);
|
|
//rotation = Quaternion.RotateTowards(FpvRotation, rotation, LimitViewAngle);
|
|
|
|
LookInput = MathV.TransientRotationAxis(rotation.eulerAngles);
|
|
}
|
|
}
|
|
|
|
AngularVelocity = new Vector3
|
|
(
|
|
raw.y,
|
|
-raw.x
|
|
);
|
|
|
|
//centerAdditive.Rotation = rotation;
|
|
centerAdditive.SetGlobalRotation(rotation);
|
|
}
|
|
public void OnJump(InputAction.CallbackContext context)
|
|
{
|
|
if (_knockdown.IsKnockdown) return;
|
|
|
|
RequestClimb = context switch
|
|
{
|
|
{ interaction: PressInteraction, performed: true } => true,
|
|
{ interaction: PressInteraction, canceled: true } => false,
|
|
_ when CurrentState is EdgeClimb => false,
|
|
_ => RequestClimb
|
|
};
|
|
|
|
switch (context)
|
|
{
|
|
case {interaction: PressInteraction, performed: true}:
|
|
|
|
|
|
break;
|
|
default: return;
|
|
}
|
|
|
|
if (ExpectJump.shouldBe) return;
|
|
|
|
switch (CurrentState)
|
|
{
|
|
case IPlayerCrawlState when topBlocked is false:
|
|
TransitionState<Walk>();
|
|
return;
|
|
break;
|
|
case IPlayerLinkState:
|
|
return;
|
|
case IPlayerWalkState:
|
|
case IPlayerRunState:
|
|
case IPlayerSprintState:
|
|
case IPlayerCrouchState:
|
|
case IPlayerSlideState:
|
|
foreach (var x in Physics.OverlapSphere(actor.Position, 1, LayerMask.GetMask("Dynamic")))
|
|
{
|
|
if (!x.TryGetComponent<OffMeshLink>(out var offMeshLink)) continue;
|
|
switch (offMeshLink.area)
|
|
{
|
|
case 5:
|
|
break;
|
|
default:
|
|
var toTarget = x.transform.position - transform.position;
|
|
|
|
toTarget = Vector3.ProjectOnPlane(toTarget, Vector3.up);
|
|
|
|
// 获取正前方的向量
|
|
var forward = actor.Forward;
|
|
|
|
// 计算点积
|
|
var dotProduct = Vector3.Dot(toTarget.normalized, forward);
|
|
|
|
if (dotProduct < 0.8f) continue;
|
|
break;
|
|
}
|
|
OffMeshLink = offMeshLink;
|
|
TransitionState<Link>();
|
|
return;
|
|
}
|
|
|
|
if (allowMovement.Allow)
|
|
{
|
|
if (MovementInput.x is not 0 && MovementInput.z < 0 || MovementInput.z is -1)
|
|
{
|
|
if (_playerCameraService.IsCameraActivated && _stamina > 0)
|
|
{
|
|
TransitionState<Dodge>();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (actor.IsGrounded)
|
|
{
|
|
case true:
|
|
ExpectJump.shouldBe = true;
|
|
break;
|
|
case false when actor.VerticalVelocity.y<=-16f:
|
|
ExpectParachute.shouldBe = true;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
ExpectCrouch.Reset();
|
|
}
|
|
public void OnRun(InputAction.CallbackContext context)
|
|
{
|
|
if (allowRun.Allow is false) return;
|
|
if (_knockdown.IsKnockdown) return;
|
|
|
|
if (context.performed)
|
|
{
|
|
if (_playerCameraService.IsCameraActivated)
|
|
{
|
|
if (MovementInput.z is 0) return;
|
|
}
|
|
else
|
|
{
|
|
if(MovementInput == default) return;
|
|
}
|
|
}
|
|
|
|
var holdToRun = Data.Get<bool>(BITConstant.Environment.cl_hold_to_run);
|
|
|
|
//Debug.Log(context);
|
|
switch (context)
|
|
{
|
|
//case { interaction: PressInteraction, started: true }:
|
|
case { interaction: PressInteraction, canceled:true } when holdToRun:
|
|
ExpectRun.Reset();
|
|
break;
|
|
case { interaction: PressInteraction, performed:true }:
|
|
if (holdToRun)
|
|
{
|
|
ExpectRun.shouldBe = true;
|
|
}
|
|
else
|
|
{
|
|
if (ExpectRun.shouldBe && Stamina> 0)
|
|
ExpectSprint.shouldBe = true;
|
|
ExpectRun.shouldBe = true;
|
|
ExpectCrouch.Reset();
|
|
}
|
|
break;
|
|
case {interaction:MultiTapInteraction,performed:true}:
|
|
//ExpectSprint.shouldBe = ExpectRun.shouldBe = true;
|
|
if (ExpectRun.shouldBe)
|
|
{
|
|
ExpectSprint.shouldBe = true;
|
|
}
|
|
else
|
|
{
|
|
ExpectRun.shouldBe = true;
|
|
}
|
|
ExpectCrouch.Reset();
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
public void OnCrouch(InputAction.CallbackContext context)
|
|
{
|
|
if (_knockdown.IsKnockdown) return;
|
|
|
|
switch (context)
|
|
{
|
|
case { interaction: PressInteraction, performed: true }:
|
|
if (CurrentState is Crawl && topBlocked is false)
|
|
{
|
|
TransitionState<Crouch>();
|
|
ExpectCrouch.shouldBe = true;
|
|
return;
|
|
}
|
|
if (CurrentState is Climb or EdgeClimb or Vault)
|
|
{
|
|
TransitionState<Walk>();
|
|
}
|
|
else
|
|
{
|
|
ExpectCrouch.shouldBe = !ExpectCrouch.shouldBe;
|
|
ExpectRun.Reset();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
public override void OnUpdate(float deltaTime)
|
|
{
|
|
CurrentState?.BeforeUpdateMovement(deltaTime);
|
|
|
|
UpdateState(deltaTime);
|
|
|
|
CurrentState?.AfterUpdateMovement(deltaTime);
|
|
|
|
}
|
|
|
|
public override void OnFixedUpdate(float deltaTime)
|
|
{
|
|
var currentVelocity = actor.Velocity;
|
|
var currentRotation = actor.Rotation;
|
|
|
|
|
|
if (Stamina is not 100 && staminaRecoveryInterval.AllowUpdateWithoutReset)
|
|
{
|
|
Stamina += 10 * deltaTime;
|
|
}
|
|
|
|
CurrentState?.UpdateVelocity(ref currentVelocity, deltaTime);
|
|
CurrentState?.UpdateRotation(ref currentRotation, deltaTime);
|
|
|
|
if (gravityDamping.TryGetRelease(out var damping) && currentVelocity.y < 0)
|
|
{
|
|
currentVelocity.y = Mathf.Clamp(currentVelocity.y + damping, float.MinValue, 0);
|
|
}
|
|
|
|
if (allowMovement && _health.IsAlive && actor.RigidbodyComponent.IsKinematic is false)
|
|
{
|
|
switch ( CurrentState,_override.IsOvering)
|
|
{
|
|
case (IPlayerParachuteState,true):
|
|
case (_,false):
|
|
actor.Velocity = currentVelocity;
|
|
actor.Rotation = currentRotation;
|
|
//if (currentVelocity.sqrMagnitude > 0.01f)
|
|
keepVelocity = currentVelocity;
|
|
break;
|
|
}
|
|
}
|
|
else if(actor.RigidbodyComponent.IsKinematic is false)
|
|
{
|
|
actor.Velocity = default;
|
|
}
|
|
|
|
var localVelocity = transform.InverseTransformDirection(Velocity) / referenceSpeed;
|
|
localVelocity.y = 0;
|
|
LocomotionBasedVelocity = localVelocity;
|
|
|
|
|
|
if (actor.IsStable is false && actor.IsGrounded is false)
|
|
{
|
|
if (cacheGravity != default && (actor.VerticalVelocity - cacheGravity).sqrMagnitude < 4)
|
|
{
|
|
gravityDamage.Release(actor.VerticalVelocity);
|
|
}
|
|
|
|
cacheGravity = actor.VerticalVelocity;
|
|
//Debug.Log($"IsGround:{actor.IsGrounded}\tIsStable:{actor.IsStable}\tVelocity:{actor.VerticalVelocity}");
|
|
}
|
|
else if (actor.IsStable && gravityDamage.TryGetRelease(out var currentGravity))
|
|
{
|
|
cacheGravity = default;
|
|
var value = currentGravity.y;
|
|
//Debug.Log($"Vector:{currentVelocity}\tMagnitude:{value}\tGravity:{currentGravity}");
|
|
//Debug.Log(value);
|
|
OnCommand?.Invoke(new OnPlayerLandCommand());
|
|
switch (value)
|
|
{
|
|
case > 0:
|
|
break;
|
|
case < -16:
|
|
UnityEntity.Invoke<DamageMessage>(new DamageMessage()
|
|
{
|
|
Damage = value < -30 ? int.MaxValue : (int)math.abs(value) * 2,
|
|
DamageType = new GravityDamage(),
|
|
Position = actor.Position,
|
|
Rotation = actor.Rotation,
|
|
Initiator = UnityEntity,
|
|
Target = UnityEntity,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
var y = actor.ColliderComponent.Size.y;
|
|
//topBlocked = Physics.Raycast(actor.Position, actor.Up, out _, actor.BodySize.y, actor.stableLayerMask) ;
|
|
// topBlocked = Physics.CheckSphere(actor.Position + actor.Up * (actor.BodySize.y - 0.1f), 0.1f,
|
|
// actor.stableLayerMask);
|
|
|
|
var walkState = StateDictionary[typeof(Walk)].As<Walk>();
|
|
topBlocked=Physics.CheckBox(actor.Position + actor.Up * (walkState.initialSize.y - 0.1f), new Vector3(actor.BodySize.x/3,0.2f,actor.BodySize.x/3), actor.Rotation, actor.stableLayerMask);
|
|
|
|
// if (actor.IsGrounded is false && actor.IsStable is false)
|
|
// {
|
|
// landFreeze.Reset();
|
|
//
|
|
// }
|
|
|
|
if(actor.IsGrounded)
|
|
{
|
|
switch (CurrentState)
|
|
{
|
|
case IPlayerSprintState:
|
|
case IPlayerClimbState:
|
|
case IPlayerSlideState:
|
|
case IPlayerVaultState:
|
|
case IPlayerRunState:
|
|
if (Physics.Raycast(actor.Position + Vector3.up, actor.Forward, out var impactHit,
|
|
actor.BodySize.x + 0.1f,
|
|
actor.stableLayerMask))
|
|
{
|
|
//Debug.Log(impactHit.collider.name);
|
|
if (impactHit.collider.TryGetComponentAny<IScenePlayerImpact>(out var impact))
|
|
{
|
|
impact.OnPlayerImpact(Entity, transform.localToWorldMatrix);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (isUnStable)
|
|
{
|
|
landFreeze.Reset();
|
|
isUnStable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
isUnStable = true;
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
// if (BITAppForUnity.IsPlaying is false) return;
|
|
// Gizmos.color = Color.red;
|
|
// var walkState = StateDictionary[typeof(Walk)].As<Walk>();
|
|
// Gizmos.DrawWireCube(actor.Position + actor.Up * (walkState.initialSize.y - 0.1f), new Vector3(actor.BodySize.x/3,0.2f,actor.BodySize.x/3));
|
|
}
|
|
|
|
public override void OnLateUpdate(float deltaTime)
|
|
{
|
|
if (_override.IsOvering)
|
|
{
|
|
locationAdditive.SetGlobalPosition(fpvPoint.position);
|
|
locationAdditive.SetGlobalRotation(fpvPoint.rotation);
|
|
LookInput = MathV.TransientRotationAxis(((Quaternion)FpvRotation).eulerAngles) ;
|
|
return;
|
|
}
|
|
|
|
|
|
//if (allowMovement.Allow is false) return;
|
|
|
|
var rotation = Quaternion.Euler(LookInput);
|
|
if (_knockdown.IsKnockdown)
|
|
{
|
|
rotation = Quaternion.Euler(LookInput.x, LookInput.y, -8);
|
|
}
|
|
|
|
if (LimitViewAngle is not 0)
|
|
{
|
|
float angleDifference = Quaternion.Angle(rotation, FpvRotation);
|
|
|
|
// 如果角度差大于最大允许角度差,进行限制
|
|
if (angleDifference > LimitViewAngle)
|
|
{
|
|
// 将当前旋转限制在最大允许角度差的范围内
|
|
rotation = Quaternion.Lerp(
|
|
rotation,
|
|
Quaternion.RotateTowards(FpvRotation, rotation, LimitViewAngle),
|
|
5 * deltaTime
|
|
);
|
|
|
|
LookInput = MathV.TransientRotationAxis(rotation.eulerAngles);
|
|
}
|
|
}
|
|
|
|
locationAdditive.SetGlobalRotation(rotation);
|
|
|
|
if (CurrentState is IPlayerFixedState)
|
|
{
|
|
}
|
|
|
|
CurrentCameraPosition.being =
|
|
Vector3.Lerp(CurrentCameraPosition.being, CurrentCameraPosition.shouldBe, 5 * deltaTime);
|
|
|
|
var rawPos = Quaternion.Euler(0,LookInput.y,0) * CurrentCameraPosition;
|
|
|
|
var newPos = transform.InverseTransformDirection(rawPos);
|
|
|
|
locationAdditive.AddPosition(newPos);
|
|
|
|
ViewCenter = CurrentCameraPosition.being;
|
|
|
|
centerAdditive.AddPosition(Vector3.up * ViewCenter.y);
|
|
|
|
if (_playerCameraService.IsCameraActivated)
|
|
{
|
|
ViewRotation = rotation;
|
|
//ViewForward = additiveTransform.position + Quaternion.Euler(LookInput) * Vector3.forward;
|
|
ViewForward = ViewRotation * Vector3.forward;
|
|
|
|
FocusPoint = Position + Rotation * ViewCenter + ViewForward * 8f;
|
|
}
|
|
|
|
|
|
switch (CurrentState)
|
|
{
|
|
case Sprint:
|
|
case Run:
|
|
case Walk:
|
|
case Crouch:
|
|
break;
|
|
default:
|
|
|
|
centerAdditive.SetGlobalRotation(rotation);
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
private float _stamina = 100;
|
|
public float2 InputVector=>new(MovementInput.x,MovementInput.z);
|
|
public float Stamina
|
|
{
|
|
get=>_stamina;
|
|
set
|
|
{
|
|
if (value < _stamina)
|
|
{
|
|
staminaRecoveryInterval.Reset();
|
|
}
|
|
_stamina = Mathf.Clamp(value, 0, 100);
|
|
OnStaminaChanged?.Invoke(_stamina);
|
|
}
|
|
}
|
|
private readonly IntervalUpdate staminaRecoveryInterval = new(8);
|
|
private int _id;
|
|
public int LimitViewAngle { get; set; }
|
|
public event Action<float> OnStaminaChanged;
|
|
public float3 FpvPosition => fpvPoint.position;
|
|
public quaternion FpvRotation => fpvPoint.rotation;
|
|
public float3 FpvLocalPosition =>fpvPoint.localPosition;
|
|
public quaternion FpvLocalRotation => fpvPoint.localRotation;
|
|
|
|
public void AddViewEuler(float2 euler)
|
|
{
|
|
LookInput = new Vector3
|
|
{
|
|
x = LookInput.x + euler.x,
|
|
y = LookInput.y + euler.y
|
|
};
|
|
LookInput = MathV.TransientRotationAxis(LookInput);
|
|
}
|
|
public event Func<bool> TryOpenParachute;
|
|
public event Action OnParachuteOpened;
|
|
public event Action OnParachuteClosed;
|
|
|
|
internal void InvokeOpenParachute()
|
|
{
|
|
OnParachuteOpened?.Invoke();
|
|
parachuteModel.gameObject.SetActive(true);
|
|
}
|
|
internal void InvokeCloseParachute()
|
|
{
|
|
OnParachuteClosed?.Invoke();
|
|
parachuteModel.gameObject.SetActive(false);
|
|
}
|
|
|
|
public int Id { get; } = new ConstantHash(nameof(IEntityMovement));
|
|
|
|
public void Serialize(BinaryWriter writer)
|
|
{
|
|
writer.WriteFloat3(actor.Position);
|
|
writer.WriteFloat3(actor.Velocity);
|
|
writer.Write(LookInput.y);
|
|
writer.Write(LookInputDelta.y);
|
|
writer.Write(actor.IsGrounded);
|
|
|
|
LookInputDelta = default;
|
|
|
|
}
|
|
public void Deserialize(BinaryReader reader)
|
|
{
|
|
//reader.ReadFloat3();
|
|
//reader.ReadQuaternion();
|
|
}
|
|
|
|
// private void OnCollisionEnter(Collision other)
|
|
// {
|
|
// if (CurrentState is not (
|
|
// IPlayerRunState
|
|
// or IPlayerSprintState
|
|
// or IPlayerClimbState
|
|
// or IPlayerVaultState
|
|
// or IPlayerLinkState
|
|
// or IPlayerSlideState
|
|
// )) return;
|
|
//
|
|
// if (other.collider.TryGetComponentAny<IScenePlayerImpact>(out var impact))
|
|
// {
|
|
// impact.OnPlayerImpact(Entity, transform.localToWorldMatrix);
|
|
// }
|
|
// }
|
|
public Bounds Bounds => new(actor.Center, actor.BodySize);
|
|
Transform ISensorTarget.Transform => Transform;
|
|
public void Detected(float weight, ISensor sensor, object sender)
|
|
{
|
|
OnDetected?.Invoke(weight, sensor, sender);
|
|
// BIT4Log.Log<ISensorTarget>($"Been detected by{sensor},weight:{weight}");
|
|
}
|
|
public event Action<float, ISensor, object> OnDetected;
|
|
}
|
|
|
|
}
|