UpperHand Gameを作る
ゲームの表示に凝ってみる
今回は音声データと画像データを使ってゲームの表示に凝ってみましょう。
以下のファイルを使います。
音声データを扱う
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()
は
音声や画像を即座にはダウンロードしません。
ダウンロードは音声や画像が必要となるとき(再生や表示のとき)に
非同期に行われるのです。
そして、ネットワークが混んでいる場合などには、
ダウンロードに失敗することもあり得ます。
音声の場合、ダウンロードに失敗しても
音が鳴らないだけでたいした問題はありませんが、
画像の場合は表示されないとゲームができません。
そこで、
- 画像がダウンロードされるまで待つ
- ダウンロードに成功したかどうかを知る
ことが必要になります。
これを可能にするのが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ソースコード (Ver. 1.1a9)
Satoshi Kobayashi
(koba@yk.rim.or.jp)