using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을 // ============================================================ // NorcielBoss.SFX.cs — 사운드·이펙트 유틸리티 (partial class) // // [역할] // ① 패턴별 AudioClip SerializeField 선언 (Inspector에서 클립 연결) // ② ParticleSystem SerializeField 선언 (Inspector에서 파티클 연결) // ③ PlayBossSFX / PlayBossVFX 헬퍼 메서드 (null 안전) // ④ OnFootstep — Unity Animation Event 수신 (발소리 랜덤 재생) // // [Inspector 연결 방법] // 각 슬롯에 AudioClip / ParticleSystem 을 드래그해서 연결하세요. // 연결하지 않으면 null 체크로 오류 없이 무시됩니다. // // [ParticleSystem 배치 팁] // - 보스 오브젝트의 자식으로 ParticleSystem 을 만들어두세요. // - 트레일(vfx_Dash_Trail 등)은 보스 발 위치 자식에 배치하면 자연스러워요. // - 임팩트(vfx_Smash_Impact 등)는 위치가 코드로 이동되므로 아무 자식이나 연결해도 됩니다. // ============================================================ public partial class NorcielBoss : MonsterClass // 부분 클래스를 선언할거에요 -> NorcielBoss의 사운드·이펙트 부분으로 { // ══════════════════════════════════════════════════════════ // 사운드 — 상태 / 발소리 // ══════════════════════════════════════════════════════════ [Header("=== 보스 사운드 — 상태 ===")] [Tooltip("Phase 전환 포효 사운드 (Roar 애니와 함께 재생)")] [SerializeField] private AudioClip sfx_Roar; // 변수를 선언할거에요 -> 포효 사운드 클립을 [Tooltip("포효 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Roar_Vol = 1f; // 변수를 선언할거에요 -> 포효 볼륨을 [Tooltip("숨돌리기(BossRest) 진입 시 숨 고르는 사운드")] [SerializeField] private AudioClip sfx_Rest; // 변수를 선언할거에요 -> 숨돌리기 사운드 클립을 [Tooltip("숨돌리기 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Rest_Vol = 0.6f; // 변수를 선언할거에요 -> 숨돌리기 볼륨을 [Tooltip("걷기/달리기 발소리 (여러 개 등록 시 랜덤 재생)\n" + "Monster_Walk / Monster_Run 클립에 Animation Event 'OnFootstep' 을 등록해주세요.")] [SerializeField] private AudioClip[] sfx_Footsteps; // 변수를 선언할거에요 -> 발소리 클립 배열을 (랜덤 선택) [Tooltip("발소리 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Footstep_Vol = 0.5f; // 변수를 선언할거에요 -> 발소리 볼륨을 // ══════════════════════════════════════════════════════════ // 사운드 — 피격 / 사망 // ══════════════════════════════════════════════════════════ [Header("=== 보스 사운드 — 피격 / 사망 ===")] [Tooltip("피격 사운드\n보스는 슈퍼아머라 MonsterClass의 hitSound가 재생 안 됩니다.\n여기에 등록하면 TakeDamage에서 별도 재생됩니다.")] [SerializeField] private AudioClip sfx_Hit; // 변수를 선언할거에요 -> 피격 사운드 클립을 [Tooltip("피격 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Hit_Vol = 0.8f; // 변수를 선언할거에요 -> 피격 볼륨을 [Tooltip("사망 사운드 (기존 MonsterClass.deathSound와 별개로 추가 재생)")] [SerializeField] private AudioClip sfx_Death; // 변수를 선언할거에요 -> 사망 사운드 클립을 [Tooltip("사망 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Death_Vol = 1f; // 변수를 선언할거에요 -> 사망 볼륨을 // ══════════════════════════════════════════════════════════ // 사운드 — 던지기 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 사운드 — 던지기 패턴 ===")] [Tooltip("쇠공 들어올리는 예비 모션 사운드")] [SerializeField] private AudioClip sfx_Throw_Windup; // 변수를 선언할거에요 -> 던지기 예비 사운드 클립을 [Tooltip("던지기 예비 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Throw_Windup_Vol = 0.8f; // 변수를 선언할거에요 -> 던지기 예비 볼륨을 [Tooltip("쇠공 발사 순간 사운드")] [SerializeField] private AudioClip sfx_Throw_Release; // 변수를 선언할거에요 -> 발사 사운드 클립을 [Tooltip("발사 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Throw_Release_Vol = 1f; // 변수를 선언할거에요 -> 발사 볼륨을 // ══════════════════════════════════════════════════════════ // 사운드 — 내려찍기 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 사운드 — 내려찍기 패턴 ===")] [Tooltip("내려찍기 팔 드는 예비 모션 사운드")] [SerializeField] private AudioClip sfx_Smash_Windup; // 변수를 선언할거에요 -> 내려찍기 예비 사운드 클립을 [Tooltip("내려찍기 예비 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Smash_Windup_Vol = 0.7f; // 변수를 선언할거에요 -> 내려찍기 예비 볼륨을 [Tooltip("내려찍기 임팩트 사운드 (쾅! 소리)")] [SerializeField] private AudioClip sfx_Smash_Impact; // 변수를 선언할거에요 -> 내려찍기 임팩트 사운드 클립을 [Tooltip("내려찍기 임팩트 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Smash_Impact_Vol = 1f; // 변수를 선언할거에요 -> 내려찍기 임팩트 볼륨을 [Tooltip("Phase2 충격파 사운드")] [SerializeField] private AudioClip sfx_Smash_Shockwave; // 변수를 선언할거에요 -> 충격파 사운드 클립을 [Tooltip("충격파 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Smash_Shockwave_Vol = 0.9f; // 변수를 선언할거에요 -> 충격파 볼륨을 // ══════════════════════════════════════════════════════════ // 사운드 — 휩쓸기 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 사운드 — 휩쓸기 패턴 ===")] [Tooltip("팔 휩쓸기 스윙 사운드 (휘익! 소리)")] [SerializeField] private AudioClip sfx_Sweep_Swing; // 변수를 선언할거에요 -> 휩쓸기 스윙 사운드 클립을 [Tooltip("휩쓸기 스윙 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Sweep_Swing_Vol = 0.8f; // 변수를 선언할거에요 -> 휩쓸기 스윙 볼륨을 [Tooltip("휩쓸기 히트 사운드 (충격음)")] [SerializeField] private AudioClip sfx_Sweep_Impact; // 변수를 선언할거에요 -> 휩쓸기 히트 사운드 클립을 [Tooltip("휩쓸기 히트 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Sweep_Impact_Vol = 1f; // 변수를 선언할거에요 -> 휩쓸기 히트 볼륨을 // ══════════════════════════════════════════════════════════ // 사운드 — 대시 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 사운드 — 대시 패턴 ===")] [Tooltip("대시 준비 사운드 (으르렁/숨들이쉬기 등)")] [SerializeField] private AudioClip sfx_Dash_Windup; // 변수를 선언할거에요 -> 대시 준비 사운드 클립을 [Tooltip("대시 준비 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Dash_Windup_Vol = 0.8f; // 변수를 선언할거에요 -> 대시 준비 볼륨을 [Tooltip("대시 돌진 중 이동 사운드 (발구르기/바람소리 등)")] [SerializeField] private AudioClip sfx_Dash_Move; // 변수를 선언할거에요 -> 돌진 이동 사운드 클립을 [Tooltip("돌진 이동 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Dash_Move_Vol = 0.7f; // 변수를 선언할거에요 -> 돌진 이동 볼륨을 [Tooltip("대시 플레이어 충돌 임팩트 사운드")] [SerializeField] private AudioClip sfx_Dash_Impact; // 변수를 선언할거에요 -> 대시 충돌 사운드 클립을 [Tooltip("대시 충돌 볼륨 (0~1)")] [SerializeField][Range(0f, 1f)] private float sfx_Dash_Impact_Vol = 1f; // 변수를 선언할거에요 -> 대시 충돌 볼륨을 // ══════════════════════════════════════════════════════════ // 이펙트 — 포효 // ══════════════════════════════════════════════════════════ [Header("=== 보스 이펙트 — 포효 ===")] [Tooltip("Phase 전환 포효 파티클 (보스 주변 폭발/빛 파티클 등)\n보스 중심 위치에서 재생됩니다.")] [SerializeField] private ParticleSystem vfx_Roar; // 변수를 선언할거에요 -> 포효 파티클을 // ══════════════════════════════════════════════════════════ // 이펙트 — 던지기 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 이펙트 — 던지기 ===")] [Tooltip("쇠공 발사 순간 파티클 (발사 화염/바람 잔상 등)\n발사 시 ironBall 위치로 자동 이동됩니다.")] [SerializeField] private ParticleSystem vfx_Throw_Release; // 변수를 선언할거에요 -> 발사 이펙트 파티클을 // ══════════════════════════════════════════════════════════ // 이펙트 — 내려찍기 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 이펙트 — 내려찍기 ===")] [Tooltip("내려찍기 임팩트 파티클 (땅 충격파/흙먼지 등)\n판정 중심 위치(보스 앞)로 자동 이동됩니다.")] [SerializeField] private ParticleSystem vfx_Smash_Impact; // 변수를 선언할거에요 -> 내려찍기 임팩트 파티클을 [Tooltip("Phase2 충격파 파티클 (원형 파동 등)\n보스 중심 위치에서 재생됩니다.")] [SerializeField] private ParticleSystem vfx_Smash_Shockwave; // 변수를 선언할거에요 -> 충격파 파티클을 // ══════════════════════════════════════════════════════════ // 이펙트 — 휩쓸기 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 이펙트 — 휩쓸기 ===")] [Tooltip("휩쓸기 팔 트레일 파티클 (바람/불꽃 잔상 등)\n임팩트 타이밍에 재생됩니다.")] [SerializeField] private ParticleSystem vfx_Sweep_Trail; // 변수를 선언할거에요 -> 휩쓸기 트레일 파티클을 // ══════════════════════════════════════════════════════════ // 이펙트 — 대시 패턴 // ══════════════════════════════════════════════════════════ [Header("=== 보스 이펙트 — 대시 ===")] [Tooltip("대시 이동 중 트레일 파티클 (먼지/발자국 등)\n돌진 시작 시 Play → 도착·종료 시 Stop됩니다.\n루프 파티클로 설정해주세요.")] [SerializeField] private ParticleSystem vfx_Dash_Trail; // 변수를 선언할거에요 -> 돌진 트레일 파티클을 (루프) [Tooltip("대시 충돌 임팩트 파티클 (폭발/충격파 등)\n플레이어 위치로 자동 이동됩니다.")] [SerializeField] private ParticleSystem vfx_Dash_Impact; // 변수를 선언할거에요 -> 대시 충돌 이펙트 파티클을 // ══════════════════════════════════════════════════════════ // 이펙트 — 피격 // ══════════════════════════════════════════════════════════ [Header("=== 보스 이펙트 — 피격 ===")] [Tooltip("피격 파티클 (피 튀김/불꽃 등)\n기존 visualFX.Flash()와 함께 재생됩니다.\n비워두면 Flash만 재생됩니다.")] [SerializeField] private ParticleSystem vfx_Hit; // 변수를 선언할거에요 -> 피격 파티클을 // ══════════════════════════════════════════════════════════ // 헬퍼 — 사운드 재생 // ══════════════════════════════════════════════════════════ /// 보스 오브젝트의 AudioSource로 효과음 재생 (3D 위치음) private void PlayBossSFX(AudioClip clip, float volume = 1f) // 함수를 선언할거에요 -> 보스 위치에서 효과음을 재생하는 { if (clip == null || audioSource == null) return; // 중단할거에요 -> 클립 또는 AudioSource 없으면 audioSource.PlayOneShot(clip, volume); // 재생할거에요 -> 효과음을 (3D 위치 기반) } /// 특정 월드 좌표에서 효과음 재생 (임팩트처럼 위치가 다를 때 사용) private void PlayBossSFXAt(AudioClip clip, Vector3 worldPos, float volume = 1f) // 함수를 선언할거에요 -> 지정 위치에서 효과음을 재생하는 { if (clip == null) return; // 중단할거에요 -> 클립 없으면 if (SoundManager.Instance != null) // 조건이 맞으면 실행할거에요 -> SoundManager 있으면 SoundManager.Instance.PlaySFXAtPosition(clip, worldPos, volume); // 재생할거에요 -> 해당 위치에서 (3D 공간음) else AudioSource.PlayClipAtPoint(clip, worldPos, volume); // 폴백할거에요 -> Unity 기본 방식으로 } // ══════════════════════════════════════════════════════════ // 헬퍼 — 파티클 재생 / 정지 // ══════════════════════════════════════════════════════════ /// ParticleSystem을 현재 배치된 위치에서 재생 (자식 파티클 포함) private void PlayBossVFX(ParticleSystem vfx) // 함수를 선언할거에요 -> 파티클을 현재 위치에서 재생하는 { if (vfx == null) return; // 중단할거에요 -> 없으면 vfx.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); // 초기화할거에요 -> 이전 잔상을 제거하고 vfx.Play(true); // 재생할거에요 -> 자식 파티클 포함해서 } /// ParticleSystem을 지정 월드 좌표로 이동한 뒤 재생 (임팩트 위치 이동) private void PlayBossVFXAt(ParticleSystem vfx, Vector3 worldPos) // 함수를 선언할거에요 -> 파티클을 지정 위치에서 재생하는 { if (vfx == null) return; // 중단할거에요 -> 없으면 // [C-1 수정] 자식 오브젝트는 부모 transform이 매 프레임 월드 위치를 덮어써서 // vfx.transform.position = worldPos 가 한 프레임 뒤 부모 기준으로 리셋됨. // → SetParent(null) 로 부모에서 분리한 뒤 이동 → 월드 좌표가 그대로 유지됨 vfx.transform.SetParent(null); // 분리할거에요 -> 부모에서 (월드 좌표 이동이 덮어써지지 않게) vfx.transform.position = worldPos; // 이동할거에요 -> 지정 위치로 vfx.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); // 초기화할거에요 -> 이전 잔상을 제거하고 vfx.Play(true); // 재생할거에요 -> 자식 파티클 포함해서 } /// ParticleSystem 발생 중단 (루프 트레일 종료에 사용) private void StopBossVFX(ParticleSystem vfx) // 함수를 선언할거에요 -> 파티클을 정지하는 (기존 파티클은 자연 소멸) { if (vfx == null) return; // 중단할거에요 -> 없으면 vfx.Stop(true, ParticleSystemStopBehavior.StopEmitting); // 정지할거에요 -> 새 파티클 발생을 (기존 것은 자연 소멸) } // ══════════════════════════════════════════════════════════ // Animation Event 수신 // ══════════════════════════════════════════════════════════ /// /// Animation Event — 걷기/달리기 클립의 발이 땅에 닿는 프레임에서 호출 /// /// [등록 방법] /// Unity Animation 창에서 Monster_Walk / Monster_Run 클립을 열고 /// 발이 땅에 닿는 프레임에 Function: "OnFootstep" 이벤트를 추가하세요. /// public void OnFootstep() // 함수를 선언할거에요 -> 발소리 이벤트를 수신하는 (Unity Animation Event) { if (sfx_Footsteps == null || sfx_Footsteps.Length == 0) return; // 중단할거에요 -> 발소리 클립 없으면 AudioClip step = sfx_Footsteps[Random.Range(0, sfx_Footsteps.Length)]; // 선택할거에요 -> 랜덤 발소리를 PlayBossSFX(step, sfx_Footstep_Vol); // 재생할거에요 -> 발소리를 (보스 위치에서 3D음) } }