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); } } } }