139 lines
6.2 KiB
C#
139 lines
6.2 KiB
C#
|
|
#if CINEMACHINE_PHYSICS
|
||
|
|
|
||
|
|
using UnityEngine;
|
||
|
|
|
||
|
|
namespace Unity.Cinemachine
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// An add-on module for CinemachineCamera that post-processes
|
||
|
|
/// the final position of the camera. It will confine the camera's position
|
||
|
|
/// to the volume specified in the Bounding Volume field.
|
||
|
|
/// </summary>
|
||
|
|
[AddComponentMenu("Cinemachine/Procedural/Extensions/Cinemachine Confiner 3D")]
|
||
|
|
[SaveDuringPlay]
|
||
|
|
[ExecuteAlways]
|
||
|
|
[DisallowMultipleComponent]
|
||
|
|
[HelpURL(Documentation.BaseURL + "manual/CinemachineConfiner3D.html")]
|
||
|
|
public class CinemachineConfiner3D : CinemachineExtension
|
||
|
|
{
|
||
|
|
/// <summary>The volume within which the camera is to be contained.</summary>
|
||
|
|
[Tooltip("The volume within which the camera is to be contained")]
|
||
|
|
public Collider BoundingVolume;
|
||
|
|
|
||
|
|
/// <summary>Size of the slow-down zone at the edge of the bounding volume.</summary>
|
||
|
|
[Tooltip("Size of the slow-down zone at the edge of the bounding volume.")]
|
||
|
|
public float SlowingDistance = 0;
|
||
|
|
|
||
|
|
/// <summary>See whether the virtual camera has been moved by the confiner</summary>
|
||
|
|
/// <param name="vcam">The virtual camera in question. This might be different from the
|
||
|
|
/// virtual camera that owns the confiner, in the event that the camera has children</param>
|
||
|
|
/// <returns>True if the virtual camera has been repositioned</returns>
|
||
|
|
public bool CameraWasDisplaced(CinemachineVirtualCameraBase vcam) => GetCameraDisplacementDistance(vcam) > 0;
|
||
|
|
|
||
|
|
/// <summary>See how far virtual camera has been moved by the confiner</summary>
|
||
|
|
/// <param name="vcam">The virtual camera in question. This might be different from the
|
||
|
|
/// virtual camera that owns the confiner, in the event that the camera has children</param>
|
||
|
|
/// <returns>True if the virtual camera has been repositioned</returns>
|
||
|
|
public float GetCameraDisplacementDistance(CinemachineVirtualCameraBase vcam)
|
||
|
|
=> GetExtraState<VcamExtraState>(vcam).PreviousDisplacement.magnitude;
|
||
|
|
|
||
|
|
void Reset()
|
||
|
|
{
|
||
|
|
BoundingVolume = null;
|
||
|
|
SlowingDistance = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void OnValidate()
|
||
|
|
{
|
||
|
|
SlowingDistance = Mathf.Max(0, SlowingDistance);
|
||
|
|
}
|
||
|
|
|
||
|
|
class VcamExtraState : VcamExtraStateBase
|
||
|
|
{
|
||
|
|
public Vector3 PreviousDisplacement;
|
||
|
|
public Vector3 PreviousCameraPosition;
|
||
|
|
};
|
||
|
|
|
||
|
|
/// <summary>Check if the bounding volume is defined</summary>
|
||
|
|
public bool IsValid => BoundingVolume != null && BoundingVolume.enabled && BoundingVolume.gameObject.activeInHierarchy;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Report maximum damping time needed for this component.
|
||
|
|
/// </summary>
|
||
|
|
/// <returns>Highest damping setting in this component</returns>
|
||
|
|
public override float GetMaxDampTime() => SlowingDistance * 0.2f; // just an approximation - we don't know the time
|
||
|
|
|
||
|
|
/// <summary>This is called to notify the extension that a target got warped,
|
||
|
|
/// so that the extension can update its internal state to make the camera
|
||
|
|
/// also warp seamlessly. Base class implementation does nothing.</summary>
|
||
|
|
/// <param name="vcam">The camera to warp</param>
|
||
|
|
/// <param name="target">The object that was warped</param>
|
||
|
|
/// <param name="positionDelta">The amount the target's position changed</param>
|
||
|
|
public override void OnTargetObjectWarped(
|
||
|
|
CinemachineVirtualCameraBase vcam, Transform target, Vector3 positionDelta)
|
||
|
|
{
|
||
|
|
var extra = GetExtraState<VcamExtraState>(vcam);
|
||
|
|
if (extra.Vcam.Follow == target)
|
||
|
|
extra.PreviousCameraPosition += positionDelta;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Callback to do the camera confining
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="vcam">The virtual camera being processed</param>
|
||
|
|
/// <param name="stage">The current pipeline stage</param>
|
||
|
|
/// <param name="state">The current virtual camera state</param>
|
||
|
|
/// <param name="deltaTime">The current applicable deltaTime</param>
|
||
|
|
protected override void PostPipelineStageCallback(
|
||
|
|
CinemachineVirtualCameraBase vcam,
|
||
|
|
CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
|
||
|
|
{
|
||
|
|
if (stage == CinemachineCore.Stage.Body && IsValid)
|
||
|
|
{
|
||
|
|
var extra = GetExtraState<VcamExtraState>(vcam);
|
||
|
|
var camPos = state.GetCorrectedPosition();
|
||
|
|
|
||
|
|
// Snap the point inside the bounds
|
||
|
|
var newPos = ConfinePoint(camPos);
|
||
|
|
if (SlowingDistance > Epsilon && deltaTime >= 0 && vcam.PreviousStateIsValid)
|
||
|
|
{
|
||
|
|
// Reduce speed if moving towards the edge and close enough to it
|
||
|
|
var prevPos = extra.PreviousCameraPosition;
|
||
|
|
var dir = newPos - prevPos;
|
||
|
|
var speed = dir.magnitude;
|
||
|
|
if (speed > Epsilon)
|
||
|
|
{
|
||
|
|
var t = GetDistanceFromEdge(prevPos, dir / speed, SlowingDistance) / SlowingDistance;
|
||
|
|
|
||
|
|
// This formula is found to give a smooth slowing curve while ensuring
|
||
|
|
// that it comes to a full stop in a reasonable time
|
||
|
|
newPos = Vector3.Lerp(prevPos, newPos, t * t * t + 0.05f);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
var displacement = newPos - camPos;
|
||
|
|
state.PositionCorrection += displacement;
|
||
|
|
extra.PreviousCameraPosition = state.GetCorrectedPosition();
|
||
|
|
extra.PreviousDisplacement = displacement;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Vector3 ConfinePoint(Vector3 p)
|
||
|
|
{
|
||
|
|
var mesh = BoundingVolume as MeshCollider;
|
||
|
|
if (mesh != null && !mesh.convex)
|
||
|
|
return p;
|
||
|
|
return BoundingVolume.ClosestPoint(p);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Returns distance from edge in direction of motion, or max if distance is greater than max.
|
||
|
|
// dirUnit must be unit length.
|
||
|
|
float GetDistanceFromEdge(Vector3 p, Vector3 dirUnit, float max)
|
||
|
|
{
|
||
|
|
p += dirUnit * max;
|
||
|
|
return max - (ConfinePoint(p) - p).magnitude;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|