ADVENT CALENDAR 2019

Altseedの音クラスのID管理を楽にした話

By Funny_Silkie

はじめに

Amusement CreatorsFunny_Silkie です。
今回はAltseedの音周りについて,自分で使いやすくしたクラスを作った話をします。
Altseed使わない人からしたら何言ってんだこいつみたいになると思いますが…

目次

  1. Alteedの現状の音周り
  2. SoundPlusクラスで何をしているか
  3. ID管理のためにどうすればよいか
  4. 実装内容
  5. 最後に

Alteedの現状の音周り

Altseedの音周りでは,主にSoundクラスとSoundSourceクラスを使用します。
SoundSourceクラスは読み込んだ音源を保管しておくクラスで,ループ地点などを設定できます。
SoundクラスではSoundSourceクラスの音源を再生したり,音量を調整できたりします。 また,音源を読み込んでSoundSourceクラスのインスタンスを生成するメソッドもあります。
SoundクラスのPlayメソッドで音源を再生するときにIDが返り値で出されます。このIDを使うことで音量の設定や一時停止などをする事が出来ます。
整理するとこんな感じになります。

  1. Soundクラスで音源を読み込んでSoundSourceクラスのインスタンス生成
  2. Sound.Play(SoundSource)で音源を再生を開始し,IDを取得
  3. 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(IsPlayBackSpeedEnabledfalse)

メソッド

メソッド名 引数             説明             例外
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