音楽ゲーム −ゲームエンジン編−



 まずは音楽ゲームのゲームエンジンとして必要な最低限の機能を抽出します。
・各レーン内のタイミングリスト、入力済みタイミングの管理
・タイミングリストの中で未入力の既に入力タイミングが経過したものの監視とその処理
・レーンの入力処理(タイミングリストの次の値と入力時間を比較しての採点)
・入力キーとレーンに対応付け
・BGM再生
・再生時間の取得

おおよその機能はこの程度です。タイミングを各レーンに振り分ける処理は必要ないのでありません。
それではこれらの機能を音楽ゲームエンジンをして実装します。
構成としては音楽再生、レーン、採点のクラスとこれらを管理するマネージャクラスが必要です。
実際に操作するのはマネージャクラスで音楽再生などはマネージャクラスから操作します。

この構成をクラス図にするとこのようになります。



まずはAudioPlayerインタフェースを定義し、その実装クラスを作成します。
AudioPlayerは次のようになります。
public interface AudioPlayer
{
    public void play();
    public int getPlayedTimeMillis();
    public void close();
    public boolean isFinished();
    public String getMusicPath();
}
 次にこれを実装したプレイヤを作ります。Javaで音楽再生を行うためにAudioClipとJavaSound というものが用意されていますし、JMFという拡張ライブラリもありますが、今回はMP3ファイルを 再生できるオープンソースのJLayer (http://www.javazoom.net/javalayer/javalayer.html)を利用します。 JLayerを使えば単純に音楽を再生するだけなら以下のコードでできます。
new javazoom.jl.player.Player(new java.io.FileInputStream("曲名.mp3")).play();
しかしJLayerはマルチスレッド対応でない為、このplay()メソッドはブロックします。これではゲームでは 使えないのでこのjavazoom.jl.player.Playerクラスの拡張クラスを作ります。
 Playerクラスでは、再生をplay()メソッドを呼び出した後、内部で別のplay(int frames)メソッドを呼び、 更にその内部でdecodeFrame()メソッドを繰り返し呼ぶことで1フレームごとの再生を行っています。なので play(int frames)メソッドをオーバーライドしてdecodeFrame()呼び出しをスレッドを作成して行えばいいのですが play(int frames)メソッド内部で使用しているPlayerクラスのフィールドがprivateで宣言されており、 単純にメソッドの中身をコピーできません。privateである以上、拡張クラスでは触らないほうがいいのかも しれませんので、その部分はとりあえず無視することにします。以下がマルチスレッドで再生できるように した部分のコードです。
public void run()
{
    boolean ret = true;
    
    while(ret)
    {
        synchronized(this)
        {
            while(stopFlag)
            {
                try
                {
                    wait();
                }
                catch(InterruptedException e1) {}
            }
        }
        
        try
        {
            ret = decodeFrame();
        }
        catch(JavaLayerException e)
        {
            throw new RuntimeException(e);
        }
    }
    
    isFinished = true;
}
JavaLayerExceptionをRuntimeExceptionに変換しているのはこの新たに作ったクラスを使用する際にJLayer の存在を無視できるようにです。更に一時停止機能も付加してあります。あとはこれを利用してAudioPlayer インタフェースを実装するメソッドを適宜作成していきます。詳しくはソースコードを参照してください。


次はScorerクラスを作成します。これは上のクラス図の通りです。
public abstract class Scorer
{
    protected int currentScore;
    
    public Scorer()
    {
        currentScore = 0;
    }
    
    public Scorer(int initialScore)
    {
        currentScore = initialScore;
    }
    
    public int getScore()
    {
        return currentScore;
    }
    
    public abstract int score(TimingLane lane);
    
    public abstract void miss();
}
各ゲームではこのクラスを継承して好きな採点方法を抽象メソッドに記述します。scoreメソッドで TimingLaneクラスを引数にとっているのは採点結果によってTimingLane内のタイミングリストの 操作を行うからです。また、TimingLaneクラスからタイミングリストの次の値を得ることもできる からです。


TimingLaneクラスもほぼクラス図の通りです。一部タイミングリストの参照を返すメソッドなどを 追加しています。タイミングリストはコンストラクタで与えます。


最後はこのパッケージの中核となるGameEngineクラスです。このクラスでは上で登場した AudioPlayerの実装クラス、Scorerの継承クラスを1つずつ、使用するTimingLaneインスタンスの 配列とそれに対応するキーオブジェクトを最初に与えます。内部で定義するメソッドはこれらを 使用します。以下がそのコードです。
public class GameEngine
{
    protected AudioPlayer player;
    protected HashMap laneMap = new HashMap();
    protected Scorer scorer;

    public GameEngine(AudioPlayer player, Scorer scorer, Object[] keys, TimingLane[] lanes)
    {
        this.player = player;
        this.scorer = scorer;
        
        if(keys.length!=lanes.length)
            throw new IllegalArgumentException("keys number must be same lanes number");
        for(int i=0;i< keys.length;i++)
        {
            laneMap.put(keys[i], lanes[i]);
            lanes[i].setEngine(this);
        }
    }

    public int getPlayedTimeMillis()
    {
        return player.getPlayedTimeMillis();
    }

    public int input(Object key)
    {
        return scorer.score((TimingLane)laneMap.get(key));
    }

    public void start()
    {
        new Thread(new Runnable(){

            public void run()
            {
                // 各レーンのタイミングリストの値に対する入力が行われていないかスレッドを作成して監視します
            }
        }, "SG-Observe Miss").start();
            
        player.play();
    }

    public void close()
    {
        player.close();
    }

    public boolean isFinished()
    {
        return player.isFinished();
    }
}
上のメソッドの他にキーを与えて対応するタイミングリストを得るgetTimingList(Object key) メソッドもあります。見ても分かるようにAudioPlayer等を単純に使用していることが多いですので このクラスは本当に中央管理するクラスと思ってください。


これで基本的な機能を持った音楽ゲームのゲームエンジンができました。
それでは次はこれを利用した専用のゲームライブラリを作成します。


ライブラリ編へ

メニューに戻る inserted by FC2 system