ADVENT CALENDAR 2019
Altseedの音クラスのID管理を楽にした話
By Funny_Silkie
はじめに
Amusement Creators の Funny_Silkie です。
今回はAltseedの音周りについて,自分で使いやすくしたクラスを作った話をします。
Altseed使わない人からしたら何言ってんだこいつみたいになると思いますが…
目次
Alteedの現状の音周り
Altseedの音周りでは,主にSoundクラスとSoundSourceクラスを使用します。
SoundSourceクラスは読み込んだ音源を保管しておくクラスで,ループ地点などを設定できます。
SoundクラスではSoundSourceクラスの音源を再生したり,音量を調整できたりします。
また,音源を読み込んでSoundSourceクラスのインスタンスを生成するメソッドもあります。
SoundクラスのPlayメソッドで音源を再生するときにIDが返り値で出されます。このIDを使うことで音量の設定や一時停止などをする事が出来ます。
整理するとこんな感じになります。
Soundクラスで音源を読み込んでSoundSourceクラスのインスタンス生成Sound.Play(SoundSource)で音源を再生を開始し,IDを取得- IDを使って音量の調整や一時停止などを実施
 
それではID管理がめんどくさい!ってことで自動でID管理してくれるクラス,SoundPlus を試しに作ってみました。
名前適当とか言わない
SoundPlusクラスで何をしているか
コンストラクタでSoundSourceクラスの参照を受け取ります。
asd.Sound.Playを実行されるときに返されるIDをフィールドに持ち,asd.SoundクラスにおけるIDが必要なメソッドの実行を代理でしてくれます。
また,SoundクラスのGet…やSet…メソッドをプロパティにして扱いやすくしています。
プロパティの中のSoundStateプロパティでは,音の再生状況を返します。
型はSoundStateという独自の列挙体で,以下のようになっています。
/// <summary>
/// 音の状態を表す列挙体
/// </summary>
public enum SoundState
{
    /// <summary>
    /// 再生中
    /// </summary>
    Played,
    /// <summary>
    /// 一時停止中
    /// </summary>
    Paused,
    /// <summary>
    /// 停止中
    /// </summary>
    Stopped
}
ID管理のためにどうすればよいか
IDは音量調整,再生速度変更,一時停止,再生再開,再生停止など様々なことに必要で,asd.Sound.Playメソッドを実行するたびに発行されます。
そのため,クラスでIDを管理するとなると1インスタンスにつき1IDが妥当になり,過度な再生を抑止してIDを一意に保つ必要が出てきます。
結果,Playメソッドを実行するときに再生中の時は例外を投げる処理にしました。
Playメソッドとは別に,再生状況を無視して強制的に新たにIDを発行し再生するForceToPlayメソッドも作りました。
また,Playを何回か行っても音量や再生速度,パン位置などのSoundクラスでのみ設定できる要素が継続できるように,プロパティをフィールドとしても保持しておき,Playした瞬間に設定しなおします(下記参照)。
//プロパティは概してこんな感じ
/// <summary>
/// 音量を取得または設定する(0.0~1.0)
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">設定する値が規定範囲より外</exception>
public float Volume
{
    get => _volume;
    set
    {
        if (value < 0 || value > 1) throw new ArgumentOutOfRangeException();
        if (IsPlaying) Engine.Sound.SetVolume(ID, value);
        _volume = value;
    }
}
private float _volume = 1;
//一時停止中かどうか
private bool isPaused = false;
//Play時に返されるID
private int ID;
/// <summary>
/// 音を再生する
/// </summary>
/// <exception cref="InvalidOperationException">音が既に再生中または一時停止中だった</exception>
public void Play()
{
    if (IsPlaying || isPaused) throw new InvalidOperationException();
    StartPlaying();
}
/// <summary>
/// 現在の状態に関係なく強制的に音を再生する
/// </summary>
/// <remarks>既に再生中だった音のID管理はこのインスタンスでは実行されなくなる</remarks>
public void ForceToPlay()
{
    StartPlaying();
}
private void StartPlaying()
{
    //プロパティのコピー
    var volume = _volume;
    var pan = _panningPosition;
    var back = _playBackSpeed;
    var enable = _isPlaybackSpeedEnabled;
    //新ID発行
    ID = Engine.Sound.Play(source);
    //プロパティ再設定
    Volume = volume;
    PanningPosition = pan;
    PlayBackSpeed = back;
    IsPlaybackSpeedEnabled = enable;
    isPaused = false;
}
プロパティで値をセットするときはArgumentOutOfRangeExceptionをボンボン投げるようになっています。
オーバーしていたら最大値や最小値に丸めるという方法もありますが,そこらへんは実装側の性格次第か…?(このクラスではしょっちゅう例外ぶん投げる)
実装内容
SoundPlusクラスの実装はこんな感じです。
コンストラクタ
| 引数の型(変数名) | 実装内容 | 例外 | 
|---|---|---|
| SoundSource(source) | sourceの参照を持つ | ArgumentNullException(source) | 
| string(path), bool(isDecompress) | pathに存在する音源を読み取りSoundSourceの参照を持つ | ArgumentException(音源読み取り失敗), ArgumentNullException(path) | 
SoundSourceクラスの参照を持つため両方それに関係する実装です。
下の方のコンストラクタではasd.Engine.Sound.CreateSoundSource(string path, bool isDecompress)を使ってSoundSourceのインスタンスを生成しています。
プロパティ
| プロパティ名 | 型 | アクセッサ | 説明 | 例外 | 
|---|---|---|---|---|
| SoundState | SoundState | get | 現在の音の再生状況(再生中,一時停止中,再生されていない) | なし | 
| IsReleased | bool | get | フィールドの音源がメモリから解放されたかどうか | なし | 
| Length | float | get | 音源の長さ(秒) | なし | 
| LoopStartingPoint | float | get, set | ループ時の再生開始点(秒) | ArgumentOutOfRangeException(0未満またはLoopEndPoint以下) | 
| LoopEndPoint | float | get, set | ループ時の再生終了点(秒) | ArgumentOutOfRange(LoopStartPoint以下またはLengthより大きい) | 
| IsLoopingMode | bool | get, set | ループするかどうか | なし | 
| Volume | float | get, set | 音量の大きさ | ArgumentOutOfRangeException(0未満または1より大きい) | 
| IsPlayBackSpeedEnabled | bool | get, set | 再生速度を変更可能かどうか | なし | 
| PanningPosition | float | get, set | 音量のパン位置(-1に行く程左,1に行く程右) | ArgumentOutOfRangeException(-1未満または1より大きい) | 
| PlayBackSpeed | float | get, set | 再生速度(音程も変化) | ArgumentOutOfRange(0.25未満または4より大きい)InvalidOperationException(IsPlayBackSpeedEnabledがfalse) | 
メソッド
| メソッド名 | 引数 | 説明 | 例外 | 
|---|---|---|---|
| Fade | float second, float targetedVolume | 指定した値に音量を変化させていく | ArgumentOutOfRangeException(secondが0未満またはLengthより大きい,又はtargetedVolumeが0未満または1より大きい)InvalidOperationException(音が再生中ではない) | 
| FadeIn | float second | 指定した時間をかけてフェードインする | ArgumentOutOfRangeException(secondが0未満またはLengthより大きい)InvalidOperationException(音が再生中ではない) | 
| FadeOut | float second | 指定した時間をかけてフェードアウトする | ArgumentOutOfRangeException(secondが0未満またはLengthより大きい)InvalidOperationException(音が再生中ではない) | 
| Pause | なし | 再生中の音を一時停止する | InvalidOperationExcetion(音が再生中ではない又は既に一時停止している) | 
| Play | なし | 音の再生を最初から開始する | InvalidOperationExcetion(音が既に再生中又は一時停止している) | 
| ForceToPlay | なし | 音の最初からの再生を現在の再生状況問わず行う | なし | 
| Resume | なし | 一時停止されている状態から再生を再開する | InvalidOperationExcetion(音が再生中ではない又は一時停止していない) | 
| Stop | なし | 音の再生を停止する | InvalidOperationExcetion(音が再生中ではない) | 
最後に
取り敢えずID管理面倒くさい!ってことで実装してみました。今後のゲーム作りで活躍できるといいかなと言う感じです。
ソースコードは私のgithubで申し訳程度に公開されているので見たければどうぞ。
リンク集
SHARE THIS POST