Projext/Assets/02_Scripts/Enemy/BossAI/NorcielBoss.SFX.cs
hydrozen e989d20668 카툰 쉐이더 추가 + 중복 스크립트 수정 + 전체 업데이트
- ToonPostProcess.shader: 횃불 고딕 스타일 후처리 쉐이더 (Built-in RP)
- ToonCameraEffect.cs: 카메라 자동 부착 후처리 스크립트
- 중복 UI 스크립트 제거 (MenuIntroController, ToggleCustom)
- 씬, 프리팹, 애니메이션 등 전체 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:31:16 +09:00

256 lines
20 KiB
C#

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; // 변수를 선언할거에요 -> 피격 파티클을
// ══════════════════════════════════════════════════════════
// 헬퍼 — 사운드 재생
// ══════════════════════════════════════════════════════════
/// <summary>보스 오브젝트의 AudioSource로 효과음 재생 (3D 위치음)</summary>
private void PlayBossSFX(AudioClip clip, float volume = 1f) // 함수를 선언할거에요 -> 보스 위치에서 효과음을 재생하는
{
if (clip == null || audioSource == null) return; // 중단할거에요 -> 클립 또는 AudioSource 없으면
audioSource.PlayOneShot(clip, volume); // 재생할거에요 -> 효과음을 (3D 위치 기반)
}
/// <summary>특정 월드 좌표에서 효과음 재생 (임팩트처럼 위치가 다를 때 사용)</summary>
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 기본 방식으로
}
// ══════════════════════════════════════════════════════════
// 헬퍼 — 파티클 재생 / 정지
// ══════════════════════════════════════════════════════════
/// <summary>ParticleSystem을 현재 배치된 위치에서 재생 (자식 파티클 포함)</summary>
private void PlayBossVFX(ParticleSystem vfx) // 함수를 선언할거에요 -> 파티클을 현재 위치에서 재생하는
{
if (vfx == null) return; // 중단할거에요 -> 없으면
vfx.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); // 초기화할거에요 -> 이전 잔상을 제거하고
vfx.Play(true); // 재생할거에요 -> 자식 파티클 포함해서
}
/// <summary>ParticleSystem을 지정 월드 좌표로 이동한 뒤 재생 (임팩트 위치 이동)</summary>
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); // 재생할거에요 -> 자식 파티클 포함해서
}
/// <summary>ParticleSystem 발생 중단 (루프 트레일 종료에 사용)</summary>
private void StopBossVFX(ParticleSystem vfx) // 함수를 선언할거에요 -> 파티클을 정지하는 (기존 파티클은 자연 소멸)
{
if (vfx == null) return; // 중단할거에요 -> 없으면
vfx.Stop(true, ParticleSystemStopBehavior.StopEmitting); // 정지할거에요 -> 새 파티클 발생을 (기존 것은 자연 소멸)
}
// ══════════════════════════════════════════════════════════
// Animation Event 수신
// ══════════════════════════════════════════════════════════
/// <summary>
/// Animation Event — 걷기/달리기 클립의 발이 땅에 닿는 프레임에서 호출
///
/// [등록 방법]
/// Unity Animation 창에서 Monster_Walk / Monster_Run 클립을 열고
/// 발이 땅에 닿는 프레임에 Function: "OnFootstep" 이벤트를 추가하세요.
/// </summary>
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음)
}
}