yaneSDKのSound周りをいじってみる

YaneuraoGameSDK.NETyaneSDK.NET,YaneGameSDK.NET)で遊ぼうといろいろいじってみた.次は.netな言語で作ってみたいなと前から思っていたのだが,yaneSDKC#実装になっていたので使ってみることにした.マルチメディア関連はpygameと同様にSDLがベースなので都合が良いと言えばよい.


一通りの機能を試してみて,あれ?と思ったのがSoundクラス.pygameには再生中のmusicの位置を取得したり設定することができたのだが,yaneSDKには実装されていないようだ.調べてみるとSDL_mixer(音楽再生関連の機能を集めたもの)には再生位置を設定する関数Mix_SetMusicPosition(double)は用意されているが,取得する方は用意されていない.


位置指定する方はこんな感じですぐに動いた.

// musicの再生位置指定
public YanesdkResult SetMusicPosition(double pos)
{
  CacheSystem.OnAccess(this);      
  if (music == IntPtr.Zero) return YanesdkResult.PreconditionError;
  SDL.Mix_RewindMusic();
  return SDL.Mix_SetMusicPosition(pos) == 0 ?
    YanesdkResult.NoError : YanesdkResult.SdlError;
}


手こずったのは再生位置取得の方だが,pygameにあるんだからどうにか実装できるんだろうと思ってCソースを見てみるとmusic.cに載っていた.どうやらmusic再生中にストリームを受け取るコールバック関数を指定して再生時間を調べているらしい.(同時に音源データのかたまりも受け取っているのでイコライザとかスペアナとかも実装しようと思えばできるのだろうか)


MixMusicCallbackをコールバック関数としてPlayMusicで指定すると,約50ミリ秒ごとにこの関数が呼ばれるので再生時間が取得できるというからくりだ.ただ,ある幅のかたまりとその長さが送られてくるのでちょっと誤差が生じるため,SDL_GetTicksで実際の時間も取得している.

private static int music_pos = 0;
private static int music_pos_time = -1;

// ResumeしてからMixMusicCallback関数が呼ばれているか否か
private static bool called_callback = false;

// music再生中にSDLから呼ばれるコールバック関数
private static void MixMusicCallback(IntPtr data, IntPtr stream, int len)
{
  called_callback = true;
  if (SDL.Mix_PausedMusic() == 0)
  {
    music_pos += len;
    music_pos_time = (int)SDL.SDL_GetTicks();
  }
}

// loadMusicで読み込んだBGMを再生させる
private YanesdkResult PlayMusic()
{
    CacheSystem.OnAccess(this);
    isPlayingLast = true;

    if (NoSound) return YanesdkResult.PreconditionError;
    ChunkManager.music = this; // チャンネルの占拠を明示

    // volumeの設定(これは再生時に設定する)
    ChunkManager.SetVolume(0, Volume);

    // コールバック関数の指定
    // 追加部分ここから
    SDL.Mix_SetPostMix(MixMusicCallback, IntPtr.Zero);
    music_pos = 0;
    music_pos_time = (int)SDL.SDL_GetTicks();
    // 追加部分ここまで

    return SDL.Mix_PlayMusic(music, loopflag) == 0 ?
        YanesdkResult.NoError : YanesdkResult.SdlError;
}


実際に時間を取得する関数はこんな感じ.if (SDL.Mix_Paused...){...}の部分が誤差調整なんだけれども,実際にこの関数を使ってみるとPauseしてResumeした後の数フレームだけ再生時間が1秒ほどずれていた.どうやらResumeしてからコールバック関数が呼ばれるまでの誤差調整のせいらしいので,コールバック関数が呼ばれたかどうかのフラグを使うことにした.Resume内でcalled_callbackをfalseにして,MixMusicCallbackでtrueにしている.

// musicの再生位置を取得する
public int GetMusicPosition()
{
  if (music_pos_time < 0)
    return -1;

  int channel = Singleton<SoundConfig>.Instance.AudioChannels;
  int format = Singleton<SoundConfig>.Instance.AudioFormat;
  int freq = Singleton<SoundConfig>.Instance.AudioRate;

  int ticks = (1000 * music_pos / (channel * freq * ((format & 0xff) >> 3)));
  
  if (SDL.Mix_PausedMusic() == 0 && called_callback)
    ticks += (int)SDL.SDL_GetTicks() - music_pos_time;
  return ticks;
}

// 前回停止させていた再生ポジションから再開する。
public YanesdkResult Resume()
{
    CacheSystem.OnAccess(this);
    isPlayingLast = true;
    called_callback = false;  // 追加部分
    if (NoSound) return YanesdkResult.NoError;
    return ChunkManager.Resume(this);
}


再生時間のずれ(一部カット)
調べてみるとpygameでも同じような挙動になっていた.(当然だが)

music_pos: 114688,pos_time : 2024
694(数字だけの部分がGetMusicPositionの返り値)
711
727
music_pos: 131072,pos_time : 2115
Pause
743
743
Resume
1643
1690
music_pos: 147456,pos_time : 2675
744
762
778

珍しく一日で片が付いた.すっきりすっきり.


さて,シューティングゲームを作ってみようか.とりあえずBulletMLの実装だが,またまた運良くC#の実装を見つけたのでこれを使うことにする(BulletMLManager).BulletML自体はあまりいじらないで行きたいが,やはり以下の描画要素くらいは入れた方が楽かもしれない.これはこれで別ファイルで指定するという手もあるけどなー.

  • 弾の回転角(描画用)
  • 弾の画像
  • 透明度

あと,耐久度も入れるとBulletMLの中で弾と同じように敵も記述できることになるが,当たり判定のことを考えると敵と弾はきっちり分けて処理したほうが良さそうである.周辺の弾に軌道を影響されるような複雑な動きをする弾はソースの中に記述しちゃってlabel付きBulletとしてBulletMLに登録してしまうのが良さそう.luaで記述するのもありだけどなぁ.