// ⓒ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); } }