人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

BGMを鳴らす

無音の世界で寂しかったのでBGMでも付けてみます。すでに作ってあるサウンドエンジン(2005/12/10)を組み込んで音を鳴らすだけなんで簡単です。マップごとに独自のBGMを用意する。マップ切り替え時にBGMを切り替えるあたりがミソです。ちなみにMIDIファイルは、ドラクエからパクるTAM Music Factoryさんからお借りしています。すばらしい曲が多く、ゲームで使ってもいいそうです。私のつぼにはまるメロディがけっこう多くて気に入ってます。

rpg21.jar

マップごとにBGMを設定する

一般的にBGMはマップごとに決まってる感じがします。お城に入ればお城のBGMが流れ、フィールドに出ればフィールドのBGMが流れます。というわけでマップごとにBGMを設定しましょう。MapクラスにBGM番号を設定できるようにします。

    // BGM番号
    private int bgmNo;
    
    public Map(String mapFile, String eventFile,
               int bgmNo, MainPanel panel) {
        this.bgmNo = bgmNo;
        
        ・・・
    }
    
    /**
     * このマップのBGM番号を返す
     * @return BGM番号
     */
    public int getBgmNo() {
        return bgmNo;
    }

MapコンストラクタでBGM番号(bgmNo)を与えて再生するBGMを指定しています。Mapが管理するのはBGM番号だけで実際にBGMを鳴らす処理は入れません。BGMの再生処理は全Mapを管理しているMainPanelで行います。

BGMの再生

マップに割り当てられたBGM番号と実際に鳴らすMIDIファイル名を関連づける必要があります。それがMainPanelのbgmNamesです。

    // BGM名(from TAM Music Factory: http://www.tam-music.com/)
    // BGM番号は0, 1, ・・・というように割り当てられる
    private static final String[] bgmNames =
        {"tamhe07.mid", "tamsu02.mid"};

ここにMIDIファイル名の配列を用意してます。BGM番号は配列のインデックスです。つまり、BGM番号0のマップではtamhe07.midが鳴り、BGM番号1のマップではtamsu02.midが鳴るようにします。コンストラクタでマップを作るときにBGM番号を渡してます。

    // マップを作成(マップで鳴らすBGM番号を渡す)
    maps = new Map[2];
    // 王の間
    maps[0] = new Map("map/king_room.map",
                      "event/king_room.evt", 0, this);
    // フィールド
    maps[1] = new Map("map/field.map",
                      "event/field.evt", 1, this);

BGMのロードはloadSound()です。

    /**
     * サウンドをロードする
     */
    private void loadSound() {
        // BGMをロード
        for (int i=0; i<bgmNames.length; i++) {
            try {
                MidiEngine.load("bgm/" + bgmNames[i]);
            } catch (MidiUnavailableException e) {
                e.printStackTrace();
            } catch (InvalidMidiDataException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

bgmNamesのMIDIファイルを全部MidiEngineにロードしています。MidiEngineの使い方はサウンドエンジンを参照してください。まあ簡単なので直感的にわかると思いますが。BGMの再生はMainPanelのコンストラクタです。

    public MainPanel() {
        ・・・・

        // サウンドをロード
        loadSound();

        // BGMを再生
        MidiEngine.play(maps[mapNo].getBgmNo());

        ・・・
    }

現在いるマップ(maps[mapNo])のBGM番号を取り出して再生しています。最後にマップを切り替えたときにBGMも切り替えないといけません。これはMainPanelのheroMove()です。

    /**
     * 勇者の移動処理
     */
    private void heroMove() {
        // 移動(スクロール)中なら移動する
        if (hero.isMoving()) {
            if (hero.move()) {  // 移動(スクロール)
                // 移動が完了した後の処理はここに書く

                // 移動イベント
                // イベントがあるかチェック
                Event event = maps[mapNo].eventCheck(hero.getX(),
                                                     hero.getY());
                if (event instanceof MoveEvent) {  // 移動イベントなら
                    MoveEvent m = (MoveEvent)event;
                    // 移動元マップの勇者を消去
                    maps[mapNo].removeChara(hero);
                    // 現在のマップ番号に移動先のマップ番号を設定
                    mapNo = m.destMapNo;
                    // 移動先マップでの座標を取得して勇者を作り直す
                    hero = new Chara(m.destX, m.destY,
                                     0, DOWN, 0, maps[mapNo]);
                    // 移動先マップに勇者を登録
                    maps[mapNo].addChara(hero);
                    // 移動先マップのBGMを鳴らす
                    MidiEngine.play(maps[mapNo].getBgmNo());
                }
            }
        }
    }

マップの切り替えと同じタイミングでBGMも切り替えればいいですね。MidiEngineは play()を呼び出せば前に鳴ってたBGMは自動的に止まります。自分で止める必要はありません。BGM番号じゃわかりにくいので定数にしておくといいですね。こんなふうに

    // BGM番号
    public static final int CASTLE = 0;
    public static final int OVERWORLD = 1;
    public static final int TOWN = 2;
    public static final int CAVE = 3;
    public static final int VILLAGE = 4;
    public static final int SHRINE = 5;
    public static final int BATTLE = 6;