Динамічні та інтерактивні звуки
ПЕРЕДУМОВИ
Ця сторінка доповнює сторінки відтворення звуків і створення власних звуків!
Проблеми з SoundEvents
Як ми дізналися на сторінці використання звуків, бажано використовувати SoundEvent на стороні логічного сервера, навіть якщо це дещо суперечить інтуїції. Адже клієнту потрібно обробляти звук, який передається у ваші навушники, чи не так?
Такий спосіб мислення правильний. Технічно, саме клієнтська сторона повинна обробляти звук. Однак для простого відтворення SoundEvent сторона сервера підготувала великий проміжний крок, який може бути неочевидним на перший погляд. Які клієнти повинні чути цей звук?
Використання звуку на стороні логічного сервера розв'яжіть проблему передачі SoundEvent. Простіше кажучи, кожному клієнту (LocalPlayer) у діапазоні відстеження надсилається мережевий пакет для відтворення цього конкретного звуку. Звукова подія в основному транслюється зі сторони логічного сервера кожному клієнту, що бере участь, без необхідності про це взагалі думати. Звук відтворюється один раз із заданою гучністю та висотою.
Але що робити, якщо цього недостатньо? Що робити, якщо під час відтворення звуку потрібно динамічно змінювати гучність і висоту, і все це на основі значень, які надходять із таких речей, як Entities або BlockEntities?
Простого способу використання SoundEvent на стороні логічного сервера недостатньо для цього випадку використання.
Підготовка аудіофайлу
Ми збираємося створити новий зациклений звук для іншого SoundEvent. Якщо ви можете знайти аудіофайл, який уже безперешкодно відтворюється, просто виконайте дії, описані в розділі створення власних звуків. Якщо звук ще не зациклюється ідеально, нам доведеться підготувати його до цього.
Знову ж таки, більшість сучасних DAW (Програмне забезпечення цифрової роботи з аудіо) мають бути здатні на це, але я люблю використовувати Reaper, якщо редагування аудіо є дещо складнішим.
Налаштування
Наш початковий звук надходитиме від механізму.
Завантажмо файл у нашу DAW.

Ми можемо почути та побачити, що механізм запускається на початку та зупиняється в кінці, що не дуже добре для циклічних звуків. Виріжмо їх і налаштуємо маркери вибору часу відповідно до нової довжини. Також увімкніть режим Toggle Repeat, щоб аудіо відтворювалося циклічно, поки ми його налаштовуємо.

Видалення завадних звукових елементів
Якщо ми уважно прислухаємося, на задньому фоні чути звуковий сигнал, який міг походити від машини. Я думаю, що в грі це не звучатиме чудово, тому спробуймо це видалити.
Це постійний звук, який зберігає свою частоту протягом усього звуку. Тому простого фільтра еквалайзера має бути достатньо, щоб відфільтрувати його.
Reaper поставляється з уже обладнаним еквалайзером, який називається «ReaEQ». Це може бути розташовано в іншому місці та називатися по-іншому в інших DAW, але використання еквалайзера є стандартним у більшості DAW на сьогодні.
Якщо ви впевнені, що у вашій DAW немає доступного фільтра еквалайзера, перевірте наявність безплатних альтернатив VST в Інтернеті, які ви можете встановити у вибраній DAW.
У Reaper використовуйте вікно ефектів, щоб додати звуковий ефект «ReaEQ» або будь-який інший еквалайзер.

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

Якщо ви не є навченим аудіоінженером, ця частина здебільшого стосується експериментів і методу проб і помилок. Між нодом 2 і 3 є досить сильна нерівність. Перемістімо ноди так, щоб знизити частоту лише для цієї частини.

