公开了面向 Unity 的自制库 Narimono(鳴物)!!
Unity向けの自作ライブラリ Narimono(鳴物) を公開しました!!
はじめに
こんにちは、空音です!
普段はUnityやUnreal Engineを使ってゲーム開発をしている学生です。
最近、UIライブラリのKumikoを公開しましたが、今回はその第二弾として、Unity向けのサウンド再生ライブラリNarimono(鳴物)のv1.0.0をGitHubで公開しました!
KumikoとNarimonoは、どちらも私が普段のゲーム制作で使い回していた仕組みを改良し、ライブラリとして公開したものです。
この記事では、自作ライブラリを公開している取り組みについて触れたあと、Narimonoでできることと、設計で意識したことを紹介していきます!
自作ライブラリを公開する取り組み
現在、普段から使っている自作ライブラリを改良し、GitHubでライブラリとして公開する取り組みをしています。KumikoとNarimonoは、この取り組みで公開した1つ目と2つ目のライブラリです。
これまでは、便利な仕組みを作っても、プロジェクトごとにコピーして使っていました。この取り組みを始めた理由は、主に次の3つです。
-
プロジェクトをまたいで同じものを使うため
コピーして使い回していると、あるプロジェクトで直した不具合や改良がほかのプロジェクトに反映されず、少しずつ別物になっていきます。パッケージとして公開すれば、どのプロジェクトにも同じものをGit URLから導入でき、バージョンで管理できます。 -
設計を見直すきっかけにするため
自分だけが使うコードは、自分が分かれば動いてしまいます。ほかの人が使うことを前提にすると、特定のプロジェクトに依存した部分を切り離したり、名前やコメント、ドキュメントを見直したりする必要が出てきます。Narimonoも、汎用的に使えるように設計を一から見直しました。 -
ほかの人の意見をもらうため
公開することで、自分だけでは気づけない使い方や問題点を教えてもらえる可能性があります。
今後も、普段使っている自作ライブラリを順に改良して公開していく予定です!
Narimonoとは?
Narimonoは、効果音やBGMの再生を扱うUnity向けのサウンド再生ライブラリです。もともとは、自分のゲーム制作で使い回していたサウンド再生の仕組みを改良したものです。
Unityでは、AudioSourceをGameObjectに付けるだけで簡単に音を鳴らせます。しかし、ゲームを作り込んでいくと、次のような悩みが出てきます。
- 弾丸の着弾音のように大量に鳴る音で、同時再生数があっという間にあふれる
- 上限に達したときに古い音を止めると、音が「ブチッ」と途切れてしまう
- 遠くで鳴っている3Dサウンドが、聞こえないのに再生枠を使い続けている
- BGMを切り替えるたびに、フェードの処理を毎回書くことになる
Narimonoは、こうしたよくある処理をまとめて引き受けるライブラリです。
Narimonoの主な特徴
-
アセットによる音の管理
鳴らす音をScriptableObject(Cue)として定義します。文字列やenumのIDを管理する必要がありません。 -
3段階の同時再生数の制御
Cueごと、カテゴリごと、全体の3段階で上限を設定でき、上限に達したときの挙動も選べます。 -
3Dサウンドの距離による制限
聞こえない距離の音は再生せず、再生中に離れた音はフェードアウトして解放します。 -
フェード
Cueごとのフェードインと、秒数を指定したフェードアウト停止に対応しています。BGMのクロスフェードもカテゴリの設定だけで実現できます。 -
計測機能
同時再生数のピークや、再生しなかった回数とその理由を記録できます。記録はEditorとDevelopment Buildだけで行い、リリースビルドでは処理ごと取り除かれます。 -
外部ライブラリに依存しない
Unity標準の機能だけで動作します。AudioClipとAudioRandomContainerのどちらも再生できます。
なぜ「Narimono」という名前なのか?
Narimonoという名前は、歌舞伎の鳴物(なりもの)に由来しています。
鳴物は、歌舞伎の下座音楽のうち、太鼓や笛などの楽器で演奏されるものです。舞台下手の黒御簾(くろみす)の中で演奏され、場面を盛り上げる音楽だけでなく、大太鼓で風や雨の音を表すといった効果音も担います。
観客の目に触れない場所で、音楽と効果音を受け持つ裏方です。ゲームの裏側で効果音とBGMを鳴らし分けるこのライブラリの役割と重なるため、この名前にしました。
Kumiko(組子)に続いて、今回も日本の伝統文化から名前をもらっています!
Narimonoの設計について
音をアセットで指定する
Narimonoでは、鳴らす音をCueというScriptableObjectで定義します。
| アセット | 役割 |
|---|---|
AudioCue |
2Dの音の再生設定 |
SpatialAudioCue |
3Dの音の再生設定(距離の設定が加わる) |
AudioCategory |
同時再生数やミキサーグループをまとめて管理する単位 |
再生するときは、Cueをそのまま渡すだけです。
using Narimono;
using UnityEngine;
public sealed class PlayerSound : MonoBehaviour
{
[SerializeField] private AudioSystem _audioSystem;
[SerializeField] private AudioCue _jumpSound;
[SerializeField] private SpatialAudioCue _footstepSound;
public void PlayJump()
{
_audioSystem.Play(_jumpSound);
}
public void PlayFootstep()
{
_audioSystem.Play(_footstepSound, transform.position);
}
}
最初はenumで音を指定する設計にしていましたが、音を追加するたびにenumとデータベースの両方を更新する必要があり、登録漏れや重複もチェックしなければなりませんでした。
アセットを直接参照する形にしたことで、存在しない音を指定するミスが起きなくなり、参照切れもInspectorで確認できるようになりました。
また、2Dと3DでCueの型を分けているため、3DのCueを位置なしで再生しようとするとコンパイルエラーになります。実行して初めて気づくミスを、できるだけ型で防げるようにしています。
3段階の同時再生数の制御
同時再生数は、次の3段階で制限します。
| 段階 | 設定する場所 | 例 |
|---|---|---|
| Cueごと |
AudioCueのMax Instances(Limit Instancesをオン) |
着弾音は同時に4つまで |
| カテゴリごと |
AudioCategoryのMax Playback Count
|
効果音全体で12個まで |
| 全体 |
AudioSystemのMax Voice Count
|
ゲーム全体で32個まで |
上限に達したときの挙動は、CueとカテゴリそれぞれでAudioOverflowPolicyから選べます。
| 値 | 挙動 |
|---|---|
DiscardNew |
新しい音を捨てる |
StopOldest |
最も古い音を止めて、新しい音を再生する |
FadeOutOldest |
最も古い音をフェードアウトさせて、新しい音を再生する |
FadeOutOldestは、古い音を単純に止めると「ブチッ」と途切れてしまう問題を避けるために用意しました。
ただし、フェードアウト中の音も鳴り終わるまで再生枠を使い続けます。そのため、空きの枠がないときは、フェード中の音を途中で切るのではなく、新しい音のほうを捨てるようにしています。フェード中の音を切ってしまうと、結局「ブチッ」と途切れてしまうからです。
大量に鳴る効果音への対策
シューティングゲームの着弾音のように大量に鳴る音は、次の設定を組み合わせて対策します。
- Min Interval:同じCueを再び鳴らせるまでの最短間隔を設定し、同じフレームでの重複を間引く
- Max Instances:同じCueの同時再生数を小さく抑える
- カテゴリの分離:専用のカテゴリに分けて、ほかの重要な音を巻き込まないようにする
- 3Dの距離制限:遠くの着弾音はそもそも再生しない
再生した音はハンドルで操作する
Play()は、再生した音を指すハンドル(AudioPlayback)を返します。停止、一時停止、再開はこのハンドルで行います。
AudioPlayback playback = _audioSystem.Play(_loopCue);
_audioSystem.Pause(playback);
_audioSystem.Resume(playback);
// 1.5秒かけてフェードアウトしてから停止する
_audioSystem.Stop(playback, fadeOutDuration: 1.5f);
NarimonoはAudioSourceを使い回しているため、古いハンドルが、同じAudioSourceで後から鳴った別の音を止めてしまうおそれがあります。
そこで、再生のたびに番号を進め、終了済みの音や、別の音に使い回された枠を指すハンドルは安全に無視されるようにしています。この仕組みについては、次の記事で詳しく紹介する予定です!
フェードとBGMの切り替え
CueにFade In Durationを設定すると、再生開始時にフェードインします。
BGM用のカテゴリを次のように設定しておけば、新しいBGMをPlay()するだけでクロスフェードになります。
| 項目 | 値 |
|---|---|
| Max Playback Count | 1 |
| Overflow Policy | Fade Out Oldest |
| Fade Out Duration |
1〜2秒など |
_audioSystem.Play(_titleBgm);
// titleBgmがフェードアウトし、stageBgmがフェードインする
_audioSystem.Play(_stageBgm);
BGMを切り替えるたびにフェード処理を書く必要がなく、設定だけで済むのが気に入っているところです!
計測機能で上限値を調整する
同時再生数の上限は、慣れないうちはどのくらいに設定すればよいか分かりにくいです。
そこでNarimonoには、実際の再生状況を記録する計測機能を入れました。AudioStatsOverlayをシーンに置いてAudioSystemを割り当てると、再生中に計測結果を画面で確認できます。
- 同時再生数のピーク
- 上限やMin Intervalなど、理由ごとの再生しなかった回数
- フェードアウト中の音のピーク
記録処理にはConditional属性を付けているため、リリースビルドでは呼び出しごとコンパイル時に取り除かれ、再生のたびに記録の処理が走ることはありません。
開発を通して学んだこと
今回の開発では、最初の設計を一度まるごと作り直しました。
最初はenumとデータベースで音を管理していましたが、汎用的に使い回すことを考えると、登録の手間や重複チェックの保守が重く感じるようになりました。アセットを直接参照する形に変えたことで、確認しなければならないことが大きく減り、設計を見直す大切さを改めて感じました。
また、フェードアウトの実装では、一つの問題を解決すると、別の形で同じ問題が出てくることを経験しました。古い音をフェードアウトさせても、空きの枠がないときにフェード中の音を止めてしまえば、結局音は途切れてしまいます。目の前の現象だけでなく、本当に避けたいことは何かを考えることが大切だと感じました。
同時再生数の上限についても、設定できるようにするだけでは不十分で、利用者が適切な値を決められるようにすることまで考える必要がありました。計測機能は、その考えから生まれた機能です。
今後について
今後は、次のような改善を考えています。
- 音の重要度による割り込み(重要な音が、重要度の低い音を止めて鳴る仕組み)
- フェードのカーブの指定
-
AudioSystemの初期化前に呼び出されたときの保護 - サンプルシーンの追加
また、自分のゲーム制作でもNarimonoを使いながら、改善を続けていく予定です。Kumikoと同じように、内部設計を解説する記事も書いていきたいと思います!
おわりに
最後まで読んでいただき、ありがとうございました!
Kumikoに続いて、自作ライブラリを公開するのは今回で2つ目になります。使ってくださる方の目線で設計を考えるのは難しいですが、そのぶん学ぶことも多いと感じています。
感想や改善案などがあれば、ZennのコメントやGitHubで教えていただけると嬉しいです!不具合の報告や機能の提案は、GitHubのIssuesで受け付けています。
前回公開したUIライブラリのKumikoも、よければ覗いてみてください!
InputとUpdate 昨日の知識を、今日の技術へ
来源:zenn · 游戏开发社区 · zenn.dev