1023 lines
27 KiB
C#
1023 lines
27 KiB
C#
![]() |
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Data;
|
||
|
using System.Globalization;
|
||
|
using System.Linq;
|
||
|
using BITKit;
|
||
|
using BITKit.Entities;
|
||
|
using BITKit.Physics;
|
||
|
using BITKit.UX;
|
||
|
using BITKit.WorldNode;
|
||
|
using Cinemachine;
|
||
|
using Kinemation.MotionWarping.Runtime.Core;
|
||
|
using Lightbug.CharacterControllerPro.Core;
|
||
|
using Microsoft.Extensions.DependencyInjection;
|
||
|
using Microsoft.Extensions.Logging;
|
||
|
using Net.Project.B.Health;
|
||
|
using Net.Project.B.Interaction;
|
||
|
using Net.Project.B.WorldNode;
|
||
|
using NodeCanvas.Framework;
|
||
|
using ParadoxNotion;
|
||
|
using Project.B.Player;
|
||
|
using Unity.AI.Navigation;
|
||
|
using Unity.Collections.LowLevel.Unsafe;
|
||
|
using Unity.Mathematics;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.AI;
|
||
|
using UnityEngine.InputSystem;
|
||
|
using UnityEngine.InputSystem.Controls;
|
||
|
using UnityEngine.InputSystem.Interactions;
|
||
|
|
||
|
namespace Project.B.CharacterController
|
||
|
{
|
||
|
public class PlayerCharacterController : ICharacterController, IPlayerCharacterController, IDisposable
|
||
|
{
|
||
|
private readonly ILogger<PlayerCharacterController> _logger;
|
||
|
internal readonly MotionWarping MotionWarping;
|
||
|
internal readonly IEntitiesService EntitiesService;
|
||
|
internal readonly Transform Transform;
|
||
|
internal readonly IEntity Entity;
|
||
|
internal readonly ICharacterService CharacterService;
|
||
|
internal readonly CharacterActor CharacterActor;
|
||
|
internal readonly CinemachineVirtualCameraBase VirtualCamera;
|
||
|
internal readonly Transform PlayerView;
|
||
|
internal readonly Transform PlayerAnimationView;
|
||
|
internal readonly InputActionGroup InputActionGroup;
|
||
|
internal readonly IPlayerKeyMap<InputAction> KeyMap;
|
||
|
internal readonly IWrapper<PlayerSettings> PlayerSettings;
|
||
|
internal readonly IMainTicker MainTicker;
|
||
|
internal readonly IAfterTicker AfterTicker;
|
||
|
internal readonly IClosePointProvider StepUpPointProvider;
|
||
|
internal readonly IClosePointProvider ClimbPointProvider;
|
||
|
internal readonly IHealthService HealthService;
|
||
|
internal readonly IKnockedService KnockedService;
|
||
|
internal readonly IWorldInteractionService WorldInteractionService;
|
||
|
internal readonly IUXService UXService;
|
||
|
|
||
|
public PlayerCharacterController(Transform transform, IEntity entity, ICharacterService characterService,
|
||
|
CharacterActor characterActor, CinemachineVirtualCameraBase virtualCamera,
|
||
|
IPlayerKeyMap<InputAction> keyMap,
|
||
|
IWrapper<PlayerSettings> playerSettings, IAfterTicker afterTicker, IBlackboard blackboard,
|
||
|
InputActionGroup inputActionGroup, IMainTicker mainTicker, IHealthService healthService,
|
||
|
IKnockedService knockedService, ILogger<PlayerCharacterController> logger,
|
||
|
IWorldInteractionService worldInteractionService, IUXService uxService, IEntitiesService entitiesService, MotionWarping motionWarping)
|
||
|
{
|
||
|
Transform = transform;
|
||
|
Entity = entity;
|
||
|
CharacterService = characterService;
|
||
|
CharacterActor = characterActor;
|
||
|
VirtualCamera = virtualCamera;
|
||
|
KeyMap = keyMap;
|
||
|
PlayerSettings = playerSettings;
|
||
|
AfterTicker = afterTicker;
|
||
|
InputActionGroup = inputActionGroup;
|
||
|
MainTicker = mainTicker;
|
||
|
HealthService = healthService;
|
||
|
KnockedService = knockedService;
|
||
|
_logger = logger;
|
||
|
WorldInteractionService = worldInteractionService;
|
||
|
UXService = uxService;
|
||
|
EntitiesService = entitiesService;
|
||
|
MotionWarping = motionWarping;
|
||
|
|
||
|
PlayerView = blackboard.GetVariable<Transform>("player_view").value;
|
||
|
PlayerAnimationView = blackboard.GetVariable<Transform>("player_model_view").value;
|
||
|
|
||
|
|
||
|
foreach (var x in Transform.GetComponentsInChildren<SkinnedMeshRenderer>())
|
||
|
{
|
||
|
x.updateWhenOffscreen = true;
|
||
|
}
|
||
|
|
||
|
StepUpPointProvider =
|
||
|
new GetClosePointFromCollider(blackboard.GetVariable<Transform>("player_vault_point").value)
|
||
|
{
|
||
|
LayerMask = characterActor.stableLayerMask,
|
||
|
MinHeight = 0.1f,
|
||
|
MaxHeight = 1.2f,
|
||
|
Distance = 1f,
|
||
|
};
|
||
|
ClimbPointProvider =
|
||
|
new GetClosePointFromCollider(blackboard.GetVariable<Transform>("player_climb_point").value)
|
||
|
{
|
||
|
LayerMask = characterActor.stableLayerMask,
|
||
|
MinHeight = 0f,
|
||
|
MaxHeight = 0.4f
|
||
|
};
|
||
|
|
||
|
Initialize();
|
||
|
|
||
|
AllowRun.AddElement(this);
|
||
|
AllowSprint.AddElement(this);
|
||
|
|
||
|
KnockedService.OnKnocked += OnKnocked;
|
||
|
HealthService.OnHealthChanged += OnHealthChanged;
|
||
|
WorldInteractionService.OnInteraction += OnInteraction;
|
||
|
|
||
|
ForceRootMotion.AddListener(x =>
|
||
|
{
|
||
|
CharacterActor.UseRootMotion = x;
|
||
|
});
|
||
|
|
||
|
foreach (var x in Transform.GetComponentsInChildren<Collider>())
|
||
|
{
|
||
|
CharacterActor.PhysicsComponent.IgnoreCollision(x.transform,true);
|
||
|
}
|
||
|
|
||
|
DisableCollision.AddListener(x =>
|
||
|
{
|
||
|
characterActor.ColliderComponent.enabled = !x;
|
||
|
characterActor.rootMotionVelocityType = x
|
||
|
? PhysicsActor.RootMotionVelocityType.SetVelocity
|
||
|
: PhysicsActor.RootMotionVelocityType.SetPlanarVelocity;
|
||
|
});
|
||
|
|
||
|
AllowOverride.AddListener(x =>
|
||
|
{
|
||
|
DisableCollision.SetElements(AllowOverride,x);
|
||
|
DisableStateTransition.SetElements(AllowOverride,x);
|
||
|
CharacterActor.enabled = !x;
|
||
|
CharacterActor.IsKinematic = x;
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
private void OnKnocked(int arg1, bool arg2)
|
||
|
{
|
||
|
if (arg1 != Entity.Id) return;
|
||
|
if (arg2)
|
||
|
{
|
||
|
TransitionState<ICharacterKnocked>();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnHealthChanged(int arg1, int arg2, int arg3, object arg4)
|
||
|
{
|
||
|
if(arg1!=Entity.Id)return;
|
||
|
|
||
|
if (CurrentState is null or ICharacterKnocked && arg3 >= 0 && KnockedService.Knocked.Contains(Entity.Id) is false)
|
||
|
{
|
||
|
TransitionState<ICharacterStateWalk>();
|
||
|
}
|
||
|
else if (arg3 < 0)
|
||
|
{
|
||
|
DisposeState();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool Enabled { get; set; }
|
||
|
public ICharacterState CurrentState { get; set; }
|
||
|
|
||
|
public event Action<ICharacterState, ICharacterState> OnStateChanged;
|
||
|
public event Action<ICharacterState> OnStateRegistered;
|
||
|
public event Action<ICharacterState> OnStateUnRegistered;
|
||
|
public IDictionary<Type, ICharacterState> StateDictionary { get; } = new Dictionary<Type, ICharacterState>();
|
||
|
internal ICharacterStateData CurrentStateData => CurrentState as ICharacterStateData;
|
||
|
internal float2 InputDirection { get; private set; }
|
||
|
public float2 InputView { get; set; }
|
||
|
internal float3 CurrentCameraLocalPosition;
|
||
|
internal bool RequestJump { get; set; }
|
||
|
internal bool JumpedThisTime { get; set; }
|
||
|
internal bool RequestedCrouch { get; set; }
|
||
|
|
||
|
private bool _inWaterThisFrame;
|
||
|
|
||
|
private readonly DoubleBuffer<Vector3> _fallGravity=new();
|
||
|
|
||
|
/// <summary>
|
||
|
/// 与其同步的碰撞体,通常是翻越,攀爬与跟随运动等
|
||
|
/// </summary>
|
||
|
internal Collider SyncCollider { get; set; }
|
||
|
|
||
|
internal Vector3 SyncPosition{ get; set; }
|
||
|
internal IList<int> LimitedViewAngle { get; private set; } = new List<int>();
|
||
|
private quaternion _lastOriginalRotation = quaternion.identity;
|
||
|
private float2 _newView;
|
||
|
private bool _clearView;
|
||
|
private bool _shouldRun;
|
||
|
|
||
|
private bool _requestRun;
|
||
|
|
||
|
private readonly IntervalUpdate _disableLadder = new(0.32f);
|
||
|
|
||
|
private readonly HashSet<int> _ignoreTouchId = new();
|
||
|
|
||
|
private void OnAfterTick(float delta)
|
||
|
{
|
||
|
UpdateRotationInternal(delta);
|
||
|
}
|
||
|
|
||
|
private void UpdateRotationInternal(float delta)
|
||
|
{
|
||
|
var lastView = InputView;
|
||
|
|
||
|
foreach (var func in OnInputViewFactor.CastAsFunc())
|
||
|
{
|
||
|
_newView = func.Invoke(_newView);
|
||
|
}
|
||
|
|
||
|
|
||
|
InputView += _newView;
|
||
|
|
||
|
var view = InputView;
|
||
|
view.y = math.clamp(InputView.y, -80, 80);
|
||
|
InputView = view;
|
||
|
|
||
|
var angularVelocity = InputView - lastView;
|
||
|
AngularVelocity = new(angularVelocity.x, angularVelocity.y, 0);
|
||
|
|
||
|
|
||
|
if (CurrentState is null) return;
|
||
|
|
||
|
quaternion viewRot = _lastOriginalRotation;
|
||
|
quaternion rot = CharacterActor.Rotation;
|
||
|
|
||
|
|
||
|
|
||
|
CurrentState?.UpdateRotation(ref CurrentCameraLocalPosition, ref rot, ref viewRot, delta);
|
||
|
|
||
|
ViewPosition = Transform.position + Transform.rotation * CurrentCameraLocalPosition;
|
||
|
|
||
|
//ViewRotation = Quaternion.Euler(InputView.y, InputView.x, 0);
|
||
|
|
||
|
if (ForceRootMotion.Allow)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Rotation = rot;
|
||
|
}
|
||
|
|
||
|
|
||
|
{
|
||
|
var startPos = CharacterActor.Position + Vector3.up * CurrentCameraLocalPosition.y;
|
||
|
|
||
|
var newCameraPosition = (Vector3)Position + CharacterActor.Rotation * CurrentCameraLocalPosition;
|
||
|
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case not ICharacterSeating or ICharacterStateAnimation when Physics.Linecast(startPos,startPos+CharacterActor.Forward*CurrentCameraLocalPosition.z, out var hit, CharacterActor.stableLayerMask) && hit.collider.isTrigger is false:
|
||
|
//var newPos = startPos + (Quaternion)ViewRotation * Vector3.forward * (hit.distance - 0.04f);
|
||
|
|
||
|
//Debug.Log(hit.distance);
|
||
|
|
||
|
var newPos = CharacterActor.transform.InverseTransformPoint(Vector3.MoveTowards( hit.point,startPos,0.04f));
|
||
|
PlayerView.transform.localPosition = newPos;
|
||
|
DisableModel.AddElement(1);
|
||
|
break;
|
||
|
default:
|
||
|
PlayerView.transform.localPosition = CurrentCameraLocalPosition;
|
||
|
DisableModel.RemoveElement(1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//AdditiveCameraQuaternion.Set(0,_playerAnimationView.localRotation);
|
||
|
|
||
|
Quaternion finalRot = viewRot;
|
||
|
foreach (var (index, value) in AdditiveCameraQuaternion)
|
||
|
{
|
||
|
finalRot *= value;
|
||
|
}
|
||
|
|
||
|
PlayerView.transform.rotation = finalRot;
|
||
|
|
||
|
if (_clearView)
|
||
|
_newView = default;
|
||
|
|
||
|
_lastOriginalRotation = finalRot;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
public void Initialize()
|
||
|
{
|
||
|
CharacterService.Dictionary.Add(Entity.Id, this);
|
||
|
|
||
|
|
||
|
AfterTicker.Add(OnAfterTick);
|
||
|
MainTicker.Add(OnTick);
|
||
|
|
||
|
VirtualCamera.enabled = true;
|
||
|
|
||
|
InputActionGroup.RegisterCallback(KeyMap.MovementKey, OnMovement);
|
||
|
InputActionGroup.RegisterCallback(KeyMap.ViewKey, OnView);
|
||
|
InputActionGroup.RegisterCallback(KeyMap.JumpKey, OnJump);
|
||
|
InputActionGroup.RegisterCallback(KeyMap.CrouchKey, OnCrouched);
|
||
|
InputActionGroup.RegisterCallback(KeyMap.RunKey, OnRun);
|
||
|
|
||
|
InputActionGroup.allowInput.AddElement(this);
|
||
|
}
|
||
|
|
||
|
private void OnTick(float obj)
|
||
|
{
|
||
|
if (BITInputSystem.AllowInput.Allow is false)
|
||
|
{
|
||
|
InputDirection = default;
|
||
|
_newView = default;
|
||
|
}
|
||
|
|
||
|
if (Touchscreen.current is { wasUpdatedThisFrame: true })
|
||
|
{
|
||
|
_clearView = true;
|
||
|
}
|
||
|
|
||
|
if (Touchscreen.current is not null)
|
||
|
{
|
||
|
foreach (var touchControl in Touchscreen.current.touches)
|
||
|
{
|
||
|
var touchId = touchControl.touchId.value;
|
||
|
|
||
|
if (touchControl.press.isPressed is false)
|
||
|
{
|
||
|
_ignoreTouchId.Remove(touchId);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (UXService.TryPick(touchControl.startPosition.ReadValue(), out var ve))
|
||
|
{
|
||
|
_ignoreTouchId.Add(touchId);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(_ignoreTouchId.Contains(touchId))continue;
|
||
|
|
||
|
var delta = touchControl.delta.ReadValue() * PlayerSettings.Value.TouchSensitivity;
|
||
|
delta.y = -delta.y;
|
||
|
_newView += (float2)delta;
|
||
|
|
||
|
_newView.y = math.clamp(_newView.y, -80, 80);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
private void OnRun(InputAction.CallbackContext obj)
|
||
|
{
|
||
|
_requestRun = obj.JustPressed();
|
||
|
|
||
|
if (AllowTpsCamera.Allow)
|
||
|
{
|
||
|
if (MathV.GetLength(InputDirection) is 0) return;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (InputDirection.y <= 0 && SelfVelocity.z <= 0) return;
|
||
|
}
|
||
|
|
||
|
switch (obj)
|
||
|
{
|
||
|
case { interaction: MultiTapInteraction, performed: true }:
|
||
|
case { interaction: PressInteraction, performed: true }:
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterKnocked:
|
||
|
break;
|
||
|
case ICharacterStateWalk when _shouldRun && Stamina > 8:
|
||
|
TransitionState<ICharacterSprint>();
|
||
|
break;
|
||
|
default:
|
||
|
if (CurrentState is ICharacterStateRun && Stamina > 8)
|
||
|
{
|
||
|
TransitionState<ICharacterSprint>();
|
||
|
}
|
||
|
|
||
|
_shouldRun = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnCrouched(InputAction.CallbackContext obj)
|
||
|
{
|
||
|
if (obj is not { interaction: PressInteraction, performed: true }) return;
|
||
|
|
||
|
if (CurrentState is ICharacterParachute)
|
||
|
{
|
||
|
TransitionState<ICharacterFreeFall>();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterStateStepUp:
|
||
|
case ICharacterStateClimb:
|
||
|
case ICharacterSeating:
|
||
|
_shouldRun = false;
|
||
|
TransitionState<ICharacterStateWalk>();
|
||
|
return;
|
||
|
case ICharacterStateRun:
|
||
|
case ICharacterSprint when Stamina>5:
|
||
|
_shouldRun = false;
|
||
|
TransitionState<ICharacterSliding>();
|
||
|
Stamina -= 30;
|
||
|
return;
|
||
|
case ICharacterKnocked:
|
||
|
return;
|
||
|
case ICharacterStateCrouched:
|
||
|
if (CharacterActor.CheckSize(CharacterActor.DefaultBodySize))
|
||
|
{
|
||
|
TransitionState<ICharacterStateWalk>();
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
default:
|
||
|
_shouldRun = false;
|
||
|
RequestedCrouch = true;
|
||
|
TransitionState<ICharacterStateCrouched>();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnJump(InputAction.CallbackContext obj)
|
||
|
{
|
||
|
if (obj.JustPressed())
|
||
|
{
|
||
|
if (Velocity.y < -8)
|
||
|
{
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterFreeFall:
|
||
|
{
|
||
|
TransitionState<ICharacterParachute>();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterKnocked:
|
||
|
RequestJump = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
RequestJump = obj switch
|
||
|
{
|
||
|
{ interaction: PressInteraction, performed: true } => true,
|
||
|
{ interaction: PressInteraction, canceled: true } => false,
|
||
|
_ => RequestJump
|
||
|
};
|
||
|
if (obj is { interaction: PressInteraction })
|
||
|
{
|
||
|
if (JumpedThisTime && RequestJump)
|
||
|
{
|
||
|
JumpedThisTime = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool _isTouching;
|
||
|
|
||
|
private void OnView(InputAction.CallbackContext obj)
|
||
|
{
|
||
|
if (Touchscreen.current is null)
|
||
|
{
|
||
|
if (BITAppForUnity.AllowCursor.Allow) return;
|
||
|
}
|
||
|
|
||
|
if (Touchscreen.current is { touches: { Count: > 0 } }) return;
|
||
|
|
||
|
_newView = (float2)obj.ReadValue<Vector2>() * (obj.control.device) switch
|
||
|
{
|
||
|
Gamepad => PlayerSettings.Value.GamePadSensitivity,
|
||
|
_ when obj.control.usages.ToString() is "OnScreen" => PlayerSettings.Value.TouchSensitivity,
|
||
|
_ when Touchscreen.current is { touches: { Count: > 0 } } => PlayerSettings.Value.TouchSensitivity,
|
||
|
Touchscreen => PlayerSettings.Value.TouchSensitivity,
|
||
|
Mouse => PlayerSettings.Value.Sensitivity * 0.022f,
|
||
|
_ => PlayerSettings.Value.Sensitivity
|
||
|
};
|
||
|
|
||
|
_clearView = obj.control.device switch
|
||
|
{
|
||
|
Gamepad => false,
|
||
|
_ => true,
|
||
|
};
|
||
|
|
||
|
_newView.y = -_newView.y;
|
||
|
}
|
||
|
|
||
|
private Vector2 _touchscreenStartPos;
|
||
|
|
||
|
private void OnMovement(InputAction.CallbackContext obj)
|
||
|
{
|
||
|
if (_requestRun)
|
||
|
{
|
||
|
_shouldRun = true;
|
||
|
}
|
||
|
|
||
|
InputDirection = obj.ReadValue<Vector2>();
|
||
|
|
||
|
MoveInput = new float3(InputDirection.x,0, InputDirection.y);
|
||
|
|
||
|
switch (InputDirection)
|
||
|
{
|
||
|
case {y : 0,x: 0} when AllowTpsCamera.Allow:
|
||
|
case { y: <= 0 } when AllowTpsCamera.Allow is false:
|
||
|
_shouldRun = false;
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
|
||
|
case ICharacterStateRun:
|
||
|
case ICharacterSprint:
|
||
|
TransitionState<ICharacterStateWalk>();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void UpdateState(float deltaTime)
|
||
|
{
|
||
|
if (CharacterActor.IsFalling && CharacterActor.IsGrounded is false)
|
||
|
{
|
||
|
_fallGravity.Release(Velocity);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (_fallGravity.TryGetRelease(out var fall))
|
||
|
{
|
||
|
OnLand?.Invoke(fall,0);
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterKnocked:
|
||
|
case ICharacterFreeFall:
|
||
|
{
|
||
|
HealthService.AddHealth(Entity.Id, -101, this);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (_refreshStamina.AllowUpdateWithoutReset)
|
||
|
{
|
||
|
_floatStamina += 32 * deltaTime;
|
||
|
if (_floatStamina > 0)
|
||
|
{
|
||
|
var addStamina = (int)_floatStamina;
|
||
|
Stamina += addStamina;
|
||
|
_floatStamina -= addStamina;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterStateCrouched:
|
||
|
case ICharacterSliding:
|
||
|
if (RequestJump)
|
||
|
{
|
||
|
if (CharacterActor.CheckSize(CharacterActor.DefaultBodySize))
|
||
|
{
|
||
|
RequestJump = CurrentState is ICharacterSliding;
|
||
|
TransitionState<ICharacterStateWalk>();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (DisableStateTransition.Allow)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var isSizeGood = CharacterActor.CheckSize(CharacterActor.DefaultBodySize);
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterStateCrouched when RequestedCrouch is false && isSizeGood:
|
||
|
{
|
||
|
TransitionState<ICharacterStateIdle>();
|
||
|
}
|
||
|
break;
|
||
|
case ICharacterStateWalk or ICharacterStateRun when isSizeGood is false:
|
||
|
TransitionState<ICharacterStateCrouched>();
|
||
|
break;
|
||
|
case ICharacterStateWalk or ICharacterStateIdle or ICharacterStateCrouched or ICharacterSliding
|
||
|
when _shouldRun && AllowRun.Allow:
|
||
|
if (isSizeGood)
|
||
|
{
|
||
|
if (AllowTpsCamera.Allow || InputDirection.y > 0)
|
||
|
TransitionState<ICharacterStateRun>();
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
case ICharacterSprint:
|
||
|
case ICharacterStateRun:
|
||
|
switch (AllowRun.Allow, AllowTpsCamera.Allow, InputDirection)
|
||
|
{
|
||
|
case (false, _, _):
|
||
|
case (_, true, { x: 0, y: 0 }):
|
||
|
case (_, false, { y: <= 0 }):
|
||
|
TransitionState<ICharacterStateWalk>();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (SelfVelocity.y < -20)
|
||
|
{
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterStateIdle:
|
||
|
case ICharacterStateWalk:
|
||
|
case ICharacterStateRun:
|
||
|
case ICharacterSprint:
|
||
|
case ICharacterStateCrouched:
|
||
|
{
|
||
|
TransitionState<ICharacterFreeFall>();
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterStateIdle:
|
||
|
case ICharacterStateWalk:
|
||
|
case ICharacterStateRun:
|
||
|
case ICharacterSprint:
|
||
|
case ICharacterStateClimb:
|
||
|
if (RequestJump)
|
||
|
{
|
||
|
if (StepUpPointProvider.TryGetValue(out var nextPosition, out var nextCollider))
|
||
|
{
|
||
|
SyncCollider = nextCollider;
|
||
|
SyncPosition = nextPosition;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
var startPos = SyncPosition + CharacterActor.Rotation * new Vector3(0,-0.2f,-0.1f);
|
||
|
if (nextCollider.Raycast(new Ray(startPos, SyncPosition - startPos), out var hit,
|
||
|
int.MaxValue))
|
||
|
{
|
||
|
|
||
|
if (nextCollider.Raycast(new Ray(startPos + hit.normal * -2, hit.normal), out hit,
|
||
|
int.MaxValue))
|
||
|
{
|
||
|
foreach (var collider in Physics.OverlapSphere(hit.point, 0.2f,
|
||
|
CharacterActor.stableLayerMask))
|
||
|
{
|
||
|
if (collider == SyncCollider) continue;
|
||
|
TransitionState<ICharacterStateStepUp>();
|
||
|
throw new OperationCanceledException();
|
||
|
}
|
||
|
|
||
|
if (Vector3.Distance(hit.point, SyncPosition) < 0.5f)
|
||
|
{
|
||
|
|
||
|
if(CurrentState is ICharacterStateClimb)
|
||
|
{
|
||
|
if ( /*Physics.Raycast(hit.point + hit.normal * -0.1f, Vector3.down, out _,
|
||
|
2f,
|
||
|
CharacterActor.stableLayerMask) || */ MoveInput.z>0)
|
||
|
{
|
||
|
TransitionState<ICharacterStateVault>();
|
||
|
throw new OperationCanceledException();
|
||
|
}
|
||
|
|
||
|
TransitionState<ICharacterStateStepUp>();
|
||
|
throw new OperationCanceledException();
|
||
|
}
|
||
|
|
||
|
TransitionState<ICharacterStateVault>();
|
||
|
throw new OperationCanceledException();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
TransitionState<ICharacterStateStepUp>();
|
||
|
}
|
||
|
catch (OperationCanceledException)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (CurrentState is ICharacterStateClimb)
|
||
|
{
|
||
|
JumpedThisTime = true;
|
||
|
TransitionState<ICharacterStateWalk>();
|
||
|
CharacterActor.ForceNotGrounded();
|
||
|
CharacterActor.Position -= CharacterActor.Forward * 0.4f;
|
||
|
CharacterActor.Velocity +=
|
||
|
(CharacterActor.GroundContactNormal + CharacterActor.Up + CharacterActor.Forward)
|
||
|
.normalized * 8 + CharacterActor.Forward * 8f;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (CurrentState is not ICharacterStateClimb)
|
||
|
{
|
||
|
if (ClimbPointProvider.TryGetValue(out nextPosition, out nextCollider))
|
||
|
{
|
||
|
SyncCollider = nextCollider;
|
||
|
SyncPosition = nextPosition;
|
||
|
TransitionState<ICharacterStateClimb>();
|
||
|
RequestJump = false;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
CurrentState?.OnStateUpdate(deltaTime);
|
||
|
|
||
|
float3 currentVelocity = CharacterActor.Velocity;
|
||
|
|
||
|
CurrentState?.BeforeUpdateVelocity(deltaTime);
|
||
|
CurrentState?.UpdateVelocity(ref currentVelocity, deltaTime);
|
||
|
|
||
|
CurrentState?.AfterUpdateVelocity(deltaTime);
|
||
|
|
||
|
ForceRootMotion.SetElements(8961844,CurrentState is null);
|
||
|
|
||
|
if (CurrentState is not null && CharacterActor.RigidbodyComponent.IsKinematic is false && AllowOverride.Allow is false)
|
||
|
{
|
||
|
CharacterActor.Velocity = currentVelocity;
|
||
|
}
|
||
|
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterStateWalk:
|
||
|
case ICharacterStateRun:
|
||
|
case ICharacterSprint:
|
||
|
case ICharacterStateCrouched:
|
||
|
case ICharacterFreeFall:
|
||
|
case ICharacterSwimming:
|
||
|
{
|
||
|
|
||
|
_inWaterThisFrame = false;
|
||
|
foreach (var trigger in CharacterActor.Triggers)
|
||
|
{
|
||
|
if(!trigger.gameObject)continue;
|
||
|
|
||
|
if (trigger.gameObject.layer == LayerMask.NameToLayer("Water"))
|
||
|
{
|
||
|
_inWaterThisFrame = true;
|
||
|
if (CurrentState is not ICharacterSwimming)
|
||
|
{
|
||
|
SyncCollider = trigger.collider3D;
|
||
|
TransitionState<ICharacterSwimming>();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_disableLadder.AllowUpdateWithoutReset)
|
||
|
{
|
||
|
|
||
|
if (trigger.transform.TryGetComponent<NavMeshLink>(out var link) &&
|
||
|
link.area == NavMesh.GetAreaFromName("Ladder"))
|
||
|
{
|
||
|
var ladder = Entity.ServiceProvider.GetRequiredService<ICharacterLadder>();
|
||
|
|
||
|
ladder.UpPoint = link.transform.TransformPoint(link.endPoint);
|
||
|
ladder.DownPoint = link.transform.TransformPoint(link.startPoint);
|
||
|
SyncCollider = link.GetComponent<Collider>();
|
||
|
|
||
|
if (Position.y < ladder.UpPoint.y - 0.5f)
|
||
|
{
|
||
|
TransitionState(ladder);
|
||
|
}
|
||
|
|
||
|
_disableLadder.Reset();
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (CurrentState is ICharacterSwimming && _inWaterThisFrame is false)
|
||
|
{
|
||
|
TransitionState<ICharacterStateIdle>();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
private float _floatStamina;
|
||
|
|
||
|
public void DisposeState()
|
||
|
{
|
||
|
CharacterActor.Velocity = default;
|
||
|
TransitionState(null);
|
||
|
}
|
||
|
|
||
|
public ICharacterState TransitionState<TState>() where TState : ICharacterState
|
||
|
{
|
||
|
//var state = StateDictionary[typeof(TState)];
|
||
|
if (_isDisposed) return null;
|
||
|
var state = Entity.ServiceProvider.GetRequiredService<TState>();
|
||
|
StateDictionary.TryAdd(typeof(TState), state);
|
||
|
if (state == null || Equals(CurrentState, state)) return state;
|
||
|
TransitionState(state);
|
||
|
|
||
|
return state;
|
||
|
}
|
||
|
|
||
|
public ICharacterState TransitionState(ICharacterState state)
|
||
|
{
|
||
|
if (Equals(CurrentState, state)) return state;
|
||
|
|
||
|
|
||
|
if (CurrentState is ICharacterStateClimb || state is ICharacterStateClimb)
|
||
|
{
|
||
|
_disableLadder.Reset();
|
||
|
}
|
||
|
|
||
|
if (state is not ICharacterStateCrouched or ICharacterSliding)
|
||
|
{
|
||
|
RequestedCrouch = false;
|
||
|
}
|
||
|
|
||
|
CurrentState?.OnStateExit(CurrentState, state);
|
||
|
|
||
|
if (state is not null)
|
||
|
{
|
||
|
//_characterActor.IsKinematic = false;
|
||
|
CharacterActor.ColliderComponent.enabled = true;
|
||
|
|
||
|
var data = state as ICharacterStateData;
|
||
|
var size = CharacterActor.BodySize;
|
||
|
size.y = data!.BaseHeight;
|
||
|
CharacterActor.SetSize(size, CharacterActor.SizeReferenceType.Bottom);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//_characterActor.IsKinematic = true;
|
||
|
CharacterActor.ColliderComponent.enabled = false;
|
||
|
}
|
||
|
|
||
|
state?.OnStateEntry(CurrentState);
|
||
|
|
||
|
OnStateChanged?.Invoke(CurrentState, state);
|
||
|
CurrentState = state;
|
||
|
|
||
|
AllowMovement.SetElements(this,state is not null);
|
||
|
|
||
|
return state;
|
||
|
}
|
||
|
|
||
|
public ValidHandle AllowMovement { get; } = new();
|
||
|
public float Height =>CurrentStateData?.BaseHeight ?? 1f;
|
||
|
public float3 Center => (Vector3)Position + Vector3.up;
|
||
|
|
||
|
public float3 Position
|
||
|
{
|
||
|
get => CharacterActor.Position;
|
||
|
set => CharacterActor.Position = value;
|
||
|
}
|
||
|
|
||
|
public float3 Velocity
|
||
|
{
|
||
|
get => CharacterActor.Velocity;
|
||
|
set
|
||
|
{
|
||
|
if (value.y > 0)
|
||
|
{
|
||
|
CharacterActor.ForceNotGrounded();
|
||
|
}
|
||
|
|
||
|
CharacterActor.Velocity = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public float3 ViewPosition { get; set; }
|
||
|
|
||
|
public float3 AngularVelocity { get; private set; }
|
||
|
|
||
|
public quaternion Rotation
|
||
|
{
|
||
|
get => CharacterActor.Rotation;
|
||
|
set => CharacterActor.Rotation = value;
|
||
|
}
|
||
|
|
||
|
public quaternion ViewRotation
|
||
|
{
|
||
|
get=> Quaternion.Euler(InputView.y, InputView.x, 0);
|
||
|
set
|
||
|
{
|
||
|
var eulerAngles = ((Quaternion)value).eulerAngles;
|
||
|
InputView = new float2(eulerAngles.y,eulerAngles.x);
|
||
|
_newView = default;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public float3 SelfVelocity
|
||
|
{
|
||
|
get => CharacterActor.transform.InverseTransformDirection(Velocity);
|
||
|
set => Velocity = CharacterActor.transform.TransformDirection(value);
|
||
|
}
|
||
|
|
||
|
public bool IsGrounded => CharacterActor.IsGrounded;
|
||
|
public event Action<float3, float> OnLand;
|
||
|
private bool _isDisposed;
|
||
|
public void Dispose()
|
||
|
{
|
||
|
if(_isDisposed)return;
|
||
|
_isDisposed = true;
|
||
|
CharacterService.Dictionary.Remove(Entity.Id);
|
||
|
AfterTicker.Remove(OnAfterTick);
|
||
|
MainTicker.Remove(OnTick);
|
||
|
|
||
|
CurrentState?.OnStateExit(CurrentState,null);
|
||
|
|
||
|
HealthService.OnHealthChanged -= OnHealthChanged;
|
||
|
KnockedService.OnKnocked -= OnKnocked;
|
||
|
WorldInteractionService.OnInteraction -= OnInteraction;
|
||
|
}
|
||
|
|
||
|
private void OnInteraction(object arg1, IWorldInteractable arg2, WorldInteractionProcess arg3, object arg4)
|
||
|
{
|
||
|
if (arg3 is not WorldInteractionProcess.Performed) return;
|
||
|
|
||
|
if(arg2.WorldObject is not GameObject gameObject)return;
|
||
|
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ICharacterKnocked:
|
||
|
case ICharacterStateEmpty:
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (EntitiesService.Entities.TryGetValue(gameObject.GetInstanceID(),out var entity) is false || entity.ServiceProvider.GetService<UnitySeatNode>() is not
|
||
|
{ } seatNode) return;
|
||
|
|
||
|
var seatState = Entity.ServiceProvider.GetRequiredService<ICharacterSeating>();
|
||
|
seatState.SeatNode = seatNode;
|
||
|
seatState.SeatEntity = entity;
|
||
|
TransitionState<ICharacterSeating>();
|
||
|
}
|
||
|
|
||
|
public float3 MoveInput { get; private set; }
|
||
|
public event Func<float2, float2> OnInputViewFactor;
|
||
|
public ICharacterController CharacterController => this;
|
||
|
public IDictionary<int, quaternion> AdditiveCameraQuaternion { get; set; } = new Dictionary<int, quaternion>();
|
||
|
|
||
|
public IDictionary<int, float> ZoomFactor { get; set; } = new Dictionary<int, float>()
|
||
|
{
|
||
|
{ 0, 1 }
|
||
|
};
|
||
|
|
||
|
public ValidHandle AllowTpsCamera { get; } = new();
|
||
|
public ValidHandle AllowRun { get; } = new();
|
||
|
public ValidHandle AllowSprint { get; } = new();
|
||
|
public ValidHandle DisableModel { get; } = new();
|
||
|
public ValidHandle ForceRootMotion { get; } = new();
|
||
|
public ValidHandle DisableStateTransition { get; } = new();
|
||
|
public ValidHandle DisableCollision { get; } = new();
|
||
|
public ValidHandle AllowOverride { get; } = new();
|
||
|
|
||
|
public void CancelRun()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
public void CancelSprint()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public int Stamina
|
||
|
{
|
||
|
get => _stamina;
|
||
|
set
|
||
|
{
|
||
|
var current = _stamina;
|
||
|
if (value < current)
|
||
|
{
|
||
|
_refreshStamina.Reset();
|
||
|
}
|
||
|
_stamina = math.clamp(value, 0, 100);
|
||
|
OnStaminaChanged?.Invoke(current,_stamina);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int _stamina=100;
|
||
|
public event Action<int, int> OnStaminaChanged;
|
||
|
private readonly IntervalUpdate _refreshStamina = new(1f);
|
||
|
}
|
||
|
|
||
|
}
|