チャプター 04 / 06

4. BGM と SE を鳴らす

MitiruEngine のオーディオの扱い。BGM のミックスと SE のワンショットを web/mitiru_runtime/mitiru_audio.js から呼ぶ最短コードと、C++ 側でトリガするパターン。

音は「常に鳴る BGM」と「瞬間的な SE」の 2 種類に整理できれば、ほとんどのジャンルで足ります。MitiruEngine では runtime layer (web/mitiru_runtime/mitiru_audio.js) が CEF 側に薄い API を出しており、C++ から executeJavaScript で再生指示を出すのが基本パターンです。

  • アクション / シューティング: SE が高頻度、BGM はステージごとに切り替え
  • RPG / シミュレーション: BGM 重視 + UI 操作の SE
  • RTS: ユニット選択音 / 命令確認音 / 戦闘 SE が大量

どのジャンルでも、C++ 側のシーン / 状態機械が「今このサウンドを鳴らせ」とトリガし、JS 側は鳴らすだけ、というシグナル指向は変わりません。

BGM と SE のミキシング C++ 側のシーン状態が「鳴らせ」とトリガし、CEF audio runtime が BGM チャネルと SE チャネルをミキサで合成して出力デバイスに送る。

C++ Scene / State “battle start” / “hit”

BGM channel (loop)

SE channels (one-shot)

Voice / ambient

Mixer volume fade pan

Output miniaudio / WebAudio

C++ → executeJavaScript(Mitiru.audio.playBgm(...)) で CEF 側の audio runtime にトリガを送る

図: BGM はループ、SE はワンショット、ボイス / 環境音は別チャネル。Mixer で最終合成して出力する。

CEF 側の最短 API

<script type="module">
  import { audio } from '/runtime/mitiru_audio.js';

  audio.playBgm('bgm/main_theme', { volume: 0.6, loop: true });
  audio.playSe('se/click');
  audio.stopBgm({ fade: 0.5 });
</script>

C++ からトリガする

C++ 側がシーン / 状態を持っているので、「戦闘に入ったから BGM 切り替え」「ヒットしたから SE 鳴らす」は C++ 側のロジックから JS を叩きます。

void onBattleStart() {
    engine.cefContext().executeJavaScript(
        "Mitiru.audio.playBgm('bgm/battle', { volume: 0.7, loop: true });");
}

void onPlayerHit() {
    engine.cefContext().executeJavaScript("Mitiru.audio.playSe('se/hit');");
}

マニフェストでまとめる

assets/audio_manifest.json のような形でファイル一覧と既定音量を JSON で持つのが自然です (内容は宣言的なので、ContentLoader<AudioEntry> で読めます)。

{
  "bgm/main_theme": { "src": "bgm/main_theme.ogg", "volume": 0.8 },
  "se/click":      { "src": "se/click.wav",      "volume": 1.0 },
  "se/hit":        { "src": "se/hit.wav",        "volume": 0.9 }
}

関連 API

もっと深く知る