[Next] [Prev] [Back] [Home]

■ UpperHand Gameを作る


ゲームの表示に凝ってみる

今回は音声データと画像データを使ってゲームの表示に凝ってみましょう。
以下のファイルを使います。

ファイル名
内容
ding.au
玉を置いたときに出す音
beep.au
不正な着手のときに出す音
laugh.au
コンピュータが勝ったときに出す音
ouch.au
コンピュータが負けたときに出す音
B1.gif, B2.gif
先手の玉の画像
R1.gif, R2.gif
後手の玉の画像
G1.gif, G2.gif
中立の玉の画像

音声データを扱う

JavaにはSun Audio形式(audio/basic)の音声データをダウンロードし、 再生する機能があります。
ダウンロードはAppletクラスの getAudioClip()メソッドで行います。 getAudioClip()AudioClipオブジェクトを返します。 音声の再生はAudioClipオブジェクトの play()メソッドで行います。

[注釈] AudioClipはインタフェースであり、 それ自体のインスタンスは存在しません。 getAudioClip()AudioClipインターフェースを 実装したサブクラスのインスタンスを返します。

まず、UpperHandクラスに 音声ファイルのファイル名を定義するクラス変数と AudioClipを格納するインスタンス変数を定義します。

//-----------------------------------------------------------------------------
//  クラス変数定義
//-----------------------------------------------------------------------------
        ...

//  音声のインデックス
private final static int    PUT_BALL_SOUND   = 0;   //  玉を置いたとき
private final static int    BAD_MOVE_SOUND   = 1;   //  不正な着手のとき
private final static int    APP_WIN_SOUND    = 2;   //  Appletが勝ったとき
private final static int    APP_LOST_SOUND   = 3;   //  Appletが負けたとき

//  音声ファイル名
private final static String soundName[] = {
                                "ding.au",
                                "beep.au",
                                "laugh.au",
                                "ouch.au"
                            };
//-----------------------------------------------------------------------------
//  インスタンス変数定義
//-----------------------------------------------------------------------------
        ...

//  音声
private AudioClip           sound[] = new AudioClip[soundName.length];

UpperHandクラスに音声ファイルをダウンロードするメソッド loadSound()と音声を再生するメソッド playSound()追加します。 loadSound()init()から呼び出します。
getAudioClip()の引数にはダウンロードする音声ファイルのURLを 指定します。 loadSound()では、AppletのベースURL (APPLETタグのCODEBASE属性で指定したURL) とファイル名の組で音声ファイルのURLを指定しています。

//-----------------------------------------------------------------------------
//  private void loadSound()
//      音声データをダウンロードする。
//-----------------------------------------------------------------------------
private void loadSound()
{
    for (int i = 0; i < sound.length; i++)
    {
        sound[i] = getAudioClip(getCodeBase(), soundName[i]);
    }
}
//-----------------------------------------------------------------------------
//  private void playSound(int id)
//      idで指定された音声を出力する。
//-----------------------------------------------------------------------------
private void playSound(int id)
{
    sound[id].play();
}

makeMove()に玉を置いたときに音声を再生する処理を追加します。

//-----------------------------------------------------------------------------
//  public void makeMove(int p)
//      位置pに着手する。
//-----------------------------------------------------------------------------
public void makeMove(int p)
{
    //  着手する。
    game.makeMove(p);

    if (game.isFinish())
    {                   //  ゲームが終わったとき
        finishGame();               //  終局時の処理を行う。
    }
    else
    {
        playSound(PUT_BALL_SOUND);  //  玉を置いた音を出す。
        nextMove();                 //  次の一手に進む。
    }
}

終局時の処理が少し複雑になったので、新規にメソッド finishiGame()を追加しました。 また、ゲームの勝者を知る必要があるので、 UpperHandGameクラスに新規にメソッド winner()を追加しています。

