362 lines
23 KiB
C#
362 lines
23 KiB
C#
|
|
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
|
|||
|
|
using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를
|
|||
|
|
using System; // 시스템 기능을 사용할거에요 -> System을
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// MonsterBehaviour v2
|
|||
|
|
//
|
|||
|
|
// 역할: 몬스터의 사운드 / 이펙트 / 넉백 / 스턴을
|
|||
|
|
// MonsterClass 코드를 건드리지 않고 Inspector에서 조합
|
|||
|
|
//
|
|||
|
|
// v2 추가 기능:
|
|||
|
|
// ① 바닥 재질(Tag/PhysicMaterial)별 발소리 자동 전환
|
|||
|
|
// ② 발소리 Pitch/Volume 랜덤 → 반복 이질감 제거
|
|||
|
|
// ③ 연속 동일 클립 방지 (마지막 재생 클립 기억)
|
|||
|
|
//
|
|||
|
|
// 투사체 충돌음은 ProjectileImpactSound 컴포넌트 사용
|
|||
|
|
// (투사체 프리팹에 별도 부착)
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────────
|
|||
|
|
// MonsterBehaviour 본체
|
|||
|
|
// SurfaceType, SurfaceFootstep → SurfaceFootstep.cs 참조
|
|||
|
|
// ─────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
public class MonsterBehaviour : MonoBehaviour // 클래스를 선언할거에요 -> MonsterBehaviour를
|
|||
|
|
{
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 전투 사운드
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
[Header("=== 전투 사운드 ===")]
|
|||
|
|
|
|||
|
|
[Tooltip("피격 시 소리 (여러 개 = 랜덤)")]
|
|||
|
|
[SerializeField] private AudioClip[] hitSounds; // 배열을 선언할거에요 -> 피격 소리들을
|
|||
|
|
|
|||
|
|
[Tooltip("사망 시 소리")]
|
|||
|
|
[SerializeField] private AudioClip deathSound; // 변수를 선언할거에요 -> 사망 소리를
|
|||
|
|
|
|||
|
|
[Tooltip("공격 시 소리 (애니메이션 이벤트 OnAttackSound)")]
|
|||
|
|
[SerializeField] private AudioClip[] attackSounds; // 배열을 선언할거에요 -> 공격 소리들을
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 발소리 — 기본
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
[Header("=== 발소리 — 기본 ===")]
|
|||
|
|
|
|||
|
|
[Tooltip("바닥 재질 매칭 실패 시 사용할 기본 발소리")]
|
|||
|
|
[SerializeField] private AudioClip[] defaultFootsteps; // 배열을 선언할거에요 -> 기본 발소리들을
|
|||
|
|
|
|||
|
|
[Tooltip("발소리 기본 볼륨")]
|
|||
|
|
[SerializeField][Range(0f, 1f)] private float footstepVolume = 0.5f; // 변수를 선언할거에요 -> 발소리 볼륨을
|
|||
|
|
|
|||
|
|
[Tooltip("Pitch 최솟값 (이 값~최댓값 랜덤 → 반복 이질감 제거)")]
|
|||
|
|
[SerializeField][Range(0.5f, 1.5f)] private float pitchMin = 0.9f; // 변수를 선언할거에요 -> 피치 최솟값을
|
|||
|
|
|
|||
|
|
[Tooltip("Pitch 최댓값")]
|
|||
|
|
[SerializeField][Range(0.5f, 1.5f)] private float pitchMax = 1.1f; // 변수를 선언할거에요 -> 피치 최댓값을
|
|||
|
|
|
|||
|
|
[Tooltip("Volume 랜덤 범위 (footstepVolume ± 이 값)")]
|
|||
|
|
[SerializeField][Range(0f, 0.3f)] private float volumeVariation = 0.1f; // 변수를 선언할거에요 -> 볼륨 변동 범위를
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 발소리 — 바닥 재질별
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
[Header("=== 발소리 — 바닥 재질별 ===")]
|
|||
|
|
|
|||
|
|
[Tooltip("바닥 감지 Raycast 거리")]
|
|||
|
|
[SerializeField] private float groundCheckDistance = 0.3f; // 변수를 선언할거에요 -> 바닥 감지 거리를
|
|||
|
|
|
|||
|
|
[Tooltip("바닥 감지 LayerMask (Ground 레이어 등)")]
|
|||
|
|
[SerializeField] private LayerMask groundLayerMask = ~0; // 변수를 선언할거에요 -> 감지 레이어를 (기본 전체)
|
|||
|
|
|
|||
|
|
[Tooltip("재질별 발소리 목록\n" +
|
|||
|
|
"바닥 오브젝트 Tag 또는 PhysicMaterial 이름으로 자동 매칭\n" +
|
|||
|
|
"예: Tag = 'Stone' → SurfaceType.Stone 클립 재생")]
|
|||
|
|
[SerializeField] private SurfaceFootstep[] surfaceFootsteps; // 배열을 선언할거에요 -> 재질별 발소리 묶음들을
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 이펙트
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
[Header("=== 이펙트 ===")]
|
|||
|
|
|
|||
|
|
[Tooltip("피격 시 파티클 (오브젝트에 붙은 ParticleSystem)")]
|
|||
|
|
[SerializeField] private ParticleSystem hitEffect; // 변수를 선언할거에요 -> 피격 파티클을
|
|||
|
|
|
|||
|
|
[Tooltip("피격 시 생성할 이펙트 프리팹 (여러 개 = 랜덤)")]
|
|||
|
|
[SerializeField] private GameObject[] hitEffectPrefabs; // 배열을 선언할거에요 -> 피격 이펙트 프리팹들을
|
|||
|
|
|
|||
|
|
[Tooltip("사망 시 생성할 이펙트 프리팹")]
|
|||
|
|
[SerializeField] private GameObject deathEffectPrefab; // 변수를 선언할거에요 -> 사망 이펙트 프리팹을
|
|||
|
|
|
|||
|
|
[Tooltip("공격/슬래쉬 이펙트 프리팹 (여러 개 = 랜덤)")]
|
|||
|
|
[SerializeField] private GameObject[] slashEffectPrefabs; // 배열을 선언할거에요 -> 슬래쉬 이펙트 프리팹들을
|
|||
|
|
|
|||
|
|
[Tooltip("이펙트 생성 위치 (칼 끝, 손 등) — 없으면 몬스터 위치")]
|
|||
|
|
[SerializeField] private Transform effectSpawnPoint; // 변수를 선언할거에요 -> 이펙트 생성 위치를
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 넉백
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
[Header("=== 넉백 ===")]
|
|||
|
|
|
|||
|
|
[Tooltip("넉백 사용 여부")]
|
|||
|
|
[SerializeField] private bool enableKnockback = true; // 변수를 선언할거에요 -> 넉백 사용 여부를
|
|||
|
|
|
|||
|
|
[Tooltip("넉백 거리")]
|
|||
|
|
[SerializeField] private float knockbackForce = 1.5f; // 변수를 선언할거에요 -> 넉백 힘을
|
|||
|
|
|
|||
|
|
[Tooltip("넉백 지속 시간 (초)")]
|
|||
|
|
[SerializeField] private float knockbackDuration = 0.15f; // 변수를 선언할거에요 -> 넉백 지속 시간을
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 스턴
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
[Header("=== 스턴 ===")]
|
|||
|
|
|
|||
|
|
[Tooltip("피격 시 스턴 사용 여부")]
|
|||
|
|
[SerializeField] private bool enableStunOnHit = false; // 변수를 선언할거에요 -> 피격 스턴 여부를
|
|||
|
|
|
|||
|
|
[Tooltip("스턴 지속 시간 (초)")]
|
|||
|
|
[SerializeField] private float stunDuration = 0.5f; // 변수를 선언할거에요 -> 스턴 시간을
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 내부 변수
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
private MonsterClass _monster; // 변수를 선언할거에요 -> 연결된 MonsterClass를
|
|||
|
|
private AudioSource _audioSource; // 변수를 선언할거에요 -> AudioSource를
|
|||
|
|
private Coroutine _stunCoroutine; // 변수를 선언할거에요 -> 스턴 코루틴 핸들을
|
|||
|
|
private AudioClip _lastFootstepClip; // 변수를 선언할거에요 -> 마지막 발소리 클립을 (연속 동일 방지)
|
|||
|
|
private float _originalPitch; // 변수를 선언할거에요 -> 원래 AudioSource Pitch를 (복원용)
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 초기화
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
private void Awake() // 함수를 실행할거에요 -> 초기화를
|
|||
|
|
{
|
|||
|
|
_monster = GetComponent<MonsterClass>(); // 가져올거에요 -> MonsterClass를
|
|||
|
|
_audioSource = GetComponent<AudioSource>(); // 가져올거에요 -> AudioSource를
|
|||
|
|
|
|||
|
|
if (_audioSource != null) // 조건이 맞으면 실행할거에요 -> AudioSource가 있으면
|
|||
|
|
_originalPitch = _audioSource.pitch; // 저장할거에요 -> 원래 Pitch를
|
|||
|
|
|
|||
|
|
if (_monster == null) // 조건이 맞으면 실행할거에요 -> MonsterClass가 없으면
|
|||
|
|
{
|
|||
|
|
Debug.LogError($"[MonsterBehaviour] {gameObject.name}에 MonsterClass가 없어요!"); // 오류를 출력할거에요
|
|||
|
|
return; // 중단할거에요
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_monster.OnHitEvent += HandleHit; // 등록할거에요 -> 피격 이벤트를
|
|||
|
|
_monster.OnDeadEvent += HandleDead; // 등록할거에요 -> 사망 이벤트를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnDestroy() // 함수를 실행할거에요 -> 파괴 시 이벤트 해제를
|
|||
|
|
{
|
|||
|
|
if (_monster == null) return; // 중단할거에요
|
|||
|
|
_monster.OnHitEvent -= HandleHit; // 해제할거에요 -> 피격 이벤트 구독을
|
|||
|
|
_monster.OnDeadEvent -= HandleDead; // 해제할거에요 -> 사망 이벤트 구독을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 이벤트 핸들러
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
private void HandleHit(Vector3 hitDirection) // 함수를 선언할거에요 -> 피격 처리를
|
|||
|
|
{
|
|||
|
|
PlayRandomSound(hitSounds); // 재생할거에요 -> 피격 소리를
|
|||
|
|
SpawnHitEffect(); // 생성할거에요 -> 피격 이펙트를
|
|||
|
|
if (hitEffect != null) hitEffect.Play(); // 재생할거에요 -> 피격 파티클을
|
|||
|
|
|
|||
|
|
if (enableKnockback) // 조건이 맞으면 실행할거에요 -> 넉백 켜져있으면
|
|||
|
|
_monster.ApplyKnockback(hitDirection, knockbackForce, knockbackDuration); // 적용할거에요 -> 넉백을
|
|||
|
|
|
|||
|
|
if (enableStunOnHit) // 조건이 맞으면 실행할거에요 -> 스턴 켜져있으면
|
|||
|
|
ApplyStun(stunDuration); // 적용할거에요 -> 스턴을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void HandleDead() // 함수를 선언할거에요 -> 사망 처리를
|
|||
|
|
{
|
|||
|
|
if (deathSound != null && _audioSource != null) // 조건이 맞으면 실행할거에요 -> 사망 소리 있으면
|
|||
|
|
_audioSource.PlayOneShot(deathSound); // 재생할거에요 -> 사망 소리를
|
|||
|
|
|
|||
|
|
SpawnDeathEffect(); // 생성할거에요 -> 사망 이펙트를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 애니메이션 이벤트
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
public void OnAttackSound() // 함수를 선언할거에요 -> 공격 소리 재생을 (애니메이션 이벤트)
|
|||
|
|
{
|
|||
|
|
PlayRandomSound(attackSounds); // 재생할거에요 -> 랜덤 공격 소리를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void OnFootstep() // 함수를 선언할거에요 -> 발소리 재생을 (애니메이션 이벤트)
|
|||
|
|
{
|
|||
|
|
if (_audioSource == null) return; // 중단할거에요 -> AudioSource 없으면
|
|||
|
|
|
|||
|
|
// ① 바닥 재질에 맞는 클립 배열 가져오기
|
|||
|
|
AudioClip[] clips = GetFootstepClips(); // 가져올거에요 -> 현재 바닥 재질 클립 배열을
|
|||
|
|
if (clips == null || clips.Length == 0) return; // 중단할거에요 -> 클립 없으면
|
|||
|
|
|
|||
|
|
// ② 직전 클립과 다른 클립 선택 (연속 동일 방지)
|
|||
|
|
AudioClip clip = PickNonRepeat(clips); // 뽑을거에요 -> 직전과 다른 클립을
|
|||
|
|
if (clip == null) return; // 중단할거에요 -> null이면
|
|||
|
|
|
|||
|
|
// ③ Pitch 랜덤 적용 → 같은 소리도 매번 다르게
|
|||
|
|
_audioSource.pitch = UnityEngine.Random.Range(pitchMin, pitchMax); // 설정할거에요 -> 랜덤 Pitch를
|
|||
|
|
|
|||
|
|
// ④ Volume 랜덤 적용 → 약간씩 다른 세기
|
|||
|
|
float vol = Mathf.Clamp( // 계산할거에요 -> 0~1 범위로 제한한 랜덤 볼륨을
|
|||
|
|
footstepVolume + UnityEngine.Random.Range(-volumeVariation, volumeVariation),
|
|||
|
|
0f, 1f
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
_audioSource.PlayOneShot(clip, vol); // 재생할거에요 -> 발소리를
|
|||
|
|
|
|||
|
|
// ⑤ Pitch 복원 (다른 소리에 영향 안 주게)
|
|||
|
|
_audioSource.pitch = _originalPitch; // 복원할거에요 -> 원래 Pitch로
|
|||
|
|
|
|||
|
|
_lastFootstepClip = clip; // 저장할거에요 -> 마지막 클립을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void OnSlashEffect() // 함수를 선언할거에요 -> 슬래쉬 이펙트 생성을 (애니메이션 이벤트)
|
|||
|
|
{
|
|||
|
|
SpawnEffect(slashEffectPrefabs); // 생성할거에요 -> 슬래쉬 이펙트를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 바닥 재질 감지
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
private AudioClip[] GetFootstepClips() // 함수를 선언할거에요 -> 바닥 재질에 맞는 클립 배열을 반환하는 GetFootstepClips를
|
|||
|
|
{
|
|||
|
|
if (surfaceFootsteps == null || surfaceFootsteps.Length == 0) // 조건이 맞으면 실행할거에요 -> 재질별 설정 없으면
|
|||
|
|
return defaultFootsteps; // 반환할거에요 -> 기본 발소리를
|
|||
|
|
|
|||
|
|
// 발 위치(약간 위)에서 아래로 Raycast
|
|||
|
|
Vector3 origin = transform.position + Vector3.up * 0.1f; // 계산할거에요 -> 레이 시작 위치를
|
|||
|
|
if (!Physics.Raycast(origin, Vector3.down, out RaycastHit hit, groundCheckDistance + 0.1f, groundLayerMask)) // 조건이 맞으면 실행할거에요 -> 바닥 못 찾으면
|
|||
|
|
return defaultFootsteps; // 반환할거에요 -> 기본 발소리를
|
|||
|
|
|
|||
|
|
// Tag로 재질 판별
|
|||
|
|
SurfaceType surface = TagToSurface(hit.collider.tag); // 변환할거에요 -> Tag를 SurfaceType으로
|
|||
|
|
|
|||
|
|
// Tag 실패 시 PhysicMaterial 이름으로 판별
|
|||
|
|
if (surface == SurfaceType.Default && hit.collider.sharedMaterial != null) // 조건이 맞으면 실행할거에요 -> Tag 매칭 실패하고 PhysicMaterial 있으면
|
|||
|
|
surface = NameToSurface(hit.collider.sharedMaterial.name); // 변환할거에요 -> PhysicMaterial 이름으로
|
|||
|
|
|
|||
|
|
// 해당 SurfaceType 클립 배열 검색
|
|||
|
|
foreach (SurfaceFootstep sf in surfaceFootsteps) // 반복할거에요 -> 재질별 설정 순회를
|
|||
|
|
{
|
|||
|
|
if (sf.surfaceType == surface && sf.clips != null && sf.clips.Length > 0) // 조건이 맞으면 실행할거에요 -> 타입 일치하고 클립 있으면
|
|||
|
|
return sf.clips; // 반환할거에요 -> 해당 재질 클립 배열을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return defaultFootsteps; // 반환할거에요 -> 매칭 실패 시 기본 발소리를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private SurfaceType TagToSurface(string tag) // 함수를 선언할거에요 -> Tag를 SurfaceType으로 변환하는 TagToSurface를
|
|||
|
|
{
|
|||
|
|
// Unity Tag에 Stone / Dirt / Wood / Metal / Grass / Water 추가해서 사용
|
|||
|
|
switch (tag) // 분기할거에요 -> Tag 값에 따라
|
|||
|
|
{
|
|||
|
|
case "Stone": return SurfaceType.Stone; // 반환할거에요 -> 돌 타입을
|
|||
|
|
case "Dirt": return SurfaceType.Dirt; // 반환할거에요 -> 흙 타입을
|
|||
|
|
case "Wood": return SurfaceType.Wood; // 반환할거에요 -> 나무 타입을
|
|||
|
|
case "Metal": return SurfaceType.Metal; // 반환할거에요 -> 금속 타입을
|
|||
|
|
case "Grass": return SurfaceType.Grass; // 반환할거에요 -> 풀 타입을
|
|||
|
|
case "Water": return SurfaceType.Water; // 반환할거에요 -> 물 타입을
|
|||
|
|
default: return SurfaceType.Default; // 반환할거에요 -> 기본 타입을
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private SurfaceType NameToSurface(string materialName) // 함수를 선언할거에요 -> PhysicMaterial 이름을 SurfaceType으로 변환하는 NameToSurface를
|
|||
|
|
{
|
|||
|
|
string lower = materialName.ToLower(); // 변환할거에요 -> 소문자로 (대소문자 무시)
|
|||
|
|
if (lower.Contains("stone") || lower.Contains("concrete") || lower.Contains("rock")) return SurfaceType.Stone;
|
|||
|
|
if (lower.Contains("dirt") || lower.Contains("sand") || lower.Contains("soil")) return SurfaceType.Dirt;
|
|||
|
|
if (lower.Contains("wood") || lower.Contains("plank")) return SurfaceType.Wood;
|
|||
|
|
if (lower.Contains("metal") || lower.Contains("iron") || lower.Contains("steel")) return SurfaceType.Metal;
|
|||
|
|
if (lower.Contains("grass") || lower.Contains("lawn")) return SurfaceType.Grass;
|
|||
|
|
if (lower.Contains("water") || lower.Contains("puddle")) return SurfaceType.Water;
|
|||
|
|
return SurfaceType.Default; // 반환할거에요 -> 매칭 실패 시 기본을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private AudioClip PickNonRepeat(AudioClip[] clips) // 함수를 선언할거에요 -> 직전 클립과 다른 클립을 뽑는 PickNonRepeat를
|
|||
|
|
{
|
|||
|
|
if (clips.Length == 1) return clips[0]; // 반환할거에요 -> 1개뿐이면 그냥 반환
|
|||
|
|
|
|||
|
|
AudioClip picked = clips[UnityEngine.Random.Range(0, clips.Length)]; // 뽑을거에요 -> 랜덤 클립을
|
|||
|
|
|
|||
|
|
if (picked == _lastFootstepClip) // 조건이 맞으면 실행할거에요 -> 직전과 같으면
|
|||
|
|
{
|
|||
|
|
int idx = Array.IndexOf(clips, picked); // 찾을거에요 -> 현재 인덱스를
|
|||
|
|
picked = clips[(idx + 1) % clips.Length]; // 뽑을거에요 -> 다음 인덱스 클립으로
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return picked; // 반환할거에요 -> 선택된 클립을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 스턴
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
public void ApplyStun(float duration) // 함수를 선언할거에요 -> 스턴을 적용하는 ApplyStun을
|
|||
|
|
{
|
|||
|
|
if (_stunCoroutine != null) StopCoroutine(_stunCoroutine); // 취소할거에요 -> 이전 스턴 코루틴을
|
|||
|
|
_stunCoroutine = StartCoroutine(StunRoutine(duration)); // 시작할거에요 -> 스턴 코루틴을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerator StunRoutine(float duration) // 코루틴을 정의할거에요 -> 스턴 처리를
|
|||
|
|
{
|
|||
|
|
yield return new WaitForSeconds(duration); // 기다릴거에요 -> 스턴 시간만큼
|
|||
|
|
_stunCoroutine = null; // 초기화할거에요 -> 핸들을
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
// 내부 유틸
|
|||
|
|
// ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
private void PlayRandomSound(AudioClip[] clips) // 함수를 선언할거에요 -> 랜덤 소리 재생을
|
|||
|
|
{
|
|||
|
|
if (clips == null || clips.Length == 0 || _audioSource == null) return; // 중단할거에요 -> 소리 없으면
|
|||
|
|
int idx = UnityEngine.Random.Range(0, clips.Length); // 뽑을거에요 -> 랜덤 인덱스를
|
|||
|
|
if (clips[idx] != null) _audioSource.PlayOneShot(clips[idx]); // 재생할거에요 -> 랜덤 소리를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SpawnHitEffect() // 함수를 선언할거에요 -> 피격 이펙트 생성을
|
|||
|
|
{
|
|||
|
|
SpawnEffect(hitEffectPrefabs); // 생성할거에요 -> 피격 이펙트를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SpawnDeathEffect() // 함수를 선언할거에요 -> 사망 이펙트 생성을
|
|||
|
|
{
|
|||
|
|
if (deathEffectPrefab == null) return; // 중단할거에요 -> 프리팹 없으면
|
|||
|
|
Vector3 pos = effectSpawnPoint != null ? effectSpawnPoint.position : transform.position; // 결정할거에요 -> 생성 위치를
|
|||
|
|
GameObject fx = Instantiate(deathEffectPrefab, pos, transform.rotation); // 생성할거에요 -> 사망 이펙트를
|
|||
|
|
Destroy(fx, 3f); // 예약할거에요 -> 3초 후 제거를
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SpawnEffect(GameObject[] prefabs) // 함수를 선언할거에요 -> 이펙트 프리팹 랜덤 생성을
|
|||
|
|
{
|
|||
|
|
if (prefabs == null || prefabs.Length == 0) return; // 중단할거에요 -> 프리팹 없으면
|
|||
|
|
int idx = UnityEngine.Random.Range(0, prefabs.Length); // 뽑을거에요 -> 랜덤 인덱스를
|
|||
|
|
if (prefabs[idx] == null) return; // 중단할거에요 -> 비어있으면
|
|||
|
|
|
|||
|
|
Vector3 pos = effectSpawnPoint != null ? effectSpawnPoint.position : transform.position; // 결정할거에요 -> 생성 위치를
|
|||
|
|
Quaternion rot = effectSpawnPoint != null ? effectSpawnPoint.rotation : transform.rotation; // 결정할거에요 -> 생성 방향을
|
|||
|
|
|
|||
|
|
GameObject fx = Instantiate(prefabs[idx], pos, rot); // 생성할거에요 -> 이펙트를
|
|||
|
|
ParticleSystem ps = fx.GetComponent<ParticleSystem>(); // 가져올거에요 -> 파티클 시스템을
|
|||
|
|
float destroyTime = ps != null ? ps.main.duration + ps.main.startLifetime.constantMax : 2f; // 계산할거에요 -> 제거 시간을
|
|||
|
|
Destroy(fx, destroyTime); // 예약할거에요 -> 자동 제거를
|
|||
|
|
}
|
|||
|
|
}
|