using System.Collections; using UnityEngine; public class FallingStalactite : MonoBehaviour { [Header("References")] [SerializeField] private Rigidbody rb; [SerializeField] private Collider damageCollider; [SerializeField] private DamageObstacle damageObstacle; [Header("Fall Settings")] [SerializeField] private float fallDelay = 0.0f; [Tooltip("떨어질 때 아래 방향으로 추가 속도를 줍니다.")] [SerializeField] private float initialDownVelocity = 0f; [Tooltip("떨어질 때 약간 회전시키고 싶으면 값을 넣습니다.")] [SerializeField] private Vector3 initialAngularVelocity = new Vector3(0f, 0f, 0f); [Header("Damage")] [SerializeField] private int damage = 10; [Tooltip("떨어지기 전에는 데미지를 끄고, 떨어질 때 켭니다.")] [SerializeField] private bool damageOnlyWhileFalling = true; [Header("Sound")] [SerializeField] private AudioSource audioSource; [Tooltip("종유석이 떨어지기 시작할 때 재생할 소리입니다.")] [SerializeField] private AudioClip fallSound; [Range(0f, 1f)] [SerializeField] private float fallSoundVolume = 1f; [Tooltip("리셋 후 다시 떨어질 때도 소리를 재생할지 여부입니다.")] [SerializeField] private bool playSoundEveryFall = true; [Tooltip("Play through the shared 2D SFX pool when available so the fall cue is always audible.")] [SerializeField] private bool playThroughSoundManager = true; [Tooltip("Force the fallback AudioSource to 2D playback.")] [SerializeField] private bool playFallSoundAs2D = true; [Header("Reset Option")] [SerializeField] private bool resetAfterFall = true; [Tooltip("떨어진 뒤 몇 초 후 원래 위치로 돌아갈지 설정합니다.")] [SerializeField] private float resetDelay = 4.0f; [Tooltip("리셋할 때 종유석을 다시 숨기지 않고 원위치에 고정합니다.")] [SerializeField] private bool readyAgainAfterReset = true; [Header("Debug")] [SerializeField] private bool showDebugLog = true; private Vector3 startPosition; private Quaternion startRotation; private bool hasFallen; private bool hasPlayedSound; private Coroutine fallRoutine; public bool HasFallen => hasFallen; private void Awake() { ResolveReferences(); startPosition = transform.position; startRotation = transform.rotation; PrepareStalactite(); } private void ResolveReferences() { if (rb == null) rb = GetComponent(); if (damageCollider == null) damageCollider = GetComponent(); if (damageObstacle == null) damageObstacle = GetComponent(); if (audioSource == null) audioSource = GetComponent(); } private void PrepareStalactite() { if (rb != null) { rb.useGravity = false; rb.isKinematic = true; rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } if (damageObstacle != null) { damageObstacle.SetDamage(damage); damageObstacle.SetCanDamage(!damageOnlyWhileFalling); } if (damageCollider != null) { damageCollider.enabled = true; } if (audioSource != null) { audioSource.playOnAwake = false; audioSource.mute = false; if (playFallSoundAs2D) audioSource.spatialBlend = 0f; } PreloadFallSound(); hasFallen = false; if (playSoundEveryFall) hasPlayedSound = false; } public void TriggerFall() { if (hasFallen) return; if (fallRoutine != null) StopCoroutine(fallRoutine); fallRoutine = StartCoroutine(FallRoutine()); } private IEnumerator FallRoutine() { hasFallen = true; if (fallDelay > 0f) yield return new WaitForSeconds(fallDelay); PlayFallSound(); if (damageObstacle != null) { damageObstacle.SetDamage(damage); damageObstacle.SetCanDamage(true); } if (rb != null) { rb.isKinematic = false; rb.useGravity = true; if (initialDownVelocity > 0f) { rb.linearVelocity = Vector3.down * initialDownVelocity; } rb.angularVelocity = initialAngularVelocity; } if (showDebugLog) Debug.Log($"[FallingStalactite] {name} 낙하 시작. 데미지 {damage}", this); if (resetAfterFall) { yield return new WaitForSeconds(resetDelay); ResetStalactite(); } fallRoutine = null; } private AudioClip GetFallClip() { if (fallSound != null) return fallSound; if (audioSource != null) return audioSource.clip; return null; } private void PreloadFallSound() { AudioClip clipToLoad = GetFallClip(); if (clipToLoad != null && clipToLoad.loadState == AudioDataLoadState.Unloaded) clipToLoad.LoadAudioData(); } private void PlayFallSound() { if (hasPlayedSound && !playSoundEveryFall) return; AudioClip clipToPlay = GetFallClip(); if (clipToPlay == null) { if (showDebugLog) Debug.LogWarning($"[FallingStalactite] {name} Fall Sound가 연결되지 않았습니다.", this); return; } if (clipToPlay.loadState == AudioDataLoadState.Unloaded) clipToPlay.LoadAudioData(); if (playThroughSoundManager && SoundManager.Instance != null) { SoundManager.Instance.PlaySFX(clipToPlay, fallSoundVolume); } else if (audioSource != null) { audioSource.enabled = true; audioSource.mute = false; audioSource.playOnAwake = false; if (audioSource.volume <= 0f) audioSource.volume = 1f; if (playFallSoundAs2D) audioSource.spatialBlend = 0f; audioSource.PlayOneShot(clipToPlay, fallSoundVolume); } else { AudioSource.PlayClipAtPoint(clipToPlay, transform.position, fallSoundVolume); } hasPlayedSound = true; if (showDebugLog) Debug.Log($"[FallingStalactite] {name} 낙하 사운드 재생: {clipToPlay.name}", this); } public void ResetStalactite() { if (rb != null) { rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; rb.useGravity = false; rb.isKinematic = true; } transform.position = startPosition; transform.rotation = startRotation; if (damageObstacle != null) { damageObstacle.SetDamage(damage); damageObstacle.SetCanDamage(!damageOnlyWhileFalling); } if (readyAgainAfterReset) { hasFallen = false; } if (playSoundEveryFall) { hasPlayedSound = false; } if (showDebugLog) Debug.Log($"[FallingStalactite] {name} 원위치 리셋", this); } }