//-----------------------------------------------------------------------------
//  private void finishGame()
//      終局時の処理を行う。
//-----------------------------------------------------------------------------
private void finishGame()
{
    // コンピュータの手番を取得する。
    int p = (player[UpperHandGame.FIRST] != null) ?
                    UpperHandGame.FIRST :
                    UpperHandGame.SECOND;

    // ゲームの勝者を取得する。
    int winner = game.winner();

    if (winner == UpperHandGame.NUTRAL)
    {                       // 引き分け
        playSound(PUT_BALL_SOUND);
    }
    else
    if (winner == p)
    {                       // コンピュータの勝ち
        playSound(APP_WIN_SOUND);
    }
    else
    {                       // コンピュータの負け
        playSound(APP_LOST_SOUND);
    }
}

不正な着手を行ったときに音声を再生する処理は、 mouseDown()に追加しました。 ゲーム中に他のコンポーネントでマウスボタンが押されたときに処理しなかった場合は、 すべて不正な着手として扱っています。

//-----------------------------------------------------------------------------
//  public boolean mouseDown(Event evt, int x, int y)
//      マウスのボタンが押されたときの処理を行う。
//-----------------------------------------------------------------------------
public boolean mouseDown(Event evt, int x, int y)
{
    if (game.isFinish())
    {                   //  ゲームが終了している場合
        startGame();                //  新しいゲームを始める。
    }
    else
    {
        playSound(BAD_MOVE_SOUND);  //  不正な着手のときの音を出す。
    }

    return true;
}

画像データを扱う

画像データの扱いは音声データに似ています。 扱うことができる画像ファイルの形式はGIF形式(image/gif)です。
ダウンロードはAppletクラスの getImage()メソッドで行います。 getImage()Imageオブジェクトを返します。 画像の表示はGraphicsオブジェクトのdrawImage() メソッドにImageオブジェクトを指定することにより行います。

[注釈] Imageは抽象クラスであり、 やはりそれ自体のインスタンスは存在しません。

音声の場合と同様に、まず、UpperHandクラスに 画像ファイルのファイル名を定義するクラス変数と Imageを格納するインスタンス変数を定義します。

//-----------------------------------------------------------------------------
//  クラス変数定義
//-----------------------------------------------------------------------------
        ...

//  画像ファイル名
private final static String imageName1[] = {
                                "B1.gif",
                                "R1.gif",
                                "G1.gif"
                            };
private final static String imageName2[] = {
                                "B2.gif",
                                "R2.gif",
                                "G2.gif"
                            };
//-----------------------------------------------------------------------------
//  インスタンス変数定義
//-----------------------------------------------------------------------------
        ...

//  画像
public Image                ballImage1[] = new Image[imageName1.length];
public Image                ballImage2[] = new Image[imageName2.length];

つぎに、UpperHandクラスに画像ファイルをダウンロードするメソッド loadImage()を追加しますが、 画像ファイルのダウンロードには一工夫必要です。
getAudioClip()getImage()は 音声や画像を即座にはダウンロードしません。 ダウンロードは音声や画像が必要となるとき(再生や表示のとき)に 非同期に行われるのです。 そして、ネットワークが混んでいる場合などには、 ダウンロードに失敗することもあり得ます。 音声の場合、ダウンロードに失敗しても 音が鳴らないだけでたいした問題はありませんが、 画像の場合は表示されないとゲームができません。 そこで、

  1. 画像がダウンロードされるまで待つ
  2. ダウンロードに成功したかどうかを知る
ことが必要になります。 これを可能にするのがMediaTrackerクラスです。
MediaTrackerクラスには、 ダウンロードする画像を登録し(addImage())、 ダウンロードされるのを待ち(waitForID())、 ダウンロードの結果を判定する(isErrorID()) メソッドが用意されています。 登録のときにダウンロードの管理単位ごとにIDを指定する必要がありますが、 今回はすべての画像を一気にダウンロードし、その結果をまとめて知りたいので、 すべての画像をID=0として登録しています。

