310 lines
7.5 KiB
C#
310 lines
7.5 KiB
C#
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<Animator>();
|
|
|
|
if (damageObstacle == null)
|
|
damageObstacle = GetComponentInChildren<DamageObstacle>();
|
|
|
|
if (damageColliders == null || damageColliders.Length == 0)
|
|
{
|
|
damageColliders = GetComponentsInChildren<Collider>(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);
|
|
}
|
|
} |