Крім того, за допомогою простого фільтра еквалайзера можна досягти інших ефектів. Наприклад, скорочення високих і/або низьких частот може створити враження звуків, що передаються по радіо.
Ви також можете накладати більше аудіофайлів, змінювати висоту звуку, додавати трохи реверберації або використовувати складніші звукові ефекти, як-от «bit-crusher». Звуковий дизайн може бути цікавим, особливо якщо ви випадково знайдете хороші варіанти звучання вашого аудіо. Експериментування є ключовим, і, можливо, ваш звук стане ще кращим, ніж раніше.
Ми продовжимо лише фільтр еквалайзера, який ми використовували для вирізання проблемної частоти.
Порівняння
Порівняймо вихідний файл з очищеною версією.
Ви можете почути чітке дзижчання та звуковий сигнал, можливо, від електричного елемента механізму, в оригінальному звуці.
За допомогою фільтра еквалайзера ми змогли майже повністю видалити його. Однозначно приємніше слухати.
Створення циклу
Якщо ми дозволимо звуку відтворити до кінця і знову розпочнемо спочатку, ми чітко почуємо, як відбувається перехід. Мета полягає в тому, щоб позбутися цього, застосувавши плавний перехід.
Почніть з того, що виріжте шматок з кінця, розміром якого ви хочете, щоб був перехід, і розмістіть його на початку нової звукової доріжки. У Reaper ви можете розділити аудіо, просто перемістивши курсор до місця розрізу та натиснувши S.

Можливо, вам також доведеться скопіювати звуковий ефект еквалайзера першої звукової доріжки на другу.
Тепер нехай кінцева частина нової доріжки зникне, а початок першої аудіодоріжки з’явиться.