//-----------------------------------------------------------------------------
//  private void loadImage()
//      画像データをダウンロードする。
//-----------------------------------------------------------------------------
private void loadImage()
{
    showStatus("Now loading image files...");

    MediaTracker tracker = new MediaTracker(this);

    for (int i = 0; i < ballImage1.length; i++)
    {
        ballImage1[i] = getImage(getCodeBase(), imageName1[i]);
        tracker.addImage(ballImage1[i], 0);

        ballImage2[i] = getImage(getCodeBase(), imageName2[i]);
        tracker.addImage(ballImage2[i], 0);
    }

    try
    {
        //  画像データがダウンロードされるのを待つ。
        tracker.waitForID(0);
    }
    catch (InterruptedException e) {}

    if (tracker.isErrorID(0))
    {                           //  ダウンロードに失敗した場合
        //  画像を破棄する。
        for (int i = 0; i < ballImage1.length; i++)
        {
            ballImage1[i].flush();
            ballImage1[i] = null;
            ballImage2[i].flush();
            ballImage2[i] = null;
        }
        showStatus("Error loading image");
    }
    else
    {
        showStatus("Done.");
    }
}

waitForID()の呼び出しには注意が必要です。 このメソッドは、ダウンロードが完了する前に中断される場合があるからです。 この場合、例外InterruptedExceptionが発生するので、 これを捕捉する必要があります。 今回は一応捕捉はしますが、 その後isErrorID()でエラー判定をしているため、 捕捉しても何もしていません。

最後に、UpperHandBoardViewクラスと UpperHandBallViewクラスに画像を表示する機能を追加します。
UpperHandBoardViewクラスでは、 paintBall()に画像データを使って玉を表示する機能を追加します。

//-----------------------------------------------------------------------------
//  private void paintBall(Graphics g, int p)
//      pで指定された位置の玉を描画する。
//-----------------------------------------------------------------------------
private void paintBall(Graphics g, int p)
{
    if (app.ballImage1[0] != null)
    {                   //  画像のダウンロードに成功したとき
        switch (game.boardStatus(p))
        {
        case UpperHandGame.FIRST:
        case UpperHandGame.SECOND:
        case UpperHandGame.NUTRAL:
            //  ダウンロードした画像を表示する。
            g.drawImage(app.ballImage1[game.boardStatus(p)],
                        area[p].x, area[p].y, this);
            break;
        case UpperHandGame.MOVABLE:
            //  描画エリアの半分の大きさで背景色の円を描く。
            Color c = UpperHand.color[UpperHand.BG_COLOR];
            g.setColor(c);
            g.fillOval(area[p].x + area[p].width / 4,
                        area[p].y + area[p].height / 4,
                        area[p].width / 2, area[p].height / 2);
            break;
        }
    }
    else
    {                   //  画像のダウンロードに失敗したとき

        今まで通りの処理

    }
}

UpperHandBallViewクラスでは、 UpperHandBallView()に画像データを引き渡すために引き数を追加し、 paint()に画像データを使って玉を表示する機能を追加しました。

//-----------------------------------------------------------------------------
//  public UpperHandBallView(UpperHandGame game, int player, UpperHand app)
//      UpperHandBallViewのインスタンスを生成し、初期化を行う。
//          UpperHandGame   game      - 表示対象のゲーム
//          int             player    - 表示対象のプレーヤー
//          UpperHand       app       - ゲームを制御するオブジェクト
//-----------------------------------------------------------------------------
public UpperHandBallView(UpperHandGame game, int player, UpperHand app)
{
        ...

    //  玉の画像を設定する。
    ballImage = app.ballImage2[player];

        ...
}
//-----------------------------------------------------------------------------
//  public void paint(Graphics g)
//      現在の持ち玉を描画する。
//-----------------------------------------------------------------------------
public void paint(Graphics g)
{
        ...

    //  玉を描く。
    if (ballImage != null)
    {                       //  画像のダウンロードに成功したとき
        int p = 0;
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                if (p < game.ball(player))
                {
                    g.drawImage(ballImage, size * x + 1, size * y + 1, this);
                }
                p++;
            }
        }
    }
    else
    {                       //  画像のダウンロードに失敗したとき

        今まで通りの処理

    }
}

ここまでにできあがったもの

あなたのブラウザはJavaに対応していません。

Javaソースコード (Ver. 1.1a9)



[Next] [Prev] [Back] [Home]
Satoshi Kobayashi (koba@yk.rim.or.jp)