7.2K Views
May 09, 18
スライド概要
講演者:一條 貴彰(株式会社ヘッドハイ)
こんな人におすすめ
・インディーゲームクリエイター
・これからサウンド処理を学ぶプログラマー
・小規模ゲーム案件を担当するプログラマー
受講者が得られる知見
・UnityにおけるAudio機能の基礎おさらい
・ゲームによくあるサウンド演出の実装例
・サウンド処理を実装する際の注意すべき点と最適化手法
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
2018/5/9 Audio機能の基礎と実装テクニック 一條貴彰 株式会社ヘッドハイ/ ゲーム作家・代表取締役
一條貴彰 ゲーム作家 / 株式会社ヘッドハイ 代表取締役 代表作:Steam / N3DS 「Back in 1995」 Unite 2016「個人ゲーム開発における『収益化』の現状と未来」 通称、狂気講演 趣味: 洋楽80’s Popsのレアレコード収集 / DJ 刀剣おじさん、ユーリおじさん @takaaki_ichijo
株式会社ヘッドハイ: 日本の小規模ゲーム開発者支援 Made with Unityの連載企画 PS4/Switch展開(メディアスケープ株式会社) Google Indie Games Festival (Japan) のサポート
講演構成 • イントロダクション • サウンド処理実装で考慮すべきこと • Unity Audioの各機能・パラメーター解説 • ゲームによくあるサウンド機能の実装例 • まとめ
イントロダクション: Unity Audio機能 わかりますか
“ Unityの音周り 未だにわからん 雰囲気でやってる 完全に 音なんて 「鳴らして」「止める」だけじゃん? 簡単じゃん? めんどいので最後に実装します! ”
(#^ω^)お?お?
気持ちは分かる • Unity本ではサウンドの扱いが薄かった • 新しい本では自分で20ページ書いた • Uniteでも実践系のAudio講演が少ない • Unity 5時の講演は少々専門的すぎた • 他国のUniteでも同じ状況っぽい • (スペクトラム解析とかNative Audio Pluginは別にいいんじゃ…)
会場アンケート •個人とか数人チームで小規模ゲーム作ってます •モバイル向けの運営型アプリゲーム作ってます
モバイル向け運営型アプリ開発者の皆さん 悪いことは言わないので、 統合型サウンドミドルウェアを 必ず検討しましょう
モバイル向けの運営型アプリゲーム について •本講演は以下をカバーしません •イベントごとに追加され続ける大量のBGMやボイスの管理 •サウンドデザイナーとの分業 •無数にあるドライバがアレなAndroid機種の対応 •モバイル向け音ゲーの開発 •インタラクティブミュージック •自前でやろうとすると工数爆発が半端ないです ※つよいサウンド技術チームがいるなら話は別 これから話すことを踏まえつつ、まずはサウンドミドルウェアの検討を
サウンド処理実装で考慮すべきこと
考慮すべき3大要素 CPU 負荷 再生レイテンシー: メモリ 使用量 再生 レイテンシー 「音をならす!」という命令を出してから鳴るまでのタイムラグ
リソースは限られている CPU負荷を抑えよう! →メモリや再生レイテンシーが犠牲になる CPU 再生レイテンシーを低く抑えよう! →CPU負荷やメモリ使用量が犠牲になる 音の特性を把握して、シチュエーションごとに調整を行う
ゲーム内にある音をざっくり分類してみる • SE (SFX) • メニュー内SE: ボタンの音とか • ゲーム内SE: 足音、発砲音 • ジングル:ちょっと長めの効果音 • 環境音:森の鳥のさえずり、町のがやがや • BGM • ボイス
ゲーム内に存在する音の種類の例 音の長さ 再生 優先度 同時 再生数 データ の数 圧縮 品質 レイテン シー 読込と破棄 GameSE 短い 低い 多い 多い 低い 高い 本編ゲーム開始 時に読込 MenuSE 超短い 高い 1音 少ない 低い 最高 ゲーム起動時に 読込 BGM 長い 最高 2音 (フェード) 多い 高い 低い ステージごとに 読込・破棄 環境音 長い 高い 1音 中程度 中程度 低い ステージごとに 読込・破棄 ジングル 中程度 高い 1音 中程度 中程度 低い ステージごとに 読込・破棄 ボイス 中程度 高い 2-3音 中程度 中~高 低い 必要なタイミン グで読込・破棄
ゲームの特性によって大事にしたい音は変わる • 格闘ゲーム • ヒット音の再生レイテンシーが大事 • 恋愛アドベンチャーゲーム • ボイスの品質が超大事 ゲームに登場する音を分類して、 どのタイプの音を最優先させるのか考える
Unity Audioの各機能 パラメーター解説
Audio 5大要素 •Audio Clip •Audio Source •Audio Listener •Audio Mixer •Audio Manager (Project Settings)
基本 Audio Source 再生処理 3次元処理など 適宜読み込み Audio Clip サウンド素材 Audio Source 再生処理 現実のスピーカーへ AudioListener ゲーム空間内の「耳」
Audio Clip
Audio Clip • Unityサウンドアセット • WAV/AIFFを入れるとこれになる • 圧縮形式や読み込みタイプを指定 • プラットフォームごとに変えられる設定と、 共通の設定がある
Audio Clip プラットフォーム共通の設定 • Force To Mono • 強制モノラル • ステレオ素材→モノラル化 で容量半分 • Load In BackGround • 非同期読み込みする。メインスレッドを止めない。 • ファイルサイズが大きい場合、再生が遅れることがある • AudioClip.loadStateから「Loading, Loaded, Failed」のステートが取れる
Audio Clipのプラットフォーム個別の設定 • Load Type • ゲーム実行時にサウンドデータをどのように読み込むかの設定 • Preload Audio Data • シーン読み込み時にメモリに読み込むかどうか • Compression FormatとQuality • 圧縮設定 • Sampling Rate Setting • 音素材のサンプリングレート周波数設定
Audio Clip: Compression Format • Vorbis(Ogg) • • • 現状はだいたいこれを選択 Qualityで圧縮率を指定できる 数値が小さいと音質は劣化する • ADPCM • • 低圧縮率(1/3.5固定)、低音質だけど再生負荷が軽い 銃撃音とか足音とか、ザラッとしても違和感のない音に有効 • PCM • 無圧縮 • デコード処理がないので、本当に負荷がヤバい時に使う 他にもプラットフォーム固有のフォーマットがあり、使うと軽くなる場合がある
Audio Clip: Sampling Rate Setting 音のアナログ信号をデジタルに変換するときの1秒当たりのサンプル数。 「44.1kHz (44,100Hz)」は、1秒に44,100回アナログ信号を計測したということ
Audio Clip: Sampling Rate Setting • Preserve Sample Rate: 変えない • Optimize Sample Rate: 自動的に最適サンプリングレートに変更 • Override Sample Rate: サンプリングレートを指定値に変更 • 小さくするとデータ量は減るものの音質が悪くなる(モワッとした感じ) • サンプリングレートの設定傾向 • 44,100Hz: CD音質。曲やカットシーンボイスとか • 22,050Hz: 効果音、環境音など重要でない音 • 11,025Hz: 雑にしても気にならない効果音向け
Audio Clip: LoadType “Decompress On Load” • 圧縮データを読み込み時にメモリで展開 • 多くのメモリが消費されるが、再生レスポンスが早く負荷が軽い • メモリをメチャクチャ使うので、だいたいの場合は適さない • これ使うより圧縮設定をADPCMにしたほうがいい (何でこれデフォルトなの…) デコード CPU
Audio Clip: LoadType “Compressed In Memory” • 圧縮データをそのままメモリに読み込み、再生時に展開 • 読み込み時のメモリ消費は少ない • 再生時に展開するので、CPU負荷が発生する(特にVorbis圧縮の場合) • CPU負荷はProfilerのAudio項目にある「DSP CPU」で確認できる • ほとんどの音はこの設定でいい デコード CPU
Audio Clip: LoadType “Steaming” • 圧縮データをメモリに置かず、再生時に遂次ストレージから読み • Vorbis圧縮の場合リアルタイムデコードをしているので負荷と再生遅延がある • ストレージに常にアクセスが走る • CPU負荷は ProfilerのAudio項目内「Streaming CPU」から見れる • ボイスやBGMなど、比較的長く、再生遅延が気にならない音向け リアルタイム デコード CPU
Preload Audio Data オン: オフ: シーンの読み込み時にAudioClipをメモリに読み込む LoadType: Streamingのときは無効になる 最初のAudioSource.Play()が呼ばれたタイミングでAudioClipをメモリに読み込む →ストレージからの読み出し・デコード処理で再生が遅れる または、AudioSource.LoadAudioData() で任意のタイミングで読む AudioSource.UnloadAudioData() で破棄する
Audio Clipのロード関連は3種類 • Load In Back Ground →非同期読みするかどうかの選択 • Load Type →メモリ上にどう読み込むのかの選択 • Decompress On Load • Compressed In Memory • Streaming • Preload Audio Data →どのタイミングでメモリに読み込むかの選択
Audio Clipのパラメーター設定を自動化 Asset Post Processorを使う public void OnPostprocessAudio(AudioClip AudioClip) { AudioImporter audioImporter = assetImporter as AudioImporter; string path = audioImporter.assetPath; // MenuSeフォルダの中身は全部モノラル化// audioImporter.forceToMono = path.Contains(“MenuSe”); //BGMフォルダの中身は全部バックグラウンド読み込み// audioImporter.loadInBackground = path.Contains(“BGM”); } ※プラットフォームごとの設定はAudioImporterSampleSettingsクラス経由で設定
Audio Source
Audio Source • サウンドファイルを鳴らすためのコンポーネント • AudioClipを指定し、Play()で音が鳴る • 重要:使いまわす、無駄に生成しない • 常に使われるAudio Sourceはずっと生存させておく
Audio Sourceインスペクター • Play On Awake • ゲームオブジェクトが有効になった瞬間に再生 (なんでデフォルトオンなの) • Loop • ループ再生する • Priority • 再生優先度 • Unity上で同時に再生できる音数は限られている • 0~255の数値で指定 • 絶対に再生されてほしい音には小さい数字
Audio Sourceインスペクター 2 • Volume • 音量設定 • 0~1の数値で指定 • Spatial Blend(スぺィシャルブレンド) • スペース、つまり空間による影響量 • 3D空間内で音が鳴ったときの、位置関係か ら音の聞こえ方をどれぐらい変化させるか • パフォーマンスの影響なし
Audio Listener
Audio Listener • 君がそこに居るだけでいい • アクティブなカメラについてればOK • ただし1つだけ • シーン内に無いと、音は聞こえない
Audio Mixer
Audio Mixer AudioのHirerarchical Mixing(階層的ミックス)を可能にする
Audio Mixerのざっくり説明 • Audio Sourceをグループ化して管理 • グループごとにボリューム変更 • エコーやリバーブなど重いエフェクトをまとめてかける • ある音が鳴ったら別の音の音量を下げるとか(サイドチェーン)
こういう音量調整オプションを楽に作る
Audio Manager (Project Settings -> Audio)
Audio Manager (Project Settings) オーディオ全体設定
Audio Manager (Project Settings) その1 • System Sample Rate • • • 出力時のサンプリングレート 「0」の場合、プラットフォームごとのデフォルト設定になる PCやハイエンド機は48,000Hz、iOS/Android は 24,000Hz。
Audio Manager (Project Settings) その2 • DSP Buffer Size • 再生レイテンシーに関係する設定 • Default: デフォルト • Best Latency: パフォーマンスの低下と引き換えにレイテンシを抑える • Good Latency: バランス • Best Performance: レイテンシの増加と引き換えにパフォーマンスをよくする
Audio Manager (Project Settings) その3 • Max Virtual Voices • Unity内部で管理可能なサウンド再生リクエストの数 • Max Real Voices • 実際に同時再生が可能な数 • ハードウェアによっては固定されている • Audio Sourceで設定したプライオリティは、この範囲内で管理される
サウンド機能の実装
全人類が必ず自前実装する機能 • フェード処理 • フェードイン、フェードアウト、クロスフェード • ボリュームコンフィグ 他にもいろいろあるけど例として
フェードイン
フェードインの実装方法は色々ある • コルーチン使う • • Tweenライブラリの機能を使う • • ぱぱっとやりたい場合、他ライブラリに依存したくない場合 おすすめ Update関数内で回す • 何か辛い事情があったとき用…
前準備: AudioSourceに拡張メソッド追加 1行でAudio Clip渡しとnullチェックとボリューム設定と再生開始をやる関数を作る public static bool Play(this AudioSource audioSource, AudioClip audioClip = null, float volume = 1f) { if (audioClip == null || volume <= 0f) return false; audioSource.clip = audioClip; audioSource.volume = volume; audioSource.Play(); return true; } audioSource.Play(audioClip, 0.5f); って書ける、楽
コルーチンを使ったフェードインの実装例
ボリューム0で再生開始して、毎フレーム徐々にボリュームアップする
public static IEnumerator PlayWithFadeIn(this AudioSource audioSource, AudioClip audioClip,
float fadeTime = 0.1f)
{
audioSource.Play(audioClip, 0f);
//目標ボリュームに到達するまで毎フレームボリュームを上げる//
while (audioSource.volume < 1f)
{
float tempVolume = audioSource.volume + (Time.deltaTime / fadeTime );
//目標ボリュームより計算結果が大きいか判定(急に1を超えた大ボリュームにならないようにする)//
audioSource.volume = tempVolume > 1f ? 1f : tempVolume;
yield return null;
}
}
フェードアウトはこの逆で、毎フレームボリュームを下げる
Tweenライブラリ どれすき? ① ② ③ ④ 漢の自前実装 に入ってるTweener
Tweenライブラリ どれすき? ① ② ③ ④ 漢の自前実装 に入ってるTweener
DOTweenをつかったフェードイン実装 アダムもそう思います using DG.Tweening; ~~中略~~ public static void PlayWithFadeIn(this AudioSource audioSource, AudioClip audioClip, float fadeTime = 0.1f) { audioSource.Play(AudioClip, 0f); audioSource.DOFade(1f, fadeTime); } ぶっちゃけTweenライブラリ利用が一番楽
クロスフェード
クロスフェードの実装方針 1. AudioSourceを2本用意する 2. BGM再生開始時、フェードイン 3. すでに別のBGMが再生されていたら、そっちはフェードアウト
クロスフェードの実装の例
private List<AudioSource> audioSourceBGMList = new List<AudioSource>();
~~中略~~
public static void PlayWithFade(AudioClip audioClip, float fadeTime = 0.1f)
{
//空いてるAudioSourceを探す//
AudioSource audioSourceEmpty = audioSourceBGMList.FirstOrDefault(asb => asb.isPlaying == false);
//現在再生中のBGMがあればフェードアウト処理//
AudioSource audioSourcePlaying = audioSourceBGMList.FirstOrDefault(asb => asb.isPlaying == true);
if (audioSourcePlaying != null)
{
StartCoroutine(audioSourcePlaying.StopWithFadeOut(fadeTime)); //フェードアウト//
}
StartCoroutine(audioSourceEmpty.PlayWithFadeIn(audioClip, fadeTime)); //フェードイン//
}
フェード実装のコツ:予期せぬ中断を想定する • ゲームのポーズ • シーンの切り替わり • BGMが変わる境界をプレイヤーが行ったり来たり BGMが聞こえなくなったりしないよう、中断を考慮した実装をする
ポーズや停止処理でもフェードを使う • AudioSource.Pause() / UnPause()ありますが… • ハードウェアによっては「プチッ」というノイズが入ることがある • つらい… • わずかにフェードアウトしてから停止、わずかにフェードインしながら再生 開始することでノイズを回避できる • しかし足音とか極端に短い音には適さない(そのまま流す)
ボリュームコンフィグ
こういう音量調整オプションを楽に作る
Audio Mixer Groupとは • AudioSourceが鳴らしている音をグループ分けできる • Audio Mixerを通じて一括してボリュームなどを操作できる
Audio Mixer GroupのAudioSource側設定 「Output」にAudio Mixer Groupを設定する
Audio Mixerをセットアップ • Audio Mixerはアセットなので メニューか右クリックメニューで作る
Audio Mixer Groupを作る • Audio Mixerタブの「Groups」の+ボタン • New Groupができるのでリネーム • Audio Sourceの「Output」に参照を入れる
パラメーターの「エクスポーズ」 • スクリプトからAudioMixerを操作するための手続き • Audio Mixer Groupのインスペクタ内にて 「Attenuation」中の「Volume」の上で右クリック 「Expose ~~ to script」をクリック
エクスポーズしたパラメーターをMixerウインドウで確認 • MyExposedParamというデフォ名になるので、分かりやすい名前にリネーム 「BGMVolume」とか。
スクリプトからボリュームを設定・取得
public float GetBgmVolumeByLinear()
{
float decibel;
audioMixer.GetFloat("BGMVolume", out decibel);
return Mathf.Pow(10f, decibel / 20f);
}
public void SetBgmVolumeByLiner(float volume)
{
float decibel = 20.0f * Mathf.Log10(volume);
if (float.IsNegativeInfinity(decibel))
{
decibel = -96f;
}
audioMixer.SetFloat("BGMVolume", decibel);
}
※Audio Mixerはボリュームの単位が
デシベルである点に注意。
あとはUnityEngine.UIのスライダーに割り振る Slider.onValueChanged.AddListener(UnityAction<T0> call);を使って、 スライダーが動かされた時のデリゲートを指定
他にも、自前実装しないといけない機能は多数! ・ファイルロード関連 メイン処理をブロックしないようにAudioClipを非同期読み込みする ・物理挙動をトリガーにして音を鳴らす 挟まったときとかに超頻度で連続再生されないように制御する 同じフレーム内で二度再生リクエストが入らないように制御する(音量がすごいことになる) ・ランダム再生 再生開始位置のランダム ピッチ(音程)のランダム 複数音ファイルからチョイスしてランダム(同じのを繰り返さない処理とか超大変) ・再生完了コールバック AudioClipが再生し終わったらプログラムへ通知する 資料は作ったけど削ったので公開資料のオマケページにあります
まとめ
サウンド処理実装は早め早めに開発しよう! • 実装には意外と時間がかかります!終盤慌てないように。 • 音の演出が丁寧=ゲーム全体が高クオリティに感じられる 小規模タイトルこそ、音の処理にこだわろう!
ノウハウをオレオレサウンドマネージャーにまとめてます • Typical Sound Manager for Unity https://github.com/TakaakiIchijo/TypicalSoundManagerForUnity
著書が出ました(合同本) 「Unity ゲーム プログラミング バイブル」 15人で書いたアドバンスド活用術集! ボーンデジタル書籍販売コーナー 「hall B5」エスカレーター上がって右。 イベント限定10%オフ! Audio機能を活用したデモゲーム入り。
「もっと詳しいUnity サウンド本」とか、欲しいですか? • 今回解説できなかったサウンド処理の実装回り • UniRxの活用 • 音素材の生成と加工 • AudioMixerのさらなる応用 • プリセット切り替え • ダッキング等、サイドチェーン • エコー等のエフェクトルーティング • Audioリバーブゾーンの設定 • サウンドミドルウェアの使い方 • 音ゲーの開発 • VR、AR関連 • 経験がないので他の開発者にヒアリングして紹介
クレジット、スペシャルサンクス(資料作成のご協力) ゲームサウンド警察の皆様 ・SQEX 給前 瞬 さま (powerd by 土田提督) ・ XVI H.Yoshitaka さま ( @TyounanMOTI ) アセット ・Adam Character Pack
Thank you! ご静聴ありがとうございました!
おまけ
再生完了コールバック
再生完了コールバック • 想定用途:会話パートでの「NEXT」ボタン表示 • セリフの再生が終わった後で、「NEXT」「次へ」ボタンを表示させたい • セリフが再生終わったかどうか • 実装方法 • サウンド再生開始後、 AudioClipの長さの分だけ待ってから • AudioSource.isPlayingを監視すればいいのでは? →isPlayingはポーズでもfalseになってしまうので再生終了判定に使えない
再生コールバックのコルーチン実装 public static IEnumerator PlayWithCompCallback(this AudioSource audioSource, AudioClip audioClip, float volume = 1f, UnityAction compCallback = null) { audioSource.Play(audioClip, volume); float timer = 0f; while (timer < audioClip.length) { timer += Time.deltaTime; yield return null; } compCallback.?Invoke(); }
さては非同期だなオメー 版 public static async Task PlayWithCompCallback(this AudioSource audioSource, AudioClip audioClip, float volume = 1f) { audioSource.Play(audioClip, volume); int audioClipLengthMs = (int)TimeSpan.FromSeconds(audioClip.length).TotalMilliseconds; await Task.Delay(audioClipLengthMs); } Task.Delay()を使った再生完了コールバックもできなくはない、しかしポーズできない
別のアプローチ ・AudioSource.timeSamplesと audioClip.samplesを使う方法 →時間切れで断念 ・UniRxでもできる →時間(ry