Files
Net.Like.Xue.Tokyo/Packages-Local/Com.Project.B.Unity/CharacterController/PlayerCharacterController.cs

1023 lines
27 KiB
C#
Raw Normal View History

2025-06-24 23:49:13 +08:00
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);
}
}