using System.Collections; using UnityEngine; public class RhinoObstacle : MonoBehaviour { [Header("References")] [SerializeField] private Transform rhinoRoot; [SerializeField] private Animator animator; [SerializeField] private DamageObstacle damageObstacle; [Tooltip("수면 위에 있을 때만 켤 코뿔소 충돌 콜라이더들입니다. RhinoHitBox의 Collider를 넣으세요.")] [SerializeField] private Collider[] damageColliders; [Header("Position")] [SerializeField] private Transform underwaterPoint; [SerializeField] private Transform surfacePoint; [Header("Timing")] [SerializeField] private float minHiddenTime = 2.0f; [SerializeField] private float maxHiddenTime = 5.0f; [SerializeField] private float riseDuration = 0.8f; [SerializeField] private float surfaceIdleTime = 0.4f; [SerializeField] private float attackStayTime = 1.2f; [SerializeField] private float diveDuration = 0.7f; [Header("Animation")] [SerializeField] private string idleStateName = "Idle"; [SerializeField] private string hitTriggerName = "Hit"; [SerializeField] private float idleCrossFadeDuration = 0.1f; [Header("Options")] [SerializeField] private bool startAutomatically = true; [SerializeField] private bool loop = true; [SerializeField] private bool showDebugLog = true; private Coroutine routine; private bool isRunning; private bool isSurfaced; public bool IsSurfaced => isSurfaced; private void Awake() { ResolveReferences(); if (animator != null) { animator.applyRootMotion = false; } SetDamageActive(false); } private void Start() { MoveImmediatelyToUnderwater(); ForceIdleAnimation(); if (startAutomatically) { StartRhino(); } } private void ResolveReferences() { if (rhinoRoot == null) rhinoRoot = transform; if (animator == null) animator = GetComponentInChildren(); if (damageObstacle == null) damageObstacle = GetComponentInChildren(); if (damageColliders == null || damageColliders.Length == 0) { damageColliders = GetComponentsInChildren(true); } } public void StartRhino() { if (routine != null) { StopCoroutine(routine); } isRunning = true; routine = StartCoroutine(RhinoRoutine()); Log("시작"); } public void StopRhino() { isRunning = false; if (routine != null) { StopCoroutine(routine); routine = null; } SetDamageActive(false); ForceIdleAnimation(); MoveImmediatelyToUnderwater(); Log("정지"); } private IEnumerator RhinoRoutine() { while (isRunning) { // 1. 물속 대기 isSurfaced = false; SetDamageActive(false); ForceIdleAnimation(); float hiddenWait = Random.Range(minHiddenTime, maxHiddenTime); Log($"물속 대기 {hiddenWait:0.0}초"); yield return new WaitForSeconds(hiddenWait); if (!isRunning) break; // 2. 수면 위로 떠오름 Log("떠오름 시작"); yield return MoveToSurface(); if (!isRunning) break; // 3. 수면 위에 도착한 순간부터 충돌 가능 isSurfaced = true; SetDamageActive(true); ForceIdleAnimation(); Log("수면 위 도착 / 데미지 콜라이더 ON"); yield return new WaitForSeconds(surfaceIdleTime); if (!isRunning) break; // 4. 공격 실행 Log("공격 시작"); PlayHitAnimation(); yield return new WaitForSeconds(attackStayTime); // 5. 공격 종료 후 Idle 복귀 Log("공격 종료 / Idle 복귀"); ForceIdleAnimation(); if (!isRunning) break; // 6. 잠수 시작 전 충돌 끄기 isSurfaced = false; SetDamageActive(false); Log("잠수 시작 / 데미지 콜라이더 OFF"); yield return MoveToUnderwater(); if (!loop) break; } routine = null; } private IEnumerator MoveToSurface() { if (rhinoRoot == null || surfacePoint == null) yield break; Vector3 startPos = rhinoRoot.position; Vector3 endPos = surfacePoint.position; float timer = 0f; float duration = Mathf.Max(0.01f, riseDuration); while (timer < duration) { timer += Time.deltaTime; float t = Mathf.Clamp01(timer / duration); float smoothT = Smooth01(t); rhinoRoot.position = Vector3.Lerp(startPos, endPos, smoothT); yield return null; } rhinoRoot.position = endPos; } private IEnumerator MoveToUnderwater() { if (rhinoRoot == null || underwaterPoint == null) yield break; Vector3 startPos = rhinoRoot.position; Vector3 endPos = underwaterPoint.position; float timer = 0f; float duration = Mathf.Max(0.01f, diveDuration); while (timer < duration) { timer += Time.deltaTime; float t = Mathf.Clamp01(timer / duration); float smoothT = Smooth01(t); rhinoRoot.position = Vector3.Lerp(startPos, endPos, smoothT); yield return null; } rhinoRoot.position = endPos; isSurfaced = false; SetDamageActive(false); ForceIdleAnimation(); Log("잠수 완료"); } private void PlayHitAnimation() { if (animator == null) { LogWarning("Animator가 없습니다."); return; } if (string.IsNullOrEmpty(hitTriggerName)) return; animator.ResetTrigger(hitTriggerName); animator.SetTrigger(hitTriggerName); } private void ForceIdleAnimation() { if (animator == null) return; if (!string.IsNullOrEmpty(hitTriggerName)) { animator.ResetTrigger(hitTriggerName); } if (!string.IsNullOrEmpty(idleStateName)) { animator.CrossFade(idleStateName, idleCrossFadeDuration, 0, 0f); } } private void MoveImmediatelyToUnderwater() { if (rhinoRoot == null || underwaterPoint == null) return; rhinoRoot.position = underwaterPoint.position; isSurfaced = false; SetDamageActive(false); } private void SetDamageActive(bool active) { if (damageObstacle != null) { damageObstacle.SetCanDamage(active); } if (damageColliders == null) return; foreach (Collider col in damageColliders) { if (col == null) continue; col.enabled = active; } } private float Smooth01(float t) { return t * t * (3f - 2f * t); } private void Log(string message) { if (showDebugLog) Debug.Log($"[RhinoObstacle] {name} / {message}"); } private void LogWarning(string message) { if (showDebugLog) Debug.LogWarning($"[RhinoObstacle] {name} / {message}", this); } }