Відтворення і управління звуками в Unity 3D (Sound complete event, Play in edit mode)

До цієї статті буде прикладений невеликий, але корисний csharp скрипт, і показано як ним користуватися.
Приводом для написання скрипта стало те, що з'явилася необхідність у налаштуванні та тестуванні звукових ефектів не запускаючи сцени проекту. А так само у відстеженні основних подій відтворення.

Скрипт працює однаково як в PlayMode так і в EditMode, і дозволяє:
1. Відтворити звук з необхідною затримкою і відстежити початок відтворення.
2. Відстежити закінчення звуку, в тому числі кожен момент завершення зацикленого відтворення.
3. Відстежити незаплановане закінчення відтворення звуку.
4. Використовувати подія для відстеження та зміни параметрів в процесі відтворення.

Для відтворення звуку використовуються статичні методи:

public static SoundTrack PlaySound(AudioClip clip, float volume = 1, float pitch = 1, float loopTime = 0, float delayTime = 0);

public static SoundTrack PlaySound(GameObject target, AudioClip clip, float volume = 1, float pitch = 1, float loopTime = 0, float delayTime = 0);

Ці методи повертають екземпляр класу SoundTrack, до якого надалі можна прикріпити необхідні події. Перший метод створює на сцені GameObject, другий додає вказаною GameObject компоненти SoundTrack і AudioSource.

Параметри volume і pitch не потребують, напевно, в поданні.

loopTime — можна використовувати для завдання часу в секундах, який буде тривати цикл відтворення. При значенні 0 звук буде програне тільки один раз, при значенні float.PositiveInfinity звук буде програватися нескінченно.
delayTime — це затримка перед відтворенням звуку в секундах.
Примітка:
По закінченні loopTime, перший метод видалить створений для звуку GameObject, другий метод видалить тільки створені для звуку компоненти.
Отже, найпростіший приклад коду для відтворення звуку в грі.

public AudioClip testSound;

void Start () {
SoundTrack.PlaySound (testSound);
}

І для відтворення звуку в редакторі.

[MenuItem("MyMenu/TestSound #F1")]
static void TestSound(){
AudioClip[] clips=Resources.FindObjectsOfTypeAll<AudioClip>();
if(clips==null||clips.Length==0){
Debug.LogError("No clips in the resources!");
return;
}
SoundTrack.PlaySound(clips[0]);
}


Тепер можна спробувати розібратися з подіями. Думаю для наочності краще відразу взяти приклад для редактора, так як в режимі гри все працює аналогічно.

Даний приклад запускається поєднанням клавіш SHIFT+F1, та показує, як використовувати події.

using UnityEngine;
using UnityEditor;
using System.Collections;

public class SoundTrackTest : Editor {

[MenuItem("MyMenu/TestSound #F1")]
static void TestSound(){
clips=Resources.FindObjectsOfTypeAll<AudioClip>();
Debug.Log("clips " + clips.Length);
if(clips==null||clips.Length==0){
Debug.LogError("No clips in the resources!");
return;
}
rePlayCount = 0;
StartNextSound (0);
}

static AudioClip[] clips;
static int rePlayCount=0;
static void StartNextSound(float timePosition){
SoundTrack track=SoundTrack.PlaySound(clips[Random.Range(0,clips.Length-1)]);
rePlayCount++;

// подія початку звуку
track.start_action += soundStartEvent;

// подія спрацьовує кожен кадр
track.processing += soundProcessEvent;

// ця подія підходить тільки для зацикленого відтворення
// воно спрацьовує кожного разу, коли звук завершується
track.complete_action += soundCompleteEvent;

// подія спрацьовує при далении звуку
track.destroy_action += soundDestroyEvent;

if(timePosition>0){
// перемотує точку на певний момент, у секундах
track.setTimePosition (timePosition);
}
}

static void soundStartEvent(SoundTrack track){
Debug.Log("Sound Start event!");
}

static void soundProcessEvent(SoundTrack track){
track.volume = track.playing_time % 1f;
}

static void soundCompleteEvent(SoundTrack track, float offset){
Debug.Log("Sound Complete event! "+offset);
}

static void soundDestroyEvent(SoundTrack track, bool atEndOfSound, float offset){
Debug.Log("Sound Destroy event! "+offset);

// перевіряє чи знищення ініційовано за закінчення відтворення
// atEndOfSound буде дорівнює false в разі якщо відтворення було перервано з інших причин
// наприклад при видаленні звуку зі сцени вручну і т. д.
if(atEndOfSound){
// даний підхід демонструє як можна плавно з'єднати кілька послідовних звуків
// offset - це неминуча затримка виклику події 
// можна її компенсувати використовуючи track.setTimePosition для наступного звуку

// це зроблено щоб звуки в редакторі не відтворювалися нескінченно
// так як якщо звук дуже короткий, це буде вельми неприємний тест
if(rePlayCount<4){
StartNextSound (offset);
}
}
}

}