Експортування
Експортуйте аудіо з двома аудіодоріжками, але лише з одним аудіоканалом (моно) і створіть новий SoundEvent для цього .ogg файлу у вашому моді. Якщо ви не впевнені, як це зробити, перегляньте сторінку створення власних звуків.
Тепер це закінчена аудіосистема циклічного відтворення для SoundEvent під назвою ENGINE_LOOP.
Використання SoundInstance
Щоб відтворювати звуки на стороні клієнта, потрібен SoundInstance. Проте вони все ще використовують SoundEvent.
Якщо ви хочете лише відтворити щось на кшталт натискання елемента інтерфейсу користувача, уже наявний клас SimpleSoundInstance.
Майте на увазі, що це буде відтворено лише на конкретному клієнті, який виконував цю частину коду.
java
No lines matched.1
WARNING
Зверніть увагу, що в класі AbstractSoundInstance, який SoundInstance успадковує, є анотація @Environment(EnvType.CLIENT).
Це означає, що цей клас (і всі його підкласи) буде доступний лише клієнтській стороні.
Якщо ви спробуєте використати це в логічному контексті на стороні сервера, ви можете спочатку не помітити проблему в грі наодинці, але сервер у мережевому середовищі вийде з ладу, оскільки він взагалі не зможе знайти цю частину коду.
Якщо ви маєте проблеми з цими проблемами, рекомендуємо створити свій мод за допомогою онлайн-генератора шаблонів з увімкненою опцією Split client and common sources.
SoundInstance може бути потужнішим, ніж просто відтворення звуків один раз.
Перевірте клас AbstractSoundInstance і які значення він може відстежувати. Окрім звичайних змінних гучності та висоти звуку, він також містить координати XYZ і якщо він має повторюватися після завершення SoundEvent.
Потім, поглянувши на його підклас, AbstractTickableSoundInstance, ми також отримуємо представлений інтерфейс TickableSoundInstance, який додає функціонал тикання до SoundInstance.
Тож, щоб скористатися цими утилітами, просто створіть новий клас для свого спеціального SoundInstance і розширте його з MovingSoundInstance.
java
No lines matched.1
Використання власного екземпляра Entity або BlockEntity замість основного екземпляра LivingEntity може дати вам ще більше контролю, наприклад, на основі методу tick() на методи доступу, але вам не обов’язково потрібно посилання на таке джерело звуку. Натомість ви також можете отримати доступ до BlockPos з іншого місця або навіть встановити його вручну лише один раз у конструкторі.
Просто майте на увазі, що всі об’єкти, на які посилаються, у SoundInstance є версіями з боку клієнта. У певних ситуаціях властивості логічної сутності на стороні сервера можуть відрізнятися від властивостей на стороні клієнта. Якщо ви помітили, що ваші значення не збігаються, переконайтеся, що ваші значення синхронізовано з пакетами S2C EntityDataAccessor, BlockEntity сутності або повними спеціальними мережевими пакетами S2C.
Після того, як ви закінчили створювати свій власний SoundInstance, його можна використовувати будь-де, якщо його було виконано на стороні клієнта за допомогою менеджера звуку. Таким же чином ви також можете зупинити настроюваний SoundInstance вручну, якщо необхідно.
java
No lines matched.1
Звуковий цикл тепер відтворюватиметься лише для клієнта, який запустив цей SoundInstance. У цьому випадку звук буде слідувати за самим LocalPlayer.
На цьому завершується пояснення створення та використання простого спеціального SoundInstance.
Розширені екземпляри звуку
WARNING
Наступний вміст охоплює складну тему.
На цьому етапі ви повинні добре знати Java, об'єктноорієнтоване програмування, генерики та системи зворотного виклику.
Знання щодо Entities, BlockEntities і користувацьких мереж також дуже допоможуть у розумінні сценарію використання та застосування розширених звуків.
Щоб показати приклад того, як можна створити складніші системи SoundInstance, ми додамо додаткові функції, абстракції й утиліти, щоб зробити роботу з такими звуками в більшому обсязі, легшою, динамічнішою та гнучкою.
Теорія
Подумаймо про те, яка наша мета із SoundInstance.
- Звук має повторюватися, доки працює зв’язаний власний
EngineBlockEntity SoundInstanceмає рухатися, дотримуючись позиції свого власногоEngineBlockEntity(BlockEntityне переміщуватиметься, тому це може бути кориснішим дляEntities)- Нам потрібні плавні переходи. Увімкнення або вимкнення майже ніколи не повинно відбуватися миттєво.
- Змінюйте гучність і висоту на основі зовнішніх факторів (наприклад, від джерела звуку)
Підсумовуючи, нам потрібно відстежувати екземпляр спеціального BlockEntity, регулюйте значення гучності та висоти під час роботи SoundInstance на основі значень із цього спеціального BlockEntity та реалізовуйте «Стани переходу».
Якщо ви плануєте створити кілька різних SoundInstance, які поводяться по-різному, краще створити новий абстрактний клас AbstractDynamicSoundInstance, який реалізує усталену поведінку і дозволяє фактичним власним класам SoundInstance поширюватися з нього.
Якщо ви плануєте використовувати лише один, ви можете пропустити абстрактний суперклас і натомість реалізувати цю функцію безпосередньо у своєму спеціальному класі SoundInstance.
Крім того, було б гарною ідеєю мати централізоване місце, де SoundInstance відстежуються, відтворюються та зупиняються. Це означає, що він повинен обробляти вхідні сигнали, напр. із власних мережевих пакетів S2C, перерахувати всі поточні запущені екземпляри та обробляти особливі випадки, наприклад, які звуки дозволено відтворювати одночасно та які звуки можуть потенційно вимкнути інші звуки після активації. Для цього можна створити новий клас DynamicSoundManager, щоб легко взаємодіяти з цією аудіосистемою.
Загалом наша звукова система може виглядати так, коли ми закінчимо.

