141 lines
3.6 KiB
C#
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);
|
|
}
|
|
} |