using UnityEngine;
using FSA = UnityEngine.Serialization.FormerlySerializedAsAttribute;
namespace Lean.Common
{
/// This component will constrain the current Transform.rotation values so that its facing direction doesn't deviate too far from the target direction.
[DefaultExecutionOrder(200)]
[HelpURL(LeanHelper.PlusHelpUrlPrefix + "LeanConstrainToDirection")]
[AddComponentMenu(LeanHelper.ComponentPathPrefix + "Constrain To Direction")]
public class LeanConstrainToDirection : MonoBehaviour
{
/// This allows you to specify which local direction is considered forward on this GameObject.
/// Leave this as the default (0,0,1) if you're not sure.
public Vector3 Forward { set { forward = value; } get { return forward; } } [FSA("Forward")] [SerializeField] private Vector3 forward = Vector3.forward;
/// This allows you to specify the target direction you want to constrain to. For example, (0,1,0) is up.
public Vector3 Direction { set { direction = value; } get { return direction; } } [FSA("Direction")] [SerializeField] private Vector3 direction = Vector3.forward;
/// If you want to constrain the direction relative to a Transform, you can specify it here.
public Transform RelativeTo { set { relativeTo = value; } get { return relativeTo; } } [FSA("relativeTo")] [SerializeField] private Transform relativeTo;
/// This allows you to specify the minimum angle delta between the Forward and Direction vectors in degrees.
public float MinAngle { set { minAngle = value; } get { return minAngle; } } [FSA("MinAngle")] [SerializeField] [Range(0.0f, 180.0f)] public float minAngle = 0.0f;
/// This allows you to specify the maximum angle delta between the Forward and Direction vectors in degrees.
public float MaxAngle { set { maxAngle = value; } get { return maxAngle; } } [FSA("MaxAngle")] [SerializeField] [Range(0.0f, 180.0f)] public float maxAngle = 90.0f;
protected virtual void LateUpdate()
{
if (forward != Vector3.zero && direction != Vector3.zero)
{
var dir = direction;
if (relativeTo != null)
{
dir = relativeTo.TransformDirection(dir);
}
var fwd = transform.TransformDirection(forward);
var angle = Vector3.Angle(dir, fwd);
var oldRotation = transform.rotation;
var newRotation = oldRotation;
if (angle < minAngle)
{
var fixedFwd = Vector3.RotateTowards(fwd.normalized, -dir.normalized, (minAngle - angle) * Mathf.Deg2Rad, 1.0f);
newRotation = Quaternion.FromToRotation(fwd, fixedFwd) * oldRotation;
}
else if (angle > maxAngle)
{
var fixedFwd = Vector3.RotateTowards(fwd.normalized, dir.normalized, (angle - maxAngle) * Mathf.Deg2Rad, 1.0f);
newRotation = Quaternion.FromToRotation(fwd, fixedFwd) * oldRotation;
}
if (Mathf.Approximately(oldRotation.x, newRotation.x) == false ||
Mathf.Approximately(oldRotation.y, newRotation.y) == false ||
Mathf.Approximately(oldRotation.z, newRotation.z) == false ||
Mathf.Approximately(oldRotation.w, newRotation.w) == false)
{
transform.rotation = newRotation;
}
}
}
#if UNITY_EDITOR
protected virtual void OnDrawGizmosSelected()
{
if (forward != Vector3.zero)
{
var fwd = transform.TransformDirection(forward);
Gizmos.DrawLine(transform.position, fwd);
}
if (direction != Vector3.zero)
{
var dir = direction;
if (relativeTo != null)
{
dir = relativeTo.TransformDirection(dir);
}
Gizmos.color = new Color(1.0f, 1.0f, 1.0f, 0.2f);
DrawCone(dir, minAngle * Mathf.Deg2Rad);
DrawCone(dir, maxAngle * Mathf.Deg2Rad);
}
}
private void DrawCone(Vector3 dir, float angle)
{
for (var i = 0; i < 360; i++)
{
var a = (Mathf.PI * 2.0f / 360.0f) * i;
var x = Mathf.Sin(a);
var y = Mathf.Cos(a);
var d = Mathf.Cos(angle);
var r = Mathf.Sin(angle) * dir.magnitude;
Gizmos.DrawLine(transform.position, transform.position + dir * d + new Vector3(x * r, y * r, 0.0f));
}
}
#endif
}
}
#if UNITY_EDITOR
namespace Lean.Common.Editor
{
using TARGET = LeanConstrainToDirection;
[UnityEditor.CanEditMultipleObjects]
[UnityEditor.CustomEditor(typeof(TARGET))]
public class LeanConstrainToDirection_Editor : LeanEditor
{
protected override void OnInspector()
{
TARGET tgt; TARGET[] tgts; GetTargets(out tgt, out tgts);
Draw("forward", "This allows you to specify which local direction is considered forward on this GameObject.\n\nLeave this as the default (0,0,1) if you're not sure.");
Draw("direction", "This allows you to specify the target direction you want to constrain to. For example, (0,1,0) is up.");
Draw("relativeTo", "If you want to constrain the direction relative to a Transform, you can specify it here.");
Draw("minAngle", "This allows you to specify the minimum angle delta between the Forward and Direction vectors in degrees.");
Draw("maxAngle", "This allows you to specify the maximum angle delta between the Forward and Direction vectors in degrees.");
}
}
}
#endif