2026-06-02 사운드 추가
This commit is contained in:
@@ -71,6 +71,13 @@ public class ActionData : ScriptableObject
|
||||
public bool HitEffectAttachToPlayer; // true면 플레이어 자식으로 부착 (같이 움직임), false면 월드 고정
|
||||
public float HitEffectLifetime = 1f; // 자동 파괴 시간 (0이면 파괴 안 함 — 프리팹이 스스로 정리)
|
||||
|
||||
// ─── 효과음 (hit 발동 시 재생) ──────────────────────────────────────
|
||||
[Header("Audio")]
|
||||
public AudioClip AttackSound; // 무기 모션(휘두름/발사) 사운드. 적중 여부와 무관하게 발동 시 항상 재생 (null이면 없음)
|
||||
[Range(0f, 1f)] public float AttackSoundVolume = 1f; // 모션 사운드 볼륨
|
||||
public AudioClip HitSound; // 타격 사운드. 실제 적중 시에만 재생 (null이면 없음)
|
||||
[Range(0f, 1f)] public float HitSoundVolume = 1f; // 타격 사운드 볼륨
|
||||
|
||||
// ─── 피격자 반응 (피격된 적의 동작) ─────────────────────────────────
|
||||
[Header("Hit Reaction")]
|
||||
public Vector2 HitVelocity = Vector2.zero; // 적에게 가할 넉백 속도 (X는 공격자 facing 방향)
|
||||
|
||||
@@ -140,7 +140,6 @@ private void TryDamage(Collider2D other)
|
||||
if (_alreadyHit.Contains(target)) return;
|
||||
|
||||
_alreadyHit.Add(target);
|
||||
Debug.Log($"[Hitbox] t={Time.time:F3} damage={_damage} → {other.name} (parent={(target as MonoBehaviour)?.gameObject.name})");
|
||||
Vector2? targetPosition = GetCorrectionTargetPosition(other);
|
||||
target.TakeDamage(_damage, _hitVelocity, _hitReactionState, targetPosition, _correctHitTargetY, _hitPositionSolidMask, _hitPositionCorrectionDuration, _hitStunDuration);
|
||||
OnHit?.Invoke(target);
|
||||
|
||||
@@ -18,8 +18,6 @@ private void OnTriggerStay2D(Collider2D other)
|
||||
{
|
||||
if((_blockLayer.value & (1 << other.gameObject.layer)) > 0)
|
||||
{
|
||||
Debug.Log("aaaaaaa");
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ public abstract class BossSkill : MonoBehaviour
|
||||
[SerializeField] private float _cooldown = 8f; // 재사용 대기 (타이머는 BossAI가 보스별로 추적)
|
||||
[SerializeField] private string _casterAnimationState = "BossCast"; // 시전 중 보스가 재생할 애니메이션
|
||||
|
||||
[Header("Audio")]
|
||||
[SerializeField] private AudioClip _castSound; // 시전(발동) 사운드. 명중 여부와 무관하게 스킬 시작 시 항상 재생 (null이면 없음)
|
||||
[SerializeField, Range(0f, 1f)] private float _castSoundVolume = 1f; // 시전 사운드 볼륨
|
||||
|
||||
public int MinPhase => _minPhase;
|
||||
public float Range => _range;
|
||||
public float Cooldown => _cooldown;
|
||||
@@ -37,6 +41,8 @@ public abstract class BossSkill : MonoBehaviour
|
||||
// destroyCancellationToken: BossAI가 이 인스턴스를 Destroy하면 시퀀스가 취소된다.
|
||||
public async void Begin()
|
||||
{
|
||||
PlayCastSound();
|
||||
|
||||
try
|
||||
{
|
||||
await RunSkill(destroyCancellationToken);
|
||||
@@ -47,12 +53,20 @@ public async void Begin()
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e, this); // 스킬 버그가 나도 아래 Destroy로 정리는 보장
|
||||
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
// 시전(발동) 사운드 재생. 명중 여부와 무관하게 스킬 시작 시 항상 1회 재생.
|
||||
// AudioManager의 SFX 풀을 통해 믹서로 라우팅된다.
|
||||
private void PlayCastSound()
|
||||
{
|
||||
if (_castSound == null || AudioManager.Instance == null) return;
|
||||
AudioManager.Instance.PlaySfx(_castSound, _castSoundVolume);
|
||||
}
|
||||
|
||||
// 서브클래스가 실제 스킬 시퀀스를 구현. token이 취소되면 finally에서 정리할 것.
|
||||
protected abstract Awaitable RunSkill(CancellationToken token);
|
||||
}
|
||||
|
||||
95
Assets/02_Scripts/Managers/AudioManager.cs
Normal file
95
Assets/02_Scripts/Managers/AudioManager.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Audio;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class AudioManager : MonoBehaviour, ISceneInitializable
|
||||
{
|
||||
public static AudioManager Instance;
|
||||
|
||||
public AudioMixer mainMixer;
|
||||
public AudioSource bgmAudioSource;
|
||||
public AudioClip StartSceneBgm;
|
||||
public AudioClip GameSceneBgm;
|
||||
public AudioClip BossSceneBgm;
|
||||
|
||||
|
||||
// ─── SFX ─────────────────────────────────────────────────────────────
|
||||
[Header("SFX")]
|
||||
public AudioMixerGroup sfxGroup; // SFX를 라우팅할 믹서 그룹 (Inspector에서 연결)
|
||||
public int sfxSourceCount = 12; // 미리 만들어둘 AudioSource 풀 크기 (동시 재생 가능 수)
|
||||
|
||||
private AudioSource[] _sfxSources; // 재사용 풀
|
||||
private int _nextIndex; // 라운드로빈 인덱스 (전부 재생 중일 때 가장 오래된 것 교체)
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 싱글톤. 중복 생성되면 새로 들어온 쪽을 파괴.
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
|
||||
InitSfxPool();
|
||||
}
|
||||
|
||||
public void OnSceneLoaded()
|
||||
{
|
||||
if(SceneManager.GetActiveScene().name == "StartScene")
|
||||
{
|
||||
bgmAudioSource.clip = StartSceneBgm;
|
||||
}
|
||||
|
||||
if(SceneManager.GetActiveScene().name == "GameScene")
|
||||
{
|
||||
bgmAudioSource.clip = GameSceneBgm;
|
||||
}
|
||||
|
||||
if(SceneManager.GetActiveScene().name == "BossScene")
|
||||
{
|
||||
bgmAudioSource.clip = BossSceneBgm;
|
||||
}
|
||||
|
||||
bgmAudioSource.Play();
|
||||
}
|
||||
|
||||
|
||||
// SFX용 AudioSource를 미리 자식으로 생성. 재생 때마다 만들지 않고 재사용한다.
|
||||
private void InitSfxPool()
|
||||
{
|
||||
_sfxSources = new AudioSource[sfxSourceCount];
|
||||
for (int i = 0; i < sfxSourceCount; i++)
|
||||
{
|
||||
var go = new GameObject($"SfxSource_{i}");
|
||||
go.transform.SetParent(transform, false);
|
||||
|
||||
var src = go.AddComponent<AudioSource>();
|
||||
src.playOnAwake = false;
|
||||
src.spatialBlend = 0f; // 2D 사운드 (위치 무시)
|
||||
src.outputAudioMixerGroup = sfxGroup; // 믹서 SFX 그룹으로 라우팅
|
||||
_sfxSources[i] = src;
|
||||
}
|
||||
}
|
||||
|
||||
// 효과음 재생. 풀에서 놀고 있는 AudioSource를 빌려 PlayOneShot.
|
||||
public void PlaySfx(AudioClip clip, float volume = 1f)
|
||||
{
|
||||
if (clip == null || _sfxSources == null) return;
|
||||
GetFreeSource().PlayOneShot(clip, volume);
|
||||
}
|
||||
|
||||
// 재생 중이 아닌 소스를 우선 반환. 전부 사용 중이면 라운드로빈으로 가장 오래된 것 재사용.
|
||||
private AudioSource GetFreeSource()
|
||||
{
|
||||
for (int i = 0; i < _sfxSources.Length; i++)
|
||||
{
|
||||
if (!_sfxSources[i].isPlaying)
|
||||
return _sfxSources[i];
|
||||
}
|
||||
|
||||
AudioSource src = _sfxSources[_nextIndex];
|
||||
_nextIndex = (_nextIndex + 1) % _sfxSources.Length;
|
||||
return src;
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/Managers/AudioManager.cs.meta
Normal file
2
Assets/02_Scripts/Managers/AudioManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7ecd57ebf02cbe4bb9a209fa486671d
|
||||
@@ -126,6 +126,7 @@ public class PlayerController : MonoBehaviour,IDamageable
|
||||
[SerializeField] private bool _showAttackDebug = true; // OnGUI에 공격 정보 패널 표시 여부
|
||||
private float _attackStartTime = -1f; // 액션 시작 시각 (디버그 elapsed 계산)
|
||||
private bool _hitFired; // 현재 액션에서 hit이 이미 발화됐는지
|
||||
private bool _hitSoundPlayed; // 현재 hit window에서 타격음을 이미 재생했는지 (적 여러 명 동시 타격 시 중복 방지)
|
||||
|
||||
private readonly List<RaycastHit2D> _castResults = new(); // Cast 결과 버퍼 (GC 회피용)
|
||||
private Rigidbody2D _rb;
|
||||
@@ -923,6 +924,13 @@ private void OnAttackHit(IDamageable target)
|
||||
{
|
||||
if (target is Enemy enemy)
|
||||
_lastHitEnemy = enemy;
|
||||
|
||||
// 실제 적중이 일어난 순간에만 타격음 재생. 한 hit window에서 한 번만.
|
||||
if (!_hitSoundPlayed)
|
||||
{
|
||||
PlayHitSound(_lastHitData);
|
||||
_hitSoundPlayed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateAttackHitbox(ActionData data)
|
||||
@@ -935,11 +943,27 @@ private void ActivateAttackHitbox(ActionData data)
|
||||
_lastHitCenter = transform.TransformPoint(localPosition);
|
||||
_lastHitTime = Time.time;
|
||||
_hitFired = true;
|
||||
_hitSoundPlayed = false; // 새 hit window 시작 — 타격음 재생 가드 초기화
|
||||
|
||||
Vector2 sourcePosition = _rb != null ? _rb.position : (Vector2)transform.position;
|
||||
_attackHitbox.Activate(data, localPosition, hitVelocity, sourcePosition, hitTargetPosition, data.CorrectHitTargetY, _groundLayer.value, _enemyLayer);
|
||||
|
||||
SpawnHitEffect(data);
|
||||
PlayAttackSound(data);
|
||||
}
|
||||
|
||||
// 무기 모션(휘두름/발사) 사운드 재생. 적중 여부와 무관하게 hit window 활성 시 항상 재생.
|
||||
private void PlayAttackSound(ActionData data)
|
||||
{
|
||||
if (data.AttackSound == null || AudioManager.Instance == null) return;
|
||||
AudioManager.Instance.PlaySfx(data.AttackSound, data.AttackSoundVolume);
|
||||
}
|
||||
|
||||
// 타격 사운드 재생. 실제 적중(OnAttackHit) 시점에만 호출된다. AudioManager의 SFX 풀을 통해 믹서로 라우팅된다.
|
||||
private void PlayHitSound(ActionData data)
|
||||
{
|
||||
if (data.HitSound == null || AudioManager.Instance == null) return;
|
||||
AudioManager.Instance.PlaySfx(data.HitSound, data.HitSoundVolume);
|
||||
}
|
||||
|
||||
// hit 발동 시점에 이펙트 프리팹 생성.
|
||||
|
||||
Reference in New Issue
Block a user