Projext/Assets/Scripts/Enemy/AI/Projectileimpactsound.cs
2026-02-27 09:44:52 +09:00

173 lines
11 KiB
C#

using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
// ============================================================
// ProjectileImpactSound
//
// 역할: 투사체(돌, 화염병 등)가 충돌했을 때
// 대상(플레이어 / 바닥 / 벽)에 따라 다른 소리 재생
//
// 사용법:
// 1. 투사체 프리팹에 이 컴포넌트 Add Component
// 2. Inspector에서 소리 슬롯 연결
// 3. 투사체의 OnCollisionEnter 또는 OnTriggerEnter에서
// HandleImpact(other.gameObject) 호출
// (또는 이 컴포넌트가 직접 감지)
//
// 충돌 대상 판별 순서:
// Player Tag → Enemy Tag → Ground Layer → Wall/Default
// ============================================================
[RequireComponent(typeof(AudioSource))] // 필수 컴포넌트를 지정할거에요 -> AudioSource를
public class ProjectileImpactSound : MonoBehaviour // 클래스를 선언할거에요 -> 투사체 충돌 사운드를
{
// ─────────────────────────────────────────────────────────
// 사운드 슬롯
// ─────────────────────────────────────────────────────────
[Header("=== 충돌 대상별 소리 ===")]
[Tooltip("플레이어에 맞았을 때 소리 (여러 개 = 랜덤)")]
[SerializeField] private AudioClip[] hitPlayerSounds; // 배열을 선언할거에요 -> 플레이어 타격음들을
[Tooltip("적 몬스터에 맞았을 때 소리 (여러 개 = 랜덤)")]
[SerializeField] private AudioClip[] hitEnemySounds; // 배열을 선언할거에요 -> 적 타격음들을
[Tooltip("바닥에 맞았을 때 소리 (여러 개 = 랜덤)")]
[SerializeField] private AudioClip[] hitGroundSounds; // 배열을 선언할거에요 -> 바닥 충돌음들을
[Tooltip("벽/장애물에 맞았을 때 소리 (여러 개 = 랜덤)")]
[SerializeField] private AudioClip[] hitWallSounds; // 배열을 선언할거에요 -> 벽 충돌음들을
[Tooltip("매칭 실패 시 기본 충돌음 (여러 개 = 랜덤)")]
[SerializeField] private AudioClip[] defaultImpactSounds; // 배열을 선언할거에요 -> 기본 충돌음들을
[Header("=== 충돌 감지 설정 ===")]
[Tooltip("바닥으로 판별할 LayerMask (Ground 레이어 등)")]
[SerializeField] private LayerMask groundLayer; // 변수를 선언할거에요 -> 바닥 레이어를
[Tooltip("벽으로 판별할 LayerMask (Wall 레이어 등)")]
[SerializeField] private LayerMask wallLayer; // 변수를 선언할거에요 -> 벽 레이어를
[Tooltip("충돌 감지 방식 선택\n" +
"Collision: OnCollisionEnter (Rigidbody 있는 투사체)\n" +
"Trigger: OnTriggerEnter (IsTrigger 켠 투사체)")]
[SerializeField] private ImpactDetectMode detectMode = ImpactDetectMode.Collision; // 변수를 선언할거에요 -> 감지 방식을
[Tooltip("충돌 후 오브젝트 자동 제거 여부")]
[SerializeField] private bool destroyOnImpact = false; // 변수를 선언할거에요 -> 충돌 후 제거 여부를 (투사체 자체 파괴는 투사체 스크립트에서)
// ─────────────────────────────────────────────────────────
// 감지 방식 열거형
// ─────────────────────────────────────────────────────────
public enum ImpactDetectMode // 열거형을 선언할거에요 -> 충돌 감지 방식을
{
Collision, // OnCollisionEnter 사용
Trigger, // OnTriggerEnter 사용
Manual, // 외부에서 HandleImpact() 직접 호출
}
// ─────────────────────────────────────────────────────────
// 내부 변수
// ─────────────────────────────────────────────────────────
private AudioSource _audioSource; // 변수를 선언할거에요 -> AudioSource를
private bool _hasImpacted = false; // 변수를 선언할거에요 -> 이미 충돌 처리됐는지를 (중복 방지)
// ─────────────────────────────────────────────────────────
// 초기화
// ─────────────────────────────────────────────────────────
private void Awake() // 함수를 실행할거에요 -> 초기화를
{
_audioSource = GetComponent<AudioSource>(); // 가져올거에요 -> AudioSource를
_audioSource.playOnAwake = false; // 끌거에요 -> 자동 재생을
}
private void OnEnable() // 함수를 실행할거에요 -> 오브젝트 활성화 시 (풀 재사용 대비)
{
_hasImpacted = false; // 초기화할거에요 -> 충돌 상태를
}
// ─────────────────────────────────────────────────────────
// 충돌 감지 — Collision 방식
// ─────────────────────────────────────────────────────────
private void OnCollisionEnter(Collision col) // 함수를 실행할거에요 -> 충돌 시
{
if (detectMode != ImpactDetectMode.Collision) return; // 중단할거에요 -> Collision 모드 아니면
HandleImpact(col.gameObject, col.contacts[0].point); // 처리할거에요 -> 충돌 대상과 충돌 지점으로
}
// ─────────────────────────────────────────────────────────
// 충돌 감지 — Trigger 방식
// ─────────────────────────────────────────────────────────
private void OnTriggerEnter(Collider other) // 함수를 실행할거에요 -> 트리거 진입 시
{
if (detectMode != ImpactDetectMode.Trigger) return; // 중단할거에요 -> Trigger 모드 아니면
HandleImpact(other.gameObject, other.ClosestPoint(transform.position)); // 처리할거에요 -> 충돌 대상과 가장 가까운 지점으로
}
// ─────────────────────────────────────────────────────────
// 충돌 처리 — 외부에서도 호출 가능 (Manual 모드)
// ─────────────────────────────────────────────────────────
public void HandleImpact(GameObject hitObject, Vector3 hitPoint) // 함수를 선언할거에요 -> 충돌 처리를 (외부 호출 가능)
{
if (_hasImpacted) return; // 중단할거에요 -> 이미 처리됐으면 (중복 방지)
_hasImpacted = true; // 설정할거에요 -> 처리됨으로
// 대상 판별 → 소리 선택
AudioClip[] clips = DetermineClips(hitObject); // 결정할거에요 -> 대상에 맞는 클립 배열을
PlayImpactSound(clips, hitPoint); // 재생할거에요 -> 충돌음을
}
// ─────────────────────────────────────────────────────────
// 충돌 대상 판별
// ─────────────────────────────────────────────────────────
private AudioClip[] DetermineClips(GameObject hitObject) // 함수를 선언할거에요 -> 충돌 대상에 맞는 클립 배열을 반환하는 DetermineClips를
{
// 1순위: 플레이어
if (hitObject.CompareTag("Player")) // 조건이 맞으면 실행할거에요 -> 플레이어이면
return hitPlayerSounds.Length > 0 ? hitPlayerSounds : defaultImpactSounds; // 반환할거에요 -> 플레이어 타격음을
// 2순위: 적 몬스터
if (hitObject.CompareTag("Enemy")) // 조건이 맞으면 실행할거에요 -> 적이면
return hitEnemySounds.Length > 0 ? hitEnemySounds : defaultImpactSounds; // 반환할거에요 -> 적 타격음을
// 3순위: 바닥 레이어
if (IsInLayerMask(hitObject.layer, groundLayer)) // 조건이 맞으면 실행할거에요 -> 바닥 레이어이면
return hitGroundSounds.Length > 0 ? hitGroundSounds : defaultImpactSounds; // 반환할거에요 -> 바닥 충돌음을
// 4순위: 벽 레이어
if (IsInLayerMask(hitObject.layer, wallLayer)) // 조건이 맞으면 실행할거에요 -> 벽 레이어이면
return hitWallSounds.Length > 0 ? hitWallSounds : defaultImpactSounds; // 반환할거에요 -> 벽 충돌음을
// 기본: defaultImpactSounds
return defaultImpactSounds; // 반환할거에요 -> 기본 충돌음을
}
private bool IsInLayerMask(int layer, LayerMask mask) // 함수를 선언할거에요 -> 레이어가 마스크에 포함됐는지 확인하는 IsInLayerMask를
{
return mask == (mask | (1 << layer)); // 반환할거에요 -> 비트 연산으로 포함 여부를
}
// ─────────────────────────────────────────────────────────
// 충돌음 재생
// ─────────────────────────────────────────────────────────
private void PlayImpactSound(AudioClip[] clips, Vector3 position) // 함수를 선언할거에요 -> 충돌음 재생을
{
if (clips == null || clips.Length == 0) return; // 중단할거에요 -> 클립 없으면
int idx = UnityEngine.Random.Range(0, clips.Length); // 뽑을거에요 -> 랜덤 인덱스를
if (clips[idx] == null) return; // 중단할거에요 -> 클립 null이면
// AudioSource.PlayClipAtPoint: 오브젝트가 사라져도 소리는 끝까지 재생됨
// 3D 위치에서 재생 → 거리에 따라 자연스럽게 들림
AudioSource.PlayClipAtPoint(clips[idx], position); // 재생할거에요 -> 충돌 위치에서 소리를
}
}