141 lines
3.6 KiB
C#

// ⓒ2025. STUDIOHUE. All rights reserved.
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class SmoothLookAt : MonoBehaviour
{
[Header("Target")]
public Transform target;
[Header("Smooth")]
[Min(0f)]
public float smoothSpeed = 5f;
[Header("Axis Lock")]
public bool rotateX = true;
public bool rotateY = true;
public bool rotateZ = true;
[Header("Options")]
public Vector3 worldUp = Vector3.up;
[Min(0.0001f)]
public float minDistance = 0.01f;
[Tooltip("에디트 모드에서 Scene 뷰도 계속 갱신")]
public bool updateInEditMode = true;
#if UNITY_EDITOR
private double _lastEditorTime;
#endif
private void OnEnable()
{
#if UNITY_EDITOR
_lastEditorTime = EditorApplication.timeSinceStartup;
EditorApplication.update -= EditorTick;
EditorApplication.update += EditorTick;
#endif
}
private void OnDisable()
{
#if UNITY_EDITOR
EditorApplication.update -= EditorTick;
#endif
}
private void OnValidate()
{
smoothSpeed = Mathf.Max(0f, smoothSpeed);
minDistance = Mathf.Max(0.0001f, minDistance);
if (!Application.isPlaying)
{
ApplyLookAtImmediate();
#if UNITY_EDITOR
SceneView.RepaintAll();
#endif
}
}
private void LateUpdate()
{
if (Application.isPlaying)
{
ApplyLookAt(Time.deltaTime);
}
}
#if UNITY_EDITOR
private void EditorTick()
{
if (Application.isPlaying) return;
if (!updateInEditMode) return;
if (this == null || !isActiveAndEnabled) return;
double now = EditorApplication.timeSinceStartup;
float deltaTime = Mathf.Max(0.0001f, (float)(now - _lastEditorTime));
_lastEditorTime = now;
ApplyLookAt(deltaTime);
if (!Application.isPlaying)
{
EditorApplication.QueuePlayerLoopUpdate();
SceneView.RepaintAll();
}
}
#endif
private void ApplyLookAt(float deltaTime)
{
if (target == null) return;
Vector3 direction = target.position - transform.position;
if (direction.sqrMagnitude < minDistance * minDistance) return;
Quaternion desiredRotation = Quaternion.LookRotation(direction.normalized, worldUp);
Vector3 currentEuler = transform.rotation.eulerAngles;
Vector3 desiredEuler = desiredRotation.eulerAngles;
if (!rotateX) desiredEuler.x = currentEuler.x;
if (!rotateY) desiredEuler.y = currentEuler.y;
if (!rotateZ) desiredEuler.z = currentEuler.z;
Quaternion limitedRotation = Quaternion.Euler(desiredEuler);
if (smoothSpeed <= 0f)
{
transform.rotation = limitedRotation;
return;
}
float t = 1f - Mathf.Exp(-smoothSpeed * deltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, limitedRotation, t);
}
private void ApplyLookAtImmediate()
{
if (target == null) return;
Vector3 direction = target.position - transform.position;
if (direction.sqrMagnitude < minDistance * minDistance) return;
Quaternion desiredRotation = Quaternion.LookRotation(direction.normalized, worldUp);
Vector3 currentEuler = transform.rotation.eulerAngles;
Vector3 desiredEuler = desiredRotation.eulerAngles;
if (!rotateX) desiredEuler.x = currentEuler.x;
if (!rotateY) desiredEuler.y = currentEuler.y;
if (!rotateZ) desiredEuler.z = currentEuler.z;
transform.rotation = Quaternion.Euler(desiredEuler);
}
}