Как сделать звук смерти после смерти объекта?

У меня есть объект Ship с событием Died и методом Die. Есть объект ShipAudio, который отлавливает различные события у Ship и проигрывает соответствующие звуки через AudioSource. Метод Die просто деактивирует объект и инвоукает событие.

ShipAudio, отловив Died, должен проигрывать звук взрыва. Но он этого не делает, из-за того, что ShipAudio прикреплён к тому же объекту, что и Ship, поэтому SetActive скрывает их обоих.

Кто бы что сделал с этой ситуацией? Можно ShipAudio расположить на другом объекте, но что если громкость звука в игре меняется в зависимости от удалённости Ship? Тогда удобнее, чтобы Ship и ShipAudio находились на одном объекте.

Код:

public class Ship : MonoBehavior
{
    public UnityEvent Died;

    public void Die()
    {
        gameObject.SetActive(false);
        Died?.Invoke();
    }
}

public class ShipAudio : MonoBehaviour
{
    [SerializeField] private Ship _ship;
    [SerializeField] private AudioClip _explosionAudio;

    private AudioSource _source;

    private void OnEnable()
    {
        _ship.Died.AddListener(OnDied);
    }

    private void OnDisable()
    {
        _ship.Died.RemoveListener(OnDied);
    }

    private void OnDied()
    {
        _source.clip = _explosionAudio;
        _source.Play();
    }
}

Ответы (1 шт):

Автор решения: Manciny

Если ShipAudio расположен на том же объекте, который выключает в момент наступления OnDie() - то проигрывание аудио будет сразу остановлено. Как вариант

Выключать объект после окончания проигрывания. Выключать в этом случае можно либо в методе (ниже в примере это будет корутина), вызывающем _source.Play(),

//public class Ship 
public void Die()
{
    Died?.Invoke();
}
//class ShipAudio
private void OnDied()
{
    StartCoroutine(PlaySoundAndDoAction());
}
private IEnumerator PlaySoundAndDoAction()
{
    _source.clip = _explosionAudio;
    _source.Play();
    yield return new WaitForSeconds(_explosionAudio.lenght);
        gameObject.SetActive(false);
}

либо передавать Action в Action, и уже из класса Ship решать, что делать дальше, например так:

//public class Ship 
Action<Action> Died;
public void Die()
{
    Died?.Invoke(()=> OnDieSoundComplete());
}
private void OnDieSoundComplete()
{
    gameObject.SetActive(false);
}

//class ShipAudio
private void OnDied(Action onComplete = null)
{
    StartCoroutine(PlaySoundAndDoAction(onComplete));
}
private IEnumerator PlaySoundAndDoAction(Action onComplete = null)
{
    _source.clip = _explosionAudio;
    _source.Play();
    yield return new WaitForSeconds(_explosionAudio.lenght);
    if(onComplete !=null)
        onComplete?.Invoke();
}

Еще, как вариант - можно скомбинировать костыль: ShipAudio повесить на объект пустышку, который будет дочерним Ship. Когда Ship погибает - то объекту с ShipAudio менять родителя на null. После проигрывания аудио - либо уничтожать объект пустышку, либо возращать его как дочерний объекту с Ship, если планируется его повторное использование (судя потому, что он в момент смерти SetActive(false), а не Destroy()).

→ Ссылка