namespace Tilia.Trackers.PseudoBody
{
using System;
using Tilia.Interactions.Interactables.Interactors.Collection;
using UnityEngine;
using UnityEngine.Events;
using Zinnia.Data.Attribute;
using Zinnia.Data.Collection.List;
using Zinnia.Extension;
///
/// The public interface for the PseudoBody prefab.
///
public class PseudoBodyFacade : MonoBehaviour
{
#region Tracking Settings
[Header("Tracking Settings")]
[Tooltip("The object to follow.")]
[SerializeField]
private GameObject source;
///
/// The object to follow.
///
public GameObject Source
{
get
{
return source;
}
set
{
source = value;
if (this.IsMemberChangeAllowed())
{
OnAfterSourceChange();
}
}
}
[Tooltip("An optional offset for the Source to use.")]
[SerializeField]
private GameObject offset;
///
/// An optional offset for the to use.
///
public GameObject Offset
{
get
{
return offset;
}
set
{
offset = value;
if (this.IsMemberChangeAllowed())
{
OnAfterOffsetChange();
}
}
}
[Tooltip("A GameObject collection of any external controller that contains a Position Mutator that may change the location of the Character.\n\nThis external Position Mutator will be prevented from causing a divergence of the Character and the Source/Offset.")]
[SerializeField]
private GameObjectObservableList externalPositionMutators;
///
/// A collection of any external controller that contains a Position Mutator that may change the location of the Character. This external Position Mutator will be prevented from causing a divergence of the Character and the Source/Offset.
///
public GameObjectObservableList ExternalPositionMutators
{
get
{
return externalPositionMutators;
}
set
{
externalPositionMutators = value;
}
}
#endregion
#region Collision Settings
[Header("Collision Settings")]
[Tooltip("Whether to automatically solve body collisions and push the character controller and source back to a safe space outside of the colliding geometry.")]
[SerializeField]
private bool preventEnterGeometry;
///
/// Whether to automatically solve body collisions and push the character controller back and source to a safe space outside of the colliding geometry.
///
public bool PreventEnterGeometry
{
get
{
return preventEnterGeometry;
}
set
{
preventEnterGeometry = value;
if (this.IsMemberChangeAllowed())
{
OnAfterPreventEnterGeometryChange();
}
}
}
[Tooltip("The radius of the character controller and capsule collider.")]
[SerializeField]
private float characterRadius = 0.3f;
///
/// The radius of the character controller and capsule collider.
///
public float CharacterRadius
{
get
{
return characterRadius;
}
set
{
characterRadius = value;
if (this.IsMemberChangeAllowed())
{
OnAfterCharacterRadiusChange();
}
}
}
[Tooltip("The thickness of Source to be used when resolving body collisions.")]
[SerializeField]
private float sourceThickness = 0.3f;
///
/// The thickness of to be used when resolving body collisions.
///
public float SourceThickness
{
get
{
return sourceThickness;
}
set
{
sourceThickness = value;
}
}
[Tooltip("The distance the pseudo body has to be away from the Source to be considered diverged.")]
[SerializeField]
private Vector3 sourceDivergenceThreshold = new Vector3(0.001f, 2f, 0.001f);
///
/// The distance the pseudo body has to be away from the to be considered diverged.
///
public Vector3 SourceDivergenceThreshold
{
get
{
return sourceDivergenceThreshold;
}
set
{
sourceDivergenceThreshold = value;
}
}
#endregion
#region Interaction Settings
[Header("Interaction Settings")]
[Tooltip("A GameObject collection to exclude from physics collision checks.")]
[SerializeField]
private GameObjectObservableList ignoredGameObjects;
///
/// A collection to exclude from physics collision checks.
///
public GameObjectObservableList IgnoredGameObjects
{
get
{
return ignoredGameObjects;
}
set
{
ignoredGameObjects = value;
}
}
#endregion
#region Body Events
///
/// Emitted when the pseudo body starts no longer being within the threshold distance of the .
///
[Header("Body Events")]
public UnityEvent Diverged = new UnityEvent();
///
/// Emitted when the pseudo body continues no longer being within the threshold distance of the for each subsequent frame.
///
public UnityEvent StillDiverged = new UnityEvent();
///
/// Emitted when the pseudo body is back within the threshold distance of the after being diverged.
///
public UnityEvent Converged = new UnityEvent();
///
/// Emitted when the pseudo body will become no longer within the threshold distance of the if the updated position is applied.
///
public UnityEvent WillDiverge = new UnityEvent();
///
/// Emitted when the body starts touching ground.
///
public UnityEvent BecameGrounded = new UnityEvent();
///
/// Emitted when the body stops touching ground.
///
public UnityEvent BecameAirborne = new UnityEvent();
///
/// Emitted when a jump is initiated.
///
public UnityEvent Jumped = new UnityEvent();
#endregion
#region Reference Settings
[Header("Reference Settings")]
[Tooltip("The linked Internal Setup.")]
[SerializeField]
[Restricted]
private PseudoBodyProcessor processor;
///
/// The linked Internal Setup.
///
public PseudoBodyProcessor Processor
{
get
{
return processor;
}
set
{
processor = value;
}
}
[Tooltip("A collection of Interactors to exclude from physics collision checks.")]
[SerializeField]
[Restricted]
[Obsolete("Use `IgnoredGameObjects` instead.")]
private InteractorFacadeObservableList ignoredInteractors;
///
/// A collection of Interactors to exclude from physics collision checks.
///
[Obsolete("Use `IgnoredGameObjects` instead.")]
public InteractorFacadeObservableList IgnoredInteractors
{
#pragma warning disable 0618
get
{
return ignoredInteractors;
}
set
{
ignoredInteractors = value;
}
#pragma warning restore 0618
}
#endregion
///
/// The object that defines the main source of truth for movement.
///
public virtual PseudoBodyProcessor.MovementInterest Interest
{
get
{
return Processor.Interest;
}
set
{
Processor.Interest = value;
}
}
///
/// Whether the body touches ground.
///
public virtual bool IsCharacterControllerGrounded => Processor.IsCharacterControllerGrounded;
///
/// The that acts as the physical representation of the body.
///
public virtual Rigidbody PhysicsBody => Processor.PhysicsBody;
///
/// Whether the has diverged from the .
///
public virtual bool IsDiverged => Processor.IsDiverged;
///
/// Clears .
///
public virtual void ClearSource()
{
if (!this.IsValidState())
{
return;
}
Source = default;
}
///
/// Clears .
///
public virtual void ClearOffset()
{
if (!this.IsValidState())
{
return;
}
Offset = default;
}
///
/// Sets the x value.
///
/// The value to set to.
public virtual void SetSourceDivergenceThresholdX(float value)
{
SourceDivergenceThreshold = new Vector3(value, SourceDivergenceThreshold.y, SourceDivergenceThreshold.z);
}
///
/// Sets the y value.
///
/// The value to set to.
public virtual void SetSourceDivergenceThresholdY(float value)
{
SourceDivergenceThreshold = new Vector3(SourceDivergenceThreshold.x, value, SourceDivergenceThreshold.z);
}
///
/// Sets the z value.
///
/// The value to set to.
public virtual void SetSourceDivergenceThresholdZ(float value)
{
SourceDivergenceThreshold = new Vector3(SourceDivergenceThreshold.x, SourceDivergenceThreshold.y, value);
}
///
/// Sets the source of truth for movement to come from until hits the ground, then is the new source of truth.
///
///
/// This method needs to be called right before or right after applying any form of movement to the while the body is grounded, i.e. in the same frame and before is called.
///
public virtual void ListenToRigidbodyMovement()
{
Interest = PseudoBodyProcessor.MovementInterest.RigidbodyUntilGrounded;
}
///
/// 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()
{
Processor.SolveBodyCollisions();
}
///
/// Snaps the to the position.
///
public virtual void SnapToSource()
{
Processor.SnapToSource();
}
///
/// 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)
{
return Processor.CheckWillDiverge(targetPosition);
}
///
/// 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()
{
Processor.ResolveDivergence();
}
///
/// Adds force to the to simulate a jump at the given force.
///
/// The force to jump by.
public virtual void Jump(float force)
{
if (!Processor.IsGrounded)
{
return;
}
if(Processor.IsDiverged)
{
Debug.Log("wee");
}
Processor.AddForce(force);
Jumped?.Invoke();
}
protected virtual void Awake()
{
#pragma warning disable 0618
if (IgnoredInteractors.NonSubscribableElements.Count > 0)
{
Debug.LogWarning("`PsuedoBodyFacade.IgnoredInteractors` list has been deprecated. Use the `PsuedoBodyFacade.IgnoredGameObjects` list instead.", gameObject);
}
#pragma warning restore 0618
OnAfterPreventEnterGeometryChange();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterSourceChange()
{
Processor.ConfigureSourceObjectFollower();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterOffsetChange()
{
Processor.ConfigureOffsetObjectFollower();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterPreventEnterGeometryChange()
{
if (PreventEnterGeometry)
{
Diverged.AddListener(SolveBodyCollisions);
StillDiverged.AddListener(SolveBodyCollisions);
}
else
{
Diverged.RemoveListener(SolveBodyCollisions);
StillDiverged.RemoveListener(SolveBodyCollisions);
}
}
///
/// Called after has been changed.
///
protected virtual void OnAfterCharacterRadiusChange()
{
Processor.ConfigureOffsetObjectFollower();
}
}
}