INFO
Усі ці переліки, інтерфейси та класи будуть створені заново. Налаштуйте систему та утиліти відповідно до конкретного випадку використання, як вважаєте за потрібне. Це лише приклад того, як можна підходити до таких тем.
Інтерфейс DynamicSoundSource
Якщо ви вирішите створити новий, більш модульний, власний клас AbstractDynamicSoundInstance як суперклас, можливо, ви захочете посилатися не лише на один тип Entity, а й на інші, або навіть на BlockEntity.
У цьому випадку використання абстракції є ключовим. Замість посилань напр. спеціальний BlockEntity безпосередньо, лише відстеження інтерфейсу, який надає дані, виправляє цю проблему.
Надалі ми використовуватимемо спеціальний інтерфейс під назвою DynamicSoundSource. Він реалізований у всіх класах, які хочуть використовувати цю функціональність динамічного звуку, як-от ваш власний BlockEntity, сутності або навіть, за допомогою міксинів, у вже наявних класах, як-от Zombie. В основному він представляє лише необхідні дані джерела звуку.
java
No lines matched.1
Після створення цього інтерфейсу обов’язково реалізуйте його також у необхідних класах.
INFO
Це утиліта, яку можна використовувати як на стороні клієнта, так і на стороні логічного сервера.
Отже, цей інтерфейс слід зберігати в загальних пакетах, а не в клієнтських пакетах, якщо ви використовуєте опцію «розділити джерела».
Підрахування TransitionState
Як згадувалося раніше, ви можете припинити запуск SoundInstance за допомогою SoundManager клієнта, але це призведе до того, що SoundInstance миттєво стихне. Наша мета полягає в тому, щоб, коли надходить сигнал зупинки, не зупинити звук, а виконати завершальну фазу його «перехідного стану». Лише після завершення фази завершення власний SoundInstance слід зупинити.
TransitionState — це щойно створений перелік, який містить три значення. Вони будуть використовуватися для відстеження фази звуку.
- Фаза
STARTING: звук спочатку тихий, але повільно голоснішає - Фаза
RUNNING: звук працює нормально - Фаза
ENDING: звук починається з вихідної гучності та повільно тихішає, поки не стихне
Технічно простого переліку з фазами може бути достатньо.
java
public enum TransitionState {
STARTING, RUNNING, ENDING
}1
2
3
2
3
Але коли ці значення надсилаються через мережу, ви можете визначити для них Identifier або навіть додати інші власні значення.
java
No lines matched.1
INFO
Знову ж таки, якщо ви використовуєте «розділені джерела», вам потрібно подумати про те, де ви будете використовувати цей перелік. Технічно лише спеціальні SoundInstance, які доступні лише на стороні клієнта, використовуватимуть ці значення enum.
Але якщо цей перелік використовується десь ще, наприклад у власних мережевих пакетах вам, можливо, доведеться розмістити цей перелік також у загальних пакетах замість пакетів лише для клієнта.
Інтерфейс SoundInstanceCallback
Цей інтерфейс використовується як зворотний виклик. Наразі нам потрібен лише метод onFinished, але ви можете додати власні методи, якщо вам потрібно надіслати також інші сигнали від об’єкта SoundInstance.
java
No lines matched.1
Упровадьте цей інтерфейс у будь-якому класі, який повинен мати можливість обробляти вхідні сигнали, наприклад, AbstractDynamicSoundInstance, який ми незабаром створимо, і створимо функціональні можливості в самому спеціальному SoundInstance.
Клас AbstractDynamicSoundInstance
Почнімо нарешті роботу над ядром динамічної системи SoundInstance. AbstractDynamicSoundInstance — це щойно створений абстрактний клас. Він реалізує функції визначення усталено та утиліти наших власних SoundInstances, які будуть успадковані від нього.
Ми можемо взяти CustomSoundInstance зі створеного раніше і покращити його. Замість LivingEntity ми будемо посилатися на наше DynamicSoundSource. Крім того, ми визначимо більше властивостей.
TransitionStateдля відстеження поточної фази- тривалість у тактах початкової та кінцевої фаз
- мінімальне та максимальне значення гучності та висоти
- логічне значення для сповіщення, якщо цей екземпляр було завершено та його можна очистити
- голдери такт для відстеження прогресу поточного звуку.
- зворотний виклик, який надсилає сигнал назад до
DynamicSoundManagerдля остаточного очищення, колиSoundInstanceфактично завершено
java
No lines matched.1
Потім установіть початкові, усталені значення для спеціального SoundInstance у конструкторі абстрактного класу.
java
No lines matched.1
Після завершення роботи конструктора вам потрібно дозволити SoundInstance відтворюватися.
java
No lines matched.1
Тепер настає важлива частина цього динамічного SoundInstance. На основі поточного такту екземпляра він може застосовувати різні значення та поведінку.
java
No lines matched.1
Як бачите, ми ще не застосували тут модуляцію гучності та висоти. Ми застосовуємо лише спільну поведінку. Таким чином, у цьому класі AbstractDynamicSoundInstance ми надаємо лише базову структуру та інструменти для підкласів, які можуть самі вирішувати, який тип звукової модуляції вони насправді хочуть застосувати.
Отже, розгляньмо кілька прикладів таких методів модуляції звуку.
java
No lines matched.1
Як бачите, нормалізовані значення в поєднанні з лінійною інтерполяцією (lerp) допомагають формувати значення відповідно до бажаних меж аудіо. Майте на увазі, що якщо ви додаєте кілька методів, які змінюють одне й те саме значення, вам потрібно буде спостерігати та коригувати, як вони працюють разом один з одним.
Тепер нам просто потрібно додати решту службових методів, і ми закінчили з класом AbstractDynamicSoundInstance.
java
No lines matched.1
Приклад реалізації SoundInstance
Якщо ми подивимося на фактичний спеціальний клас SoundInstance, який походить від новоствореного AbstractDynamicSoundInstance, нам потрібно лише подумати про умови, які призведуть до зупинки звуку та яку звукову модуляцію ми хочемо застосувати.
java
No lines matched.1
Клас DynamicSoundManager
Ми обговорювали раніше, як відтворити та зупинити SoundInstance. Щоб очистити, централізувати та керувати цими взаємодіями, ви можете створити власний обробник SoundInstance, який будується на цьому.
Цей новий клас DynamicSoundManager керуватиме спеціальними SoundInstances, тому він також буде доступний лише для клієнта. Крім того, клієнт повинен дозволити існування лише одному екземпляру цього класу. Кілька звукових менеджерів для одного клієнта не мають особливого сенсу та ще більше ускладнюють взаємодію. Отже, використаймо «Singleton Design Pattern».
java
No lines matched.1
Отримавши правильну базову структуру, ви можете додати методи, необхідні для взаємодії зі звуковою системою.
- відтворення звуків
- зупинення звуку
- перевірка, чи відтворюється звук
java
No lines matched.1
Замість того, щоб мати лише список усіх SoundInstances, які зараз відтворюються, ви також можете відстежувати, які звуки відтворюють джерела звуку. Наприклад, двигун, який має два звуки двигуна одночасно, не матиме сенсу, тоді як кілька двигунів, які відтворюють відповідні звуки двигуна, є дійсним крайнім випадком. Заради простоти ми щойно створили List<AbstractDynamicSoundInstance>, але в багатьох випадках HashMap DynamicSoundSource і AbstractDynamicSoundInstance може бути кращим вибором.
Використання розширеної системи звуків
Щоб використовувати цю систему звуків, просто скористайтеся методами DynamicSoundManager або SoundInstance. Використання onStartedTrackingBy і onStoppedTrackingBy від об’єктів або просто спеціальної мережі S2C, тепер ви можете запускати та зупиняти власні динамічні SoundInstance.
java
Not Found: /opt/build/repo/reference/latest/src/client/java/com/example/docs/network/ReceiveS2C.java1
Кінцевий продукт може регулювати свою гучність на основі звукової фази, щоб згладити переходи та змінити висоту на основі значення стресу, яке надходить від джерела звуку.
Ви можете додати інше значення до свого джерела звуку, яке відстежує значення «перегріву» і, крім того, дозволити шиплячому SoundInstance повільно зникати, якщо значення вище 0, або додати новий інтерфейс до вашого спеціального динамічного SoundInstance `s, який призначає значення пріоритету типам звуків, що допомагає вибрати звук для відтворення, якщо вони стикаються один з одним.
За допомогою поточної системи ви можете легко обробляти кілька SoundInstance одночасно та створювати звук відповідно до ваших потреб.

