using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을 /// /// 플레이어 사운드 관리 /// 플레이어 오브젝트에 부착 — AudioSource 자동 추가 /// 애니메이션 이벤트 또는 코드에서 호출 /// [RequireComponent(typeof(AudioSource))] // 컴포넌트를 강제 추가할거에요 -> AudioSource를 public class PlayerSoundFX : MonoBehaviour // 클래스를 선언할거에요 -> 플레이어 사운드를 { [Header("--- 활 사운드 ---")] // 인스펙터 제목을 달거에요 -> 활 사운드를 [SerializeField] private AudioClip bow_draw; // 변수를 선언할거에요 -> 활 시위 당기기 소리를 [SerializeField] private AudioClip bow_charge_loop; // 변수를 선언할거에요 -> 차징 루프 소리를 [SerializeField] private AudioClip bow_release; // 변수를 선언할거에요 -> 화살 발사 소리를 [Header("--- 화살 명중 ---")] // 인스펙터 제목을 달거에요 -> 명중 사운드를 [SerializeField] private AudioClip arrow_hit_flesh; // 변수를 선언할거에요 -> 적 명중 소리를 [SerializeField] private AudioClip arrow_hit_wall; // 변수를 선언할거에요 -> 벽 명중 소리를 [Header("--- 이동 사운드 ---")] // 인스펙터 제목을 달거에요 -> 이동 사운드를 [SerializeField] private AudioClip[] player_footsteps; // 배열을 선언할거에요 -> 발걸음 소리들을 (2~3개 변형) [SerializeField] private AudioClip player_dash; // 변수를 선언할거에요 -> 대시 소리를 [Header("--- 피격/사망 ---")] // 인스펙터 제목을 달거에요 -> 피격 사운드를 [SerializeField] private AudioClip player_hit; // 변수를 선언할거에요 -> 피격 소리를 [SerializeField] private AudioClip player_death; // 변수를 선언할거에요 -> 사망 소리를 [Header("--- 볼륨 ---")] // 인스펙터 제목을 달거에요 -> 볼륨 설정을 [Range(0f, 1f)] [SerializeField] private float sfxVolume = 0.6f; // 변수를 선언할거에요 -> 효과음 볼륨을 [Range(0f, 1f)] [SerializeField] private float footstepVolume = 0.3f; // 변수를 선언할거에요 -> 발걸음 볼륨을 private AudioSource _audioSource; // 변수를 선언할거에요 -> 오디오 소스를 private AudioSource _loopSource; // 변수를 선언할거에요 -> 루프 전용 소스를 (차징 루프용) private void Awake() // 함수를 실행할거에요 -> 초기화를 { _audioSource = GetComponent(); // 가져올거에요 -> 기본 오디오 소스를 _audioSource.playOnAwake = false; // 설정할거에요 -> 자동 재생 끄기로 // 루프 전용 AudioSource 추가 (차징 소리가 원샷과 겹치지 않게) _loopSource = gameObject.AddComponent(); // 추가할거에요 -> 루프 전용 채널을 _loopSource.loop = true; // 설정할거에요 -> 루프로 _loopSource.playOnAwake = false; // 설정할거에요 -> 자동 재생 끄기로 _loopSource.volume = sfxVolume * 0.7f; // 설정할거에요 -> 약간 작은 볼륨으로 } // ============================================ // 활 사운드 — PlayerAttack에서 호출 // ============================================ /// 차징 시작 시 호출 (PlayerAttack.StartCharging) public void PlayBowDraw() // 함수를 선언할거에요 -> 활 당기기 소리를 { PlayOneShot(bow_draw); // 재생할거에요 -> 시위 당기기 소리를 // 차징 루프 시작 if (bow_charge_loop != null) // 조건이 맞으면 실행할거에요 -> 루프 클립이 있다면 { _loopSource.clip = bow_charge_loop; // 설정할거에요 -> 루프 클립을 _loopSource.Play(); // 재생할거에요 -> 루프를 } } /// 발사 시 호출 (PlayerAttack.OnShootArrow) public void PlayBowRelease() // 함수를 선언할거에요 -> 화살 발사 소리를 { StopChargeLoop(); // 실행할거에요 -> 차징 루프 정지를 PlayOneShot(bow_release); // 재생할거에요 -> 발사 소리를 } /// 차징 취소 시 호출 (PlayerAttack.CancelCharging) public void StopChargeLoop() // 함수를 선언할거에요 -> 차징 루프 정지를 { if (_loopSource.isPlaying) _loopSource.Stop(); // 정지할거에요 -> 루프를 } // ============================================ // 화살 명중 — Arrow.cs HandleHit에서 호출 // ============================================ /// 적 명중 시 public void PlayArrowHitFlesh(Vector3 position) // 함수를 선언할거에요 -> 적 명중 소리를 { if (arrow_hit_flesh != null) // 조건이 맞으면 실행할거에요 -> 클립이 있다면 AudioSource.PlayClipAtPoint(arrow_hit_flesh, position, sfxVolume); // 재생할거에요 -> 명중 위치에서 } /// 벽/바닥 명중 시 public void PlayArrowHitWall(Vector3 position) // 함수를 선언할거에요 -> 벽 명중 소리를 { if (arrow_hit_wall != null) // 조건이 맞으면 실행할거에요 -> 클립이 있다면 AudioSource.PlayClipAtPoint(arrow_hit_wall, position, sfxVolume); // 재생할거에요 -> 명중 위치에서 } // ============================================ // 발걸음 — 애니메이션 이벤트에서 호출 // ============================================ /// /// 걷기/달리기 애니메이션에 이벤트로 추가 /// 애니메이션 창 → 발이 닿는 프레임에 이벤트 추가 → 함수명: PlayFootstep /// public void PlayFootstep() // 함수를 선언할거에요 -> 발걸음 소리를 { if (player_footsteps == null || player_footsteps.Length == 0) return; // 조건이 맞으면 중단할거에요 -> 클립이 없으면 // 랜덤 변형으로 반복감 감소 int index = Random.Range(0, player_footsteps.Length); // 뽑을거에요 -> 랜덤 인덱스를 if (player_footsteps[index] != null) // 조건이 맞으면 실행할거에요 -> 클립이 있다면 { _audioSource.PlayOneShot(player_footsteps[index], footstepVolume); // 재생할거에요 -> 랜덤 발걸음을 } } // ============================================ // 대시 — PlayerMovement.DashRoutine에서 호출 // ============================================ public void PlayDash() // 함수를 선언할거에요 -> 대시 소리를 { PlayOneShot(player_dash); // 재생할거에요 -> 대시 소리를 } // ============================================ // 피격/사망 — PlayerHealth에서 호출 // ============================================ public void PlayPlayerHit() // 함수를 선언할거에요 -> 피격 소리를 { PlayOneShot(player_hit); // 재생할거에요 -> 피격 소리를 } public void PlayPlayerDeath() // 함수를 선언할거에요 -> 사망 소리를 { PlayOneShot(player_death); // 재생할거에요 -> 사망 소리를 } // ============================================ // 유틸리티 // ============================================ private void PlayOneShot(AudioClip clip) // 함수를 선언할거에요 -> 원샷 재생 헬퍼를 { if (clip != null && _audioSource != null) // 조건이 맞으면 실행할거에요 -> 둘 다 있다면 _audioSource.PlayOneShot(clip, sfxVolume); // 재생할거에요 -> 효과음을 } }