namespace Tilia.Trackers.PseudoBody
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Tilia.Interactions.Interactables.Interactables;
using Tilia.Interactions.Interactables.Interactors;
using UnityEngine;
using UnityEngine.Events;
using Zinnia.Cast;
using Zinnia.Data.Attribute;
using Zinnia.Data.Operation.Mutation;
using Zinnia.Data.Type;
using Zinnia.Extension;
using Zinnia.Process;
using Zinnia.Tracking.Collision;
using Zinnia.Tracking.Follow;
///
/// Sets up the PseudoBody prefab based on the provided user settings and implements the logic to represent a body.
///
public class PseudoBodyProcessor : MonoBehaviour, IProcessable
{
///
/// The object that defines the main source of truth for movement.
///
public enum MovementInterest
{
///
/// The source of truth for movement comes from .
///
CharacterController,
///
/// The source of truth for movement comes from until is in the air, then is the new source of truth.
///
CharacterControllerUntilAirborne,
///
/// The source of truth for movement comes from .
///
Rigidbody,
///
/// The source of truth for movement comes from until hits the ground, then is the new source of truth.
///
RigidbodyUntilGrounded
}
///
/// The divergence state of the pseudo body.
///
public enum DivergenceState
{
///
/// The pseudo body is not diverged.
///
NotDiverged,
///
/// the pseudo body has become diverged.
///
BecameDiverged,
///
/// the pseudo body is no longer diverged.
///
BecameConverged,
///
/// the pseudo body is still diverged.
///
StillDiverged
}
#region Facade Settings
[Header("Facade Settings")]
[Tooltip("The public interface facade.")]
[SerializeField]
[Restricted]
private PseudoBodyFacade facade;
///
/// The public interface facade.
///
public PseudoBodyFacade Facade
{
get
{
return facade;
}
set
{
facade = value;
}
}
#endregion
#region Movement Settings
[Header("Movement Settings")]
[Tooltip("Whether the processor should update the Facade.Source position.")]
[SerializeField]
private bool updateSourcePosition = true;
///
/// Whether the processor should update the position.
///
public bool UpdateSourcePosition
{
get
{
return updateSourcePosition;
}
set
{
updateSourcePosition = value;
}
}
[Tooltip("The duration to smooth damp the alias Source and Offset movement by.")]
[SerializeField]
private float aliasMovementDuration = 0f;
///
/// The duration to smooth damp the alias Source and Offset movement by.
///
public float AliasMovementDuration
{
get
{
return aliasMovementDuration;
}
set
{
aliasMovementDuration = value;
}
}
[Tooltip("The duration to smooth damp the collision resolution movement by.")]
[SerializeField]
private float collisionMovementDuration = 0f;
///
/// The duration to smooth damp the collision resolution movement by.
///
public float CollisionMovementDuration
{
get
{
return collisionMovementDuration;
}
set
{
collisionMovementDuration = value;
}
}
#endregion
#region Force Settings
[Header("Force Settings")]
[Tooltip("The direction to apply the force to on the `AddForce` method.")]
[SerializeField]
private Vector3 forceDirection = Vector3.up;
///
/// The direction to apply the force to on the `AddForce` method.
///
public Vector3 ForceDirection
{
get
{
return forceDirection;
}
set
{
forceDirection = value;
}
}
[Tooltip("The ForceMode to apply the force to on the `AddForce` method.")]
[SerializeField]
private ForceMode forceType;
///
/// The to apply the force to on the `AddForce` method.
///
public ForceMode ForceType
{
get
{
return forceType;
}
set
{
forceType = value;
}
}
#endregion
#region Reference Settings
[Header("Reference Settings")]
[Tooltip("The CharacterController that acts as the main representation of the body.")]
[SerializeField]
[Restricted]
private CharacterController character;
///
/// The that acts as the main representation of the body.
///
public CharacterController Character
{
get
{
return character;
}
set
{
character = value;
}
}
[Tooltip("The Rigidbody that acts as the physical representation of the body.")]
[SerializeField]
[Restricted]
private Rigidbody physicsBody;
///
/// The that acts as the physical representation of the body.
///
public Rigidbody PhysicsBody
{
get
{
return physicsBody;
}
set
{
physicsBody = value;
}
}
[Tooltip("The CapsuleCollider that acts as the physical collider representation of the body.")]
[SerializeField]
[Restricted]
private CapsuleCollider rigidbodyCollider;
///
/// The that acts as the physical collider representation of the body.
///
public CapsuleCollider RigidbodyCollider
{
get
{
return rigidbodyCollider;
}
set
{
rigidbodyCollider = value;
}
}
[Tooltip("A CollisionIgnorer to manage ignoring collisions with the PseudoBody colliders.")]
[SerializeField]
[Restricted]
private CollisionIgnorer collisionsToIgnore;
///
/// A to manage ignoring collisions with the PseudoBody colliders.
///
public CollisionIgnorer CollisionsToIgnore
{
get
{
return collisionsToIgnore;
}
set
{
collisionsToIgnore = value;
}
}
#endregion
///
/// The private backing field for .
///
private MovementInterest interest = MovementInterest.CharacterControllerUntilAirborne;
///
/// The object that defines the main source of truth for movement.
///
public MovementInterest Interest
{
get
{
return interest;
}
set
{
interest = value;
if (this.IsMemberChangeAllowed())
{
OnAfterInterestChange();
}
}
}
///
/// The current divergence state of the pseudo body.
///
public virtual DivergenceState CurrentDivergenceState => GetDivergenceState();
///
/// Whether touches ground.
///
public virtual bool IsCharacterControllerGrounded => wasCharacterControllerGrounded == true;
///
/// Whether the PseudoBody is grounded using a detailed ground check.
///
public virtual bool IsGrounded => CheckIfCharacterControllerIsGrounded();
///
/// Whether has diverged from the .
///
public virtual bool IsDiverged { get; protected set; }
///
/// Movement to apply to to resolve collisions.
///
protected static readonly Vector3 collisionResolutionMovement = Vector3.right * 0.001f;
///
/// The colliders to ignore body collisions with.
///
protected readonly HashSet ignoredColliders = new HashSet();
///
/// The colliders to restore after an ungrab.
///
protected readonly HashSet restoreColliders = new HashSet();
///
/// The center of the .
///
protected Vector3 CharacterCenter => Vector3.up * (Character.radius - Character.skinWidth - 0.001f);
///
/// The previous position of .
///
protected Vector3 previousRigidbodyPosition;
///
/// The previous position of the .
///
protected Vector3 previousOffsetPosition;
///
/// The previous position for the .
///
protected Vector3 previousCharacterControllerPosition;
///
/// Whether was grounded previously.
///
protected bool? wasCharacterControllerGrounded;
///
/// The frame count of the last time was set to or .
///
protected int rigidbodySetFrameCount;
///
/// Stores the routine for ignoring Interactor collisions.
///
protected Coroutine ignoreInteractorCollisions;
///
/// An optional follower of .
///
protected ObjectFollower offsetObjectFollower;
///
/// An optional follower of .
///
protected ObjectFollower sourceObjectFollower;
///
/// Whether was previously diverged from the .
///
protected bool wasDiverged;
///
/// The routine for checking to see if the is still diverged with the at the end of the frame.
///
protected Coroutine checkDivergedAtEndOfFrameRoutine;
///
/// The routine for resetting the to after a force is added.
///
protected Coroutine resetInterestAfterAddForceRoutine;
///
/// A that waits for the end of the fixed update process.
///
protected YieldInstruction waitForEndOfFixedUpdate = new WaitForFixedUpdate();
///
/// Whether to snap the dependents to the without any divergent checking.
///
protected bool doSnapToSource;
///
/// A reference to output any smooth damp velocity to.
///
protected Vector3 smoothDampVelocity;
///
/// A collection of actions to perform on each to prevent mutations from occurring.
///
protected Dictionary preventMutateActions = new Dictionary();
///
/// A collection of actions to perform on each to allow mutations to occur.
///
protected Dictionary> allowMutateActions = new Dictionary>();
///
/// Snaps the to the position.
///
public virtual void SnapToSource()
{
doSnapToSource = true;
}
///
/// Positions, sizes and controls all variables necessary to make a body representation follow the given .
///
public virtual void Process()
{
if (!this.IsValidState())
{
return;
}
if (doSnapToSource)
{
SnapDependentsToSource();
doSnapToSource = false;
return;
}
UpdateAliasForNonCharacterControllerInterest();
Vector3 characterControllerSourceMovement = UpdateAliasForRigidbodyControllerInterest(out Vector3 rigidbodyPhysicsMovement);
bool isGrounded = CheckIfCharacterControllerIsGrounded();
UpdateInterestType(isGrounded, rigidbodyPhysicsMovement);
UpdateAliasForVerticalMovement(isGrounded, characterControllerSourceMovement);
MatchRigidbodyAndColliderWithCharacterController();
CheckDivergence();
RememberCurrentPositions();
EmitIsGroundedChangedEvent(isGrounded);
}
///
/// Solves body collisions by not moving the body in case it can't go to its current position.
///
///
/// If body collisions should be prevented this method needs to be called right before or right after applying any form of movement to the body.
///
public virtual void SolveBodyCollisions()
{
if (!this.IsValidState() || Facade.Source == null)
{
return;
}
ProcessObjectFollowers();
Process();
UpdateAliasForCollision();
Process();
}
///
/// Checks to see if the given position will cause a divergence between the and the to the .
///
/// The new position to check for.
/// Whether a divergence will occur.
public virtual bool CheckWillDiverge(Vector3 targetPosition)
{
Vector3 difference = targetPosition - Facade.Offset.transform.localPosition;
Vector3 position = GetCharacterPosition(Facade.Source.transform.position + difference, out float _);
Vector3 movement = position - Character.transform.position;
Character.Move(movement);
if (WillDiverge(Facade.Source.transform.position + difference, Facade.SourceDivergenceThreshold))
{
Facade.WillDiverge?.Invoke();
return true;
}
return false;
}
///
/// Checks to see if the given position will cause a divergence between the and the to the .
///
/// The new position to check for.
public virtual void DoCheckWillDiverge(Vector3 targetPosition)
{
CheckWillDiverge(targetPosition);
}
///
/// Resolves any divergence between the position and the actual position of the and .
///
public virtual void ResolveDivergence()
{
UpdateAliasForDivergence();
ProcessObjectFollowers();
}
///
/// Resolves any divergence between the position and the actual position of the and .
///
/// The diverged position.
public virtual void ResolveDivergence(Vector3 divergedPosition)
{
ResolveDivergence();
}
///
/// Adds a force to the in the using the .
///
/// The amount of force to apply in the .
public virtual void AddForce(float power)
{
if (PhysicsBody == null)
{
return;
}
Interest = MovementInterest.Rigidbody;
PhysicsBody.AddForce(power * ForceDirection, ForceType);
StopResetInterestAfterForceRoutine();
resetInterestAfterAddForceRoutine = StartCoroutine(ResetInterestAfterForce());
}
///
/// Configures the source object follower based on the facade settings.
///
public virtual void ConfigureSourceObjectFollower()
{
if (Facade.Source != null)
{
sourceObjectFollower = Facade.Source.GetComponent();
}
}
///
/// Configures the offset object follower based on the facade settings.
///
public virtual void ConfigureOffsetObjectFollower()
{
if (Facade.Offset != null)
{
offsetObjectFollower = Facade.Offset.GetComponent();
}
}
///
/// Configures the character controller and capsule collider radius based on the facade settings.
///
public virtual void ConfigureCharacterRadius()
{
Character.radius = Facade.CharacterRadius;
RigidbodyCollider.radius = Facade.CharacterRadius;
}
///
/// Ignores all collisions between any found Interactor and this PsuedoBody.
///
/// The object to ignore.
public virtual void IgnoreInteractorsCollisions(GameObject ignoredObject)
{
InteractorFacade interactor = ignoredObject.GetComponent();
if (interactor != null)
{
interactor.Grabbed.AddListener(IgnoreInteractorGrabbedCollision);
interactor.Ungrabbed.AddListener(ResumeInteractorUngrabbedCollision);
}
}
///
/// Resumes all collisions between any found Interactor and this PsuedoBody.
///
/// The object being ignored.
public virtual void ResumeInteractorsCollisions(GameObject ignoredObject)
{
InteractorFacade interactor = ignoredObject.GetComponent();
if (interactor != null)
{
interactor.Grabbed.RemoveListener(IgnoreInteractorGrabbedCollision);
interactor.Ungrabbed.RemoveListener(ResumeInteractorUngrabbedCollision);
}
}
///
/// Adds a found found in the given .
///
/// The container to look for the Position Mutator in.
public virtual void AddPositionMutator(GameObject mutatorContainer)
{
if (!TryGetMutator(mutatorContainer, out TransformPositionMutator mutator))
{
return;
}
preventMutateActions[mutator] = () => mutator.AllowMutate = false;
allowMutateActions[mutator] = (_) => mutator.AllowMutate = true;
mutator.PreMutated.AddListener(DoCheckWillDiverge);
mutator.MutationSkipped.AddListener(ResolveDivergence);
mutator.MutationSkipped.AddListener(allowMutateActions[mutator]);
Facade.WillDiverge.AddListener(preventMutateActions[mutator]);
}
///
/// Removes a found found in the given .
///
/// The container to look for the Position Mutator in.
public virtual void RemovePositionMutator(GameObject mutatorContainer)
{
if (!TryGetMutator(mutatorContainer, out TransformPositionMutator mutator))
{
return;
}
mutator.PreMutated.RemoveListener(DoCheckWillDiverge);
mutator.MutationSkipped.RemoveListener(ResolveDivergence);
mutator.MutationSkipped.RemoveListener(allowMutateActions[mutator]);
Facade.WillDiverge.RemoveListener(preventMutateActions[mutator]);
preventMutateActions.Remove(mutator);
allowMutateActions.Remove(mutator);
}
///
/// Ignores all of the colliders on the Interactor collection.
///
[Obsolete("Add `InteractorFacade.gameObject` to `PseudoBodyProcessor.CollisionsToIgnore.Targets` instead.")]
public virtual void IgnoreInteractorsCollisions(InteractorFacade interactor)
{
CollisionsToIgnore.RunWhenActiveAndEnabled(() => CollisionsToIgnore.Targets.AddUnique(interactor.gameObject));
}
///
/// Resumes all of the colliders on the Interactor collection.
///
[Obsolete("Remove `InteractorFacade.gameObject` to `PseudoBodyProcessor.CollisionsToIgnore.Targets` instead.")]
public virtual void ResumeInteractorsCollisions(InteractorFacade interactor)
{
CollisionsToIgnore.RunWhenActiveAndEnabled(() => CollisionsToIgnore.Targets.Remove(interactor.gameObject));
}
protected virtual void Awake()
{
Physics.IgnoreCollision(Character, RigidbodyCollider, true);
}
protected virtual void OnEnable()
{
ConfigureSourceObjectFollower();
ConfigureOffsetObjectFollower();
ConfigureCharacterRadius();
Interest = MovementInterest.CharacterControllerUntilAirborne;
SnapDependentsToSource();
}
protected virtual void OnDisable()
{
StopCheckDivergenceAtEndOfFrameRoutine();
StopResetInterestAfterForceRoutine();
sourceObjectFollower = null;
offsetObjectFollower = null;
}
///
/// Attempts to get the component nested within the given .
///
/// The container to look for the component in.
/// The found mutator.
/// Whether a mutator has been found.
protected virtual bool TryGetMutator(GameObject mutatorContainer, out TransformPositionMutator mutator)
{
mutator = null;
if (mutatorContainer == null)
{
return false;
}
mutator = mutatorContainer.TryGetComponent(true);
return mutator != null;
}
///
/// Updates the alias targets for when the is not of type CharacterController.
///
protected virtual void UpdateAliasForNonCharacterControllerInterest()
{
if (Interest != MovementInterest.CharacterController && Facade.Offset != null)
{
Vector3 offsetPosition = Facade.Offset.transform.position;
offsetPosition.y = PhysicsBody.position.y - Character.skinWidth;
UpdateAliasPosition(offsetPosition, offsetPosition - previousOffsetPosition, false, true, false, AliasMovementDuration);
previousOffsetPosition = offsetPosition;
}
}
///
/// Updates the alias targets for when the is of a Rigidbody type.
///
/// The calculated movement position.
/// The current movement position.
protected virtual Vector3 UpdateAliasForRigidbodyControllerInterest(out Vector3 rigidbodyPhysicsMovement)
{
// Handle walking down stairs/slopes and physics affecting the RigidBody in general.
rigidbodyPhysicsMovement = PhysicsBody.position - previousRigidbodyPosition;
if (Interest == MovementInterest.Rigidbody || Interest == MovementInterest.RigidbodyUntilGrounded)
{
previousCharacterControllerPosition = Character.transform.position;
Character.Move(rigidbodyPhysicsMovement);
if (Facade.Offset != null)
{
Vector3 movement = Character.transform.position - previousCharacterControllerPosition;
UpdateAliasPosition(movement, movement, true, true, false, AliasMovementDuration);
}
}
// Position the CharacterController and handle moving the source relative to the offset.
Vector3 characterControllerPosition = Character.transform.position;
previousCharacterControllerPosition = characterControllerPosition;
MatchCharacterControllerWithSource(Facade.Source.transform.position, false);
return characterControllerPosition - previousCharacterControllerPosition;
}
///
/// Updates the type based on whether the controller is grounded or not.
///
/// Whether the controller is touching the ground.
/// The calculated movement position.
protected virtual void UpdateInterestType(bool isGrounded, Vector3 rigidbodyPhysicsMovement)
{
// Allow moving the RigidBody via physics.
if (Interest == MovementInterest.CharacterControllerUntilAirborne && !isGrounded)
{
Interest = MovementInterest.RigidbodyUntilGrounded;
}
else if (Interest == MovementInterest.RigidbodyUntilGrounded
&& isGrounded
&& rigidbodyPhysicsMovement.sqrMagnitude <= 1E-06F
&& rigidbodySetFrameCount > 0
&& rigidbodySetFrameCount + 1 < Time.frameCount)
{
Interest = MovementInterest.CharacterControllerUntilAirborne;
}
}
///
/// Updates the alias targets for any vertical movement.
///
/// Whether the controller is touching the ground.
/// The calculated movement position.
protected virtual void UpdateAliasForVerticalMovement(bool isGrounded, Vector3 characterControllerSourceMovement)
{
// Handle walking up stairs/slopes via the CharacterController.
if (isGrounded && Facade.Offset != null && characterControllerSourceMovement.y > 0f)
{
UpdateAliasPosition(Vector3.up * characterControllerSourceMovement.y, default, true, true, true, AliasMovementDuration);
}
}
///
/// Updates the position of the alias objects.
///
/// The new position for the .
/// The new position for the .
/// Whether to increment the position or set it to a new value.
/// Whether to increment the position or set it to a new value.
/// Whether to ignore setting the position.
/// The duration to dampen the movemnt of the position updates.
protected virtual void UpdateAliasPosition(Vector3 newOffsetPosition, Vector3 newSourcePosition, bool incrementOffset, bool incrementSource, bool ignoreSourcePosition, float dampDuration)
{
if (Facade.Offset != null)
{
Vector3 targetOffsetPosition = (incrementOffset ? Facade.Offset.transform.position : Vector3.zero) + newOffsetPosition;
Facade.Offset.transform.position = dampDuration > 0f ?
Vector3.SmoothDamp(Facade.Offset.transform.position, targetOffsetPosition, ref smoothDampVelocity, dampDuration) :
targetOffsetPosition;
}
if (Facade.Source != null && UpdateSourcePosition && !ignoreSourcePosition)
{
Vector3 targetSourcePosition = (incrementSource ? Facade.Source.transform.position : Vector3.zero) + newSourcePosition;
Facade.Source.transform.position = dampDuration > 0f ?
Vector3.SmoothDamp(Facade.Source.transform.position, targetSourcePosition, ref smoothDampVelocity, dampDuration) :
targetSourcePosition;
}
}
///
/// Updates the alias targets to resolve any collisions.
///
protected virtual void UpdateAliasForCollision()
{
Vector3 characterControllerPosition = Character.transform.position + Character.center;
Vector3 difference = Facade.Source.transform.position - characterControllerPosition;
difference.y = 0f;
float minimumDistanceToColliders = Character.radius - Facade.SourceThickness;
if (difference.magnitude < minimumDistanceToColliders && !IsDiverged)
{
return;
}
float newDistance = difference.magnitude - minimumDistanceToColliders;
Vector3 newPosition = difference.normalized * newDistance * -1f;
UpdateAliasPosition(newPosition, newPosition, true, true, false, CollisionMovementDuration);
}
///
/// Updates the alias targets to resolve any divergence.
///
protected virtual void UpdateAliasForDivergence()
{
Vector3 characterControllerPosition = Character.transform.position;
Vector3 difference = Facade.Source.transform.position - characterControllerPosition;
difference.y = 0f;
Vector3 newOffsetPosition = Facade.Offset.transform.position - difference - (Vector3.one * -Character.skinWidth);
newOffsetPosition.y = Facade.Offset.transform.position.y;
UpdateAliasPosition(-difference, -difference, true, true, false, CollisionMovementDuration);
}
///
/// Processes the object followers for the and .
///
protected virtual void ProcessObjectFollowers()
{
if (offsetObjectFollower != null)
{
offsetObjectFollower.Process();
}
if (sourceObjectFollower != null)
{
sourceObjectFollower.Process();
}
}
///
/// Snaps the and the to the .
///
protected virtual void SnapDependentsToSource()
{
MatchCharacterControllerWithSource(Facade.Source.transform.position, true);
MatchRigidbodyAndColliderWithCharacterController();
RememberCurrentPositions();
}
///
/// Ignores the Interactable grabbed by the Interactor.
///
/// The Interactable to ignore.
protected virtual void IgnoreInteractorGrabbedCollision(InteractableFacade interactable)
{
CollisionsToIgnore.RunWhenActiveAndEnabled(() => CollisionsToIgnore.Targets.AddUnique(interactable.gameObject));
}
///
/// Resumes the Interactable ungrabbed by the Interactor.
///
/// The Interactable to resume.
protected virtual void ResumeInteractorUngrabbedCollision(InteractableFacade interactable)
{
if (!Facade.IgnoredGameObjects.Contains(interactable.gameObject) &&
(
interactable.GrabbingInteractors.Count == 0 ||
!Facade.IgnoredGameObjects.NonSubscribableElements.Intersect(GetGameObjectListFromInteractorFacadeList(interactable.GrabbingInteractors)).Any())
)
{
CollisionsToIgnore.RunWhenActiveAndEnabled(() => CollisionsToIgnore.Targets.Remove(interactable.gameObject));
}
}
///
/// Converts the collection to a collection.
///
/// The list to convert.
/// The converted list.
protected virtual IReadOnlyList GetGameObjectListFromInteractorFacadeList(IReadOnlyList interactorList)
{
List gameObjectList = new List();
foreach (InteractorFacade interactor in interactorList)
{
gameObjectList.Add(interactor.gameObject);
}
return gameObjectList;
}
///
/// Gets the position of the .
///
/// The given position of the source.
/// The calculated height of the Character.
/// The world position of the Character.
protected virtual Vector3 GetCharacterPosition(Vector3 sourcePosition, out float height)
{
height = Facade.Offset == null ? sourcePosition.y : Facade.Offset.transform.InverseTransformPoint(sourcePosition).y * Facade.Offset.transform.lossyScale.y;
height -= Character.skinWidth;
// CharacterController enforces a minimum height of twice its radius, so let's match that here.
height = Mathf.Max(height, 2f * Character.radius);
Vector3 position = sourcePosition;
position.y -= height;
if (Facade.Offset != null)
{
// The offset defines the source's "floor".
position.y = Mathf.Max(position.y, Facade.Offset.transform.position.y + Character.skinWidth);
}
return position;
}
///
/// Changes the height and position of to match .
///
/// The position to update to.
/// Whether to set the position directly or tell to move to it.
protected virtual void MatchCharacterControllerWithSource(Vector3 targetPosition, bool setPositionDirectly)
{
Vector3 position = GetCharacterPosition(targetPosition, out float height);
if (setPositionDirectly)
{
Character.transform.position = position;
}
else
{
Vector3 movement = position - Character.transform.position;
// The CharacterController doesn't resolve any potential collisions in case we don't move it.
Character.Move(movement == Vector3.zero ? movement + collisionResolutionMovement : movement);
if (movement == Vector3.zero)
{
Character.Move(movement - collisionResolutionMovement);
}
}
Character.height = height;
Vector3 center = Character.center;
center.y = height / 2f;
Character.center = center;
}
///
/// Changes to match the collider settings of and moves to match .
///
protected virtual void MatchRigidbodyAndColliderWithCharacterController()
{
RigidbodyCollider.height = Character.height + Character.skinWidth;
Vector3 center = Character.center;
center.y = (Character.height - Character.skinWidth) / 2f;
RigidbodyCollider.center = center;
PhysicsBody.position = Character.transform.position;
}
///
/// Checks whether is grounded.
///
///
/// isn't accurate so this method does an additional check using .
///
/// Whether is grounded.
protected virtual bool CheckIfCharacterControllerIsGrounded()
{
return Character.isGrounded ? true : CheckForSurroundingCollisions(Character.transform.position + CharacterCenter, Character.radius);
}
///
/// Checks for any collisions in the surrounding area.
///
/// The center point to start the spherecast check from.
/// The radius to perform the spherecast check to.
/// Whether any collisions have occcured.
protected virtual bool CheckForSurroundingCollisions(Vector3 center, float radius)
{
HeapAllocationFreeReadOnlyList hitColliders = PhysicsCast.OverlapSphereAll(null, center, radius, 0);
foreach (Collider hitCollider in hitColliders)
{
if (hitCollider != Character
&& hitCollider != RigidbodyCollider
&& !ignoredColliders.Contains(hitCollider)
&& !Physics.GetIgnoreLayerCollision(hitCollider.gameObject.layer, Character.gameObject.layer)
&& !Physics.GetIgnoreLayerCollision(hitCollider.gameObject.layer, PhysicsBody.gameObject.layer))
{
return true;
}
}
return false;
}
///
/// Updates the previous position variables to remember the current state.
///
protected virtual void RememberCurrentPositions()
{
previousRigidbodyPosition = PhysicsBody.position;
}
///
/// Emits or .
///
/// The current state.
protected virtual void EmitIsGroundedChangedEvent(bool isCharacterControllerGrounded)
{
if (wasCharacterControllerGrounded == isCharacterControllerGrounded)
{
return;
}
wasCharacterControllerGrounded = isCharacterControllerGrounded;
if (isCharacterControllerGrounded)
{
Facade.BecameGrounded?.Invoke();
}
else
{
Facade.BecameAirborne?.Invoke();
}
}
///
/// Determines the divergence state of the pseudo body.
///
/// The divergence state.
protected virtual DivergenceState GetDivergenceState()
{
int isDivergedValue = IsDiverged ? 1 : 0;
int wasDivergedValue = wasDiverged ? 2 : 0;
switch (isDivergedValue + wasDivergedValue)
{
case 0:
return DivergenceState.NotDiverged;
case 1:
return DivergenceState.BecameDiverged;
case 2:
return DivergenceState.BecameConverged;
case 3:
return DivergenceState.StillDiverged;
}
return DivergenceState.NotDiverged;
}
///
/// Determines whether the given position will diverge from the position.
///
/// The position to check.
/// The threshold in which to consider a divergence has occurred.
/// Whether there was a divergence between the two positions.
protected virtual bool WillDiverge(Vector3 targetPosition, Vector3 divergenceThreshold)
{
return !targetPosition.WithinDistance(Character.transform.position + Character.center, divergenceThreshold);
}
///
/// Check to see if the has diverged or converged with the .
///
protected virtual void CheckDivergence()
{
wasDiverged = IsDiverged;
IsDiverged = WillDiverge(Facade.Source.transform.position, Facade.SourceDivergenceThreshold);
switch (GetDivergenceState())
{
case DivergenceState.BecameDiverged:
Facade.Diverged?.Invoke();
break;
case DivergenceState.StillDiverged:
StopCheckDivergenceAtEndOfFrameRoutine();
checkDivergedAtEndOfFrameRoutine = StartCoroutine(CheckDivergenceAtEndOfFrame());
break;
case DivergenceState.BecameConverged:
StopCheckDivergenceAtEndOfFrameRoutine();
Facade.Converged?.Invoke();
break;
}
}
///
/// Check to see if the is still diverged with the at the end of the frame.
///
/// An Enumerator to manage the running of the Coroutine.
protected virtual IEnumerator CheckDivergenceAtEndOfFrame()
{
yield return waitForEndOfFixedUpdate;
if (IsDiverged)
{
Facade.StillDiverged?.Invoke();
}
checkDivergedAtEndOfFrameRoutine = null;
}
///
/// Stops the divergence check coroutine from running.
///
protected virtual void StopCheckDivergenceAtEndOfFrameRoutine()
{
if (checkDivergedAtEndOfFrameRoutine != null)
{
StopCoroutine(checkDivergedAtEndOfFrameRoutine);
checkDivergedAtEndOfFrameRoutine = null;
}
}
///
/// Resets the back to after a force is applied to the .
///
/// An Enumerator to manage the running of the Coroutine.
protected IEnumerator ResetInterestAfterForce()
{
yield return new WaitForFixedUpdate();
Interest = MovementInterest.RigidbodyUntilGrounded;
}
///
/// Stops the reset interest after force coroutine from running.
///
protected virtual void StopResetInterestAfterForceRoutine()
{
if (resetInterestAfterAddForceRoutine != null)
{
StopCoroutine(resetInterestAfterAddForceRoutine);
resetInterestAfterAddForceRoutine = null;
}
}
///
/// Called after has been changed.
///
protected virtual void OnAfterInterestChange()
{
switch (Interest)
{
case MovementInterest.CharacterController:
case MovementInterest.CharacterControllerUntilAirborne:
PhysicsBody.isKinematic = true;
rigidbodySetFrameCount = 0;
break;
case MovementInterest.Rigidbody:
case MovementInterest.RigidbodyUntilGrounded:
PhysicsBody.isKinematic = false;
rigidbodySetFrameCount = Time.frameCount;
break;
default:
throw new ArgumentOutOfRangeException(nameof(Interest), Interest, null);
}
}
}
}