Files
WhaleAdventure_VR/Assets/02_Scripts/Cave/ClamOpenClose.cs
2026-06-16 09:46:59 +09:00

238 lines
6.5 KiB
C#

using System.Collections;
using UnityEngine;
public class ClamOpenClose : MonoBehaviour
{
[Header("Shell Hinges")]
[SerializeField] private Transform upShell;
[SerializeField] private Transform downShell;
[Header("Open Angle")]
[Tooltip("X축 기준으로 얼마나 열릴지 설정합니다. 음수값이 커질수록 더 많이 열립니다.")]
[SerializeField] private float upShellOpenX = -45f;
[Tooltip("아래 조개도 함께 움직일 경우 사용합니다. 필요 없으면 0으로 두세요.")]
[SerializeField] private float downShellOpenX = 0f;
[Header("Timing")]
[SerializeField] private float minClosedTime = 0.6f;
[SerializeField] private float maxClosedTime = 1.8f;
[SerializeField] private float minOpenTime = 0.8f;
[SerializeField] private float maxOpenTime = 2.0f;
[SerializeField] private float openDuration = 1.5f;
[SerializeField] private float closeDuration = 0.18f;
[Header("Motion")]
[SerializeField] private AnimationCurve openCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[SerializeField] private AnimationCurve closeCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
[Header("Snap Close Effect")]
[SerializeField] private bool useSnapClose = true;
[Tooltip("닫힐 때 살짝 더 닫히는 오버슈트 각도입니다.")]
[SerializeField] private float snapCloseOvershootX = 5f;
[Tooltip("쾅 하고 닫힌 뒤 원래 닫힌 위치로 돌아오는 시간입니다.")]
[SerializeField] private float snapCloseOvershootDuration = 0.06f;
[Header("Start Option")]
[SerializeField] private bool startAutomatically = true;
[SerializeField] private bool startOpened = false;
private Quaternion upClosedRot;
private Quaternion downClosedRot;
private Quaternion upOpenedRot;
private Quaternion downOpenedRot;
private Coroutine routine;
private bool isOpen;
public bool IsOpen => isOpen;
private void Awake()
{
if (upShell == null)
{
Transform found = transform.Find("Scale/UpShell");
if (found != null) upShell = found;
}
if (downShell == null)
{
Transform found = transform.Find("Scale/DownShell");
if (found != null) downShell = found;
}
if (upShell == null)
{
Debug.LogWarning("[ClamOpenClose] UpShell이 연결되지 않았습니다.", this);
return;
}
// 현재 Scene에서 맞춰둔 로컬 회전을 닫힌 상태로 저장
upClosedRot = upShell.localRotation;
if (downShell != null)
downClosedRot = downShell.localRotation;
// 닫힌 상태 기준으로 X축 회전 추가
upOpenedRot = upClosedRot * Quaternion.Euler(upShellOpenX, 0f, 0f);
if (downShell != null)
downOpenedRot = downClosedRot * Quaternion.Euler(downShellOpenX, 0f, 0f);
}
private void Start()
{
if (startOpened)
SetOpenedImmediately();
else
SetClosedImmediately();
if (startAutomatically)
StartClam();
}
public void StartClam()
{
if (routine != null)
StopCoroutine(routine);
routine = StartCoroutine(OpenCloseRoutine());
}
public void StopClam()
{
if (routine != null)
{
StopCoroutine(routine);
routine = null;
}
}
private IEnumerator OpenCloseRoutine()
{
while (true)
{
float closedWait = Random.Range(minClosedTime, maxClosedTime);
yield return new WaitForSeconds(closedWait);
// 천천히 열기
yield return MoveShells(
upClosedRot,
upOpenedRot,
downClosedRot,
downOpenedRot,
openDuration,
openCurve
);
isOpen = true;
float openWait = Random.Range(minOpenTime, maxOpenTime);
yield return new WaitForSeconds(openWait);
// 빠르게 닫기
yield return MoveShells(
upOpenedRot,
upClosedRot,
downOpenedRot,
downClosedRot,
closeDuration,
closeCurve
);
isOpen = false;
// 쾅 닫히는 느낌
if (useSnapClose)
{
yield return SnapCloseEffect();
}
}
}
private IEnumerator MoveShells(
Quaternion upFrom,
Quaternion upTo,
Quaternion downFrom,
Quaternion downTo,
float duration,
AnimationCurve curve)
{
float timer = 0f;
while (timer < duration)
{
timer += Time.deltaTime;
float t = Mathf.Clamp01(timer / duration);
float curvedT = curve.Evaluate(t);
if (upShell != null)
upShell.localRotation = Quaternion.Slerp(upFrom, upTo, curvedT);
if (downShell != null)
downShell.localRotation = Quaternion.Slerp(downFrom, downTo, curvedT);
yield return null;
}
if (upShell != null)
upShell.localRotation = upTo;
if (downShell != null)
downShell.localRotation = downTo;
}
private IEnumerator SnapCloseEffect()
{
Quaternion upSnapRot = upClosedRot * Quaternion.Euler(snapCloseOvershootX, 0f, 0f);
Quaternion downSnapRot = downClosedRot;
if (downShell != null)
downSnapRot = downClosedRot * Quaternion.Euler(-snapCloseOvershootX * 0.3f, 0f, 0f);
// 살짝 더 닫힘
if (upShell != null)
upShell.localRotation = upSnapRot;
if (downShell != null)
downShell.localRotation = downSnapRot;
yield return new WaitForSeconds(snapCloseOvershootDuration);
// 원래 닫힌 상태로 복귀
if (upShell != null)
upShell.localRotation = upClosedRot;
if (downShell != null)
downShell.localRotation = downClosedRot;
}
private void SetClosedImmediately()
{
if (upShell != null)
upShell.localRotation = upClosedRot;
if (downShell != null)
downShell.localRotation = downClosedRot;
isOpen = false;
}
private void SetOpenedImmediately()
{
if (upShell != null)
upShell.localRotation = upOpenedRot;
if (downShell != null)
downShell.localRotation = downOpenedRot;
isOpen = true;
}
}