Для того щоб приклад працював цей скрипт треба покласти в папку Editor, а так само мати в ресурсах проекту хоча б один звуковий файл. Якщо потрібен тест в занедбаному проекті, то можна просто функції TestSound перенести код в функції Start, і якщо потрібно, то зробити всі властивості і методи нестатическими.

void Start(){
clips=Resources.FindObjectsOfTypeAll<AudioClip>();
Debug.Log("clips " + clips.Length);
if(clips==null||clips.Length==0){
Debug.LogError("No clips in the resources!");
return;
}
rePlayCount = 0;
StartNextSound (0);
}

Це подія спрацьовує безпосередньо при початку відтворення звуку.

track.start_action += soundStartEvent; // подія початку звуку

Тобто в разі, якщо delayTime > 0, воно не спрацює при створенні звуку, а при старті відтворення.

Ця подія може бути корисно в ряді випадків.

track.processing += soundProcessEvent; // подія спрацьовує кожен кадр

До прикладу можна музики і ефектів задати різні події, і однієї змінної регулювати гучність всієї музики в грі, а інший всіх ефектів. А потім додати і ефектів і музики ще одна подія, в якому вже регулювати швидкість їх відтворення, якщо в грі є ефект Slow Motion.
В даному прикладі звук повинен протягом кожної секунди змінювати гучність.

track.complete_action += soundCompleteEvent; // подія спрацьовує кожного разу, коли звук завершується

Ця подія може стати в нагоді в ситуації, коли loopTime перевищує довжину звукового файлу, це означає, що звук буде програватися циклічно. У цьому випадку подія буде спрацьовувати в той момент, коли звук починається заново. В інших випадках виклик цієї події не гарантується.

track.destroy_action += soundDestroyEvent; // подія спрацьовує при видаленні звуку

Це подія спрацює в 2-х випадках. Перший випадок, це коли відтворення звуку завершено, після закінчення loopTime або при одноразовому відтворенні. Другий випадок, це ніяк не пов'язане з логікою скрипта видалення звуку зі сцени (наприклад вручну видаливши об'єкт з ієрархії сцени). Відрізнити один випадок від іншого можна за допомогою параметра atEndOfSound, якщо він дорівнює false, то це якраз другий випадок.

У другому випадку ми вже не будемо мати доступ до GameObject'у і компонентів звуку.

Якщо, наприклад, необхідно послідовно програти кілька мелодій і створити відчуття безперервного звучання, можна спробувати зробити це як показано в прикладі, функції soundDestroyEvent.

Крім цього в скрипті є параметри, які можуть знадобитися:
  • time_position — позиція відтворення аудіофайлу, від 0 до довжини файлу;
  • life_time — час з початку відтворення звуку;
  • playing_time — час витрачений на відтворення звуку, не враховує паузи;
  • loop_time — час, який буде програватись звук;
  • delay_time — час затримки перед відтворенням;
  • startTime — момент початку відтворення звуку;
  • created_time — момент створення звуку (startTime-delay_time).


Весь час розраховується щодо Time.realtimeSinceStartup.

Тут можна скачати сам скрипт і наведений вище приклад.

Якщо в прикладі не працює сполучення клавіш, можна знайти пункт TestSound в меню.

Якщо Unity не знаходить звук в ресурсах, треба його просто виділити (подивитися налаштування аудіо).

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.