解説してきたことを応用して、ゲームを作ってみましょう。よくあるゲームではありますが、もぐらたたきゲームです。ゲームだと、複雑な動きが要求されることもあります。複数のプログラムを並行して実行させるスレッドという機能を利用すれば、複雑な要求もシンプルにプログラミングすることが可能になります。
もぐらたたきには、当然のこととしてもぐらの絵が欠かせません。JavaではJPEGやGIF画像なら、drawImageメソッドでコンポーネント内に描くことができます。そこで、もぐらの絵をGIF画像で用意しておくことにします。64×64ドットの以下のようなカラーGIF画像を用意しました。ファイル名はmogura.gif(URLは、http://msyk.net/keio/JavaBook/eclipse-indigo/mogura.gif")です。もちろん、自分でグラフィックスソフトで自分で描いてもいいでしょう。
もぐらがサングラスをかけている…というのは、きっと筆者が子供のころに見たどなたかのマンガの影響が大きくあるかと思いますが、ご了承いただくことにしたいと思います。なお、もぐらは、ここでは黄色く塗りつぶしてありますが、モグラの外側は、適当な色で塗りつぶして透過色として設定してあります。Javaの描画処理では、GIFの透過色はきちんと認識されて、透過色となります。
まず、プロジェクトを新たに作成します。今週は「Game」という名前のプロジェクトを作成することにします。そのプロジェクトに、アプリケーションを作ります。実際の電卓のクラスは、javax.swing.JFrameをスパークラスとした「Mogura」クラスとします。このMoguraクラスのパッケージは「mogura」とし、「public static void main(String[] args)」のチェックを入れておきます。
モグラのGIF画像を表示することは、グラフィックス環境に対してdrawImageを使えばできます。しかし、消すことまでを考えたとき、ウインドウに直接描いてしまうといろいろ面倒です。そこで、AWTに用意されているCanvasというコンポーネントを利用します。Canvasはボタンやテキストフィールドと同列に見ることができるコンポーネントです。Canvasはプログラムでの描画結果やグラフィックスファイルの内容を、1つのオブジェクトとして扱う場合に便利なコンポーネントです。しかし、ボタンのように単独で使うというよりも、Canvasクラスを拡張して独自の描画機能を組み込み、特定のグラフィックスを表示するようなコンポーネントとして使うのが扱いやすいでしょう。
Mogura.javaに次のようなプログラムを入力して、実行してみてください。単に、ウインドウにもぐらの絵が出てくるだけだとは思いますが、基本的なところから少しずつ作っていきましょう。
package mogura;
import java.awt.*;
import java.awt.image.*;
import javax.swing.JFrame;
import javax.imageio.*;
import java.net.*;
import java.io.*;
public class Mogura extends JFrame {
private static final long serialVersionUID = 1L;
private BufferedImage moguraImage;
public Mogura() {
try {
URL url = new URL("http://msyk.net/keio/JavaBook/eclipse-indigo/mogura.gif");
moguraImage = ImageIO.read(url);
} catch (IOException e) {
throw new RuntimeException(e);
}
this.getContentPane().add(new ImageCanvas(moguraImage));
this.setBounds(0 ,0, 400, 400);
this.setVisible(true);
}
public static void main(String[] args) {
new Mogura();
}
}
class ImageCanvas extends Canvas
{
private static final long serialVersionUID = 1L;
Image presenImage;
int w, h;
public ImageCanvas(Image img) {
presenImage = img;
w = presenImage.getWidth(this);
h = presenImage.getHeight(this);
this.setSize(w, h);
}
public void paint(Graphics g) {
g.drawImage(presenImage, 0, 0, this);
}
}
プログラムを順番に見ていきましょう。まず、Moguraクラスはがアプリケーションとして機能するように、public static void mainメソッドがあります。
Moguraクラスのコンストラクタ(Mogura()というメソッド)では、最初にURLを指定して画像を取り出しています。文字列で与えたURLから、URLクラスを生成し、さらにreadメソッドで読み込んでおけば、描画可能な画像として扱えるようになります。
ここで、tryやcatchというのがありますが、これは「例外処理」という手法です。Javaでは、あるプログラム内でそれ以上の実行ができないような問題が起こったなどの場合、「例外」というメカニズムが働いて、その「例外」が呼び出し元に伝達されます。readメソッドの場合、ネットワークの切断など画像の取り込み処理が何からの理由で中断されることもあります。そのような処理が起こって取り込みが行われなかったような場合に、「例外」が発生します。複雑なプログラムの場合、あちこちで異なる理由でエラーが発生し処理を中断する必要があります。そのようなとき、まとめてtry〜catchで囲んでしまって「やりたいこと」をともかく書き、エラーは別途記述するということができるのです。エラー処理を確実に組み込むということが言語の仕様にまで組み込まれているのですが、ここでは「例外」が発生しても、そのエラーメッセージを出すという処理しか組み込んでいません。
ImageCanvasクラスはCanvasクラスを拡張して定義しています。つまり、画像を表示するキャンバスという意味なのですが、まずはImageCanvasのコンストラクタを見てください。コンストラクタではImageオブジェクトへの参照を引数に取っており、その値を変数presenImageに残してあります。これは、描画したい画像をコンストラクタの引数として与えるということを意味します。コンストラクタではさらに、画像の大きさを求め、作成したImageCanvasオブジェクトの大きさを画像のサイズとぴったりに合わせます。
Canvasクラスでは、paintメソッドの記述ができます。Canvasあるいはそれを拡張したクラスでは、paintメソッドを定義することで、コンポーネントの描画が必要なときに自動的に呼び出されます。したがって、paintメソッド中にdrawImageメソッドを記述して、コンストラクタの引数に指定した画像を表示するようにしておけば、画像が表示されるはずです。
Moguraクラスのコンストラクタの最後に、newキーワードでImageCanvasオブジェクトが生成されています。引数には、もぐらのGIF画像が指定されています。そして、生成したImageCanvasは、addメソッドにより、ウインドウの中に表示されます。ボタンなどと同じく、ImageCanvasもgetContentPaneメソッドの値に対してaddメソッドで追加します。ここではBorderLayoutなので、適当な位置に表示されますが、まずは表示されることを確かめたいので、これで十分でしょう。
ここでは複数のクラスが1つのファイルに書かれています。1つのソースファイルでは、1つのpublicなクラスしか定義できません。2つ以上のpublicなクラスを定義するには、別々のファイルに分ける必要があります。publicというのはあらゆるJavaのソフトウェアで利用できることを意味するのですが、publicを付けないと、別のパッケージからのアクセスができないなどの制約があります。
ただし、自分でアプリケーションを作って行く場合、いくつもクラスを作る場合は、publicを付けなくても同一ファイルは同一パッケージと見てよいので、気にしないでいくつもクラスを1つのファイルに定義してかまいません。
続いて、もぐらが地面からじわじわとせり出てくるというアニメーションを行うようにしましょう。方針としては「画像が表示できるコンポーネント」のImageCanvasを拡張して、「画像が下からせり出すアニメーションを行うコンポーネント」であるAnimatedCanvasを定義します。
アニメーションとなると、表示した絵を動かすという見方もできるのですが、Javaでの1つの方法は、ともかく1コマ1コマ描画します。たとえば、20枚ほどのGIF画像を用意しておき、それらを一定時間ごとに画面に繰り返して描画することで、アニメーションに見えるようにするという具合です。
そうなると、一定時間間隔というコントロールがしたくなりますし、ゆくゆくは、よくあるもぐらたたきゲームのように、もぐらがランダムな位置にランダムな間隔で登場しては消えるというようにしたいわけです。つまり、複数のアニメーションが並行して行われるような状況です。このような動作を実現するためには、「スレッド」という、プログラムを並列実行するメカニズムを利用するのがいちばん手軽でしょう。並列で実行させるというと難しそうですが、意外にシンプルです。Mogura.javaに手を加えて、以下のようなプログラムに変更します。
package mogura;
import java.awt.*;
import java.awt.image.*;
import javax.swing.JFrame;
import javax.imageio.*;
import java.net.*;
import java.io.*;
public class Mogura extends JFrame {
private static final long serialVersionUID = 1L;
private BufferedImage moguraImage;
public Mogura() {
try {
URL url = new URL("http://msyk.net/keio/JavaBook/eclipse-indigo/mogura.gif");
moguraImage = ImageIO.read(url);
} catch (IOException e) {
throw new RuntimeException(e);
}
this.getContentPane().setLayout(null); //この行を追加
this.getContentPane().add(new AnimatedCanvas(moguraImage)); //この行を変更
this.setBounds(0 ,0, 400, 400);
this.setVisible(true);
}
public static void main(String[] args) {
new Mogura();
}
}
class ImageCanvas extends Canvas //このクラスはそのまま
{
private static final long serialVersionUID = 1L;
Image presenImage;
int w, h;
public ImageCanvas(Image img) {
presenImage = img;
w = presenImage.getWidth(this);
h = presenImage.getHeight(this);
this.setSize(w, h);
}
public void paint(Graphics g) {
g.drawImage(presenImage, 0, 0, this);
}
}
class AnimatedCanvas extends ImageCanvas implements Runnable //このクラスを追加する
{
private static final long serialVersionUID = 1L;
Thread myTh;
Color backColor = Color.pink;
float visibleArea;
public AnimatedCanvas(Image img) {
super(img);
myTh = new Thread(this);
myTh.start();
}
public void run() {
Graphics cg = this.getGraphics();
cg.setColor(backColor);
for(visibleArea = 0; visibleArea <= 1.0; visibleArea += 0.1) {
cg.fillRect(0,0,w,h);
cg.drawImage(presenImage, 0, (int)(h*(1-visibleArea)), this);
try {
Thread.sleep(1000);
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
public void paint(Graphics g) {}
}
スレッドは、プログラムを並行に実行させるメカニズムでもありますが、実行する1つ1つのプログラムも「スレッド」と呼ばれます。Javaでは言語の基本機能としてスレッド処理が組み込まれており、Threadというクラスが用意されています。
スレッドとして動作可能なプログラムを作る1つの方法として、スレッドにしたいクラスに、Runnableインタフェースを組み込むという方法があります。すると、そのクラスにはrunメソッドを定義しないといけなくなります。ここで少しややこしいのですが、スレッドを立ち上げるには、まずはThreadクラスのインスタンスを生成します。ただし、ThreadのコンストラクタにはRunnableを組み込んだクラス、つまりスレッドにしたいオブジェクトへの参照を指定する必要があります。そして、Threadクラスのstartメソッドを呼び出すと、システムの側からrunメソッドが呼び出されます。もし、複数のスレッドがシステムの管理下にあれば、それらが適当な順序で切り替わりながら、マルチタスク状態で動作するわけです。つまり、同時並行動作したいプログラムをrunメソッド中に作成しておけばよいわけです。
では、AnimatedCanvasクラスではどのようにやっているのでしょうか。まずは、コンストラクタを見てください。superはこの場合、ImageCanvasのコンストラクタを呼び出すことを意味しています。つまり、ImageCanvasのコンストラクタの処理がまずは行われるということです。そして、新たに、自分自身を引数に指定して、Threadクラスのインスタンスが生成されています。これで、AnimatedCanvasの1つのインスタンスが、スレッドの1つに登録されたと考えればよいでしょう。そして、スレッドに対してstartメソッドを実行して、スレッドが開始されます。
スレッドが開始されると、runメソッドが呼び出されます。runメソッドが繰り返し呼び出されるということではなく、1度だけ呼び出されます。したがって、runメソッド内で、もぐらがせり出てくるアニメーションを作成しなければなりません。そのために、もぐらの画像をdrawImageで描くのですが、描く位置を下から上に移動させることによって、あたかもアニメーションしているかのように見せるということを行っています。念のため、g.fillRect(0,0,w,h);によって、まずCanvas全体を消去してから、drawImageで画像を描いています。左上の縦座標位置を、下から上に徐々に変動させるために、forステートメントを使って、visibleArea変数を、0.0、0.1、0.2…1.0と変化させ、その値にコンポーネントの高さをかけ算するなどして、画像の位置を計算しています。なお、そうすると、コンポーネントの下にはみ出すのではないかと心配するかもしれませんが、コンポーネントの外部に描いた内容は画面には出てきません。だから、表示範囲だけマスクして…というややこしいことを考えなくても、こうした処理は自動的になされるので、コンポーネントの外部にはみ出ることはありません。
このままだと、だだーっと画像表示して終わってしまいますが、途中に、Threadクラスのスタティックメソッドであるsleepを利用して、スレッドの動作を一時的にとめています。sleepの引数の時間(単位はミリ秒)の間だけ、このスレッドの動作を中断します。つまり、若干の誤差はあるかとは思いますが、この場合だと100ミリ秒ごとに、もぐらの画像を順番に上にずらしながら描くことで、もぐらがせり上がってくるように見えると言う具合です。このspleepメソッドの引数を1000にしたら、1秒ごとに書き換えが行われるため、アニメーションはゆっくりになります。試してみましょう。sleepメソッドは例外処理を組み込まないといけません。
なお、AnimatedCanvasクラスの最後に、中身が何もないpaintメソッドがあります。この定義がないと、ImageCanvasクラスのpaintメソッドの処理が有効になり、AnimatedCanvasクラスを生成した直後にpaintメソッドが呼び出されてしまうようです。その結果、一瞬もぐらが表示されてからアニメーションが始まるという具合になってしまうので、ImageCanvasのpaintメソッド処理はキャンセルしたいわけです。そのようなときに、もとのImageCanvasクラスを修正するというのも1つの方法ですが、同名のメソッドを定義して、中身を何も定義しないようにすればOKです。こうすることで、もとになったクラスの定義を何も変えないで、新しいクラスを定義することができるわけです。
さらに続けて、もぐらがせり出てきたあとにもぐらを消してみましょう。消すのは簡単そうに見えるかもしれませんが、具体的にはウインドウに登録したコンポーネントを取り除くという作業が必要になり、ちょっとやっかいです。この作業をどこに組み込むかがポイントになります。ここでは、AnimatedCanvasクラス、つまりモグラの絵を動かしているクラスで、モグラの絵が全部表示された後に、自分自身をウインドウから取り除くようにしました。以下のように1行だけ追加してみてください。実行してみれば、アニメーションのあと、ピンク色のパネルが消えているのがわかるはずです。
public void run() {
Graphics cg = this.getGraphics();
cg.setColor(backColor);
for(visibleArea = 0; visibleArea <= 1.0; visibleArea += 0.1) {
cg.fillRect(0,0,w,h);
cg.drawImage(presenImage, 0, (int)(h*(1-visibleArea)), this);
try {
Thread.sleep(1000);
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
this.getParent().remove(this); //この行を追加する
}
これまで、Canvas→ImageCanvas→AnimatedCanvasとクラスをどんどんと発展させてきました。こうした作りができるのが、オブジェクト指向プログラミングの特徴で、たとえば、AnimatedCanvasと少し仕様の違うようなクラスを作りたいときには、別途AnimatedCanvasから拡張して定義するということを行えばよいのです。ただ、今回のもぐらの場合は、せり出てくるもぐらは1種類ですので、ここで、1つのクラスにまとめることにします。
クリックの受け付けは、ボタンはアクションイベントでした。しかしながら、Canvasではアクションイベント処理は組み込まれていません。かわりに、マウスイベントというイベントを扱います。新しいイベントではありますが、アクションイベントと使い方は同じです。ポイントとしては次のとおりです。
public void mouseClicked(MouseEvent e) { } //クリック時
public void mousePressed(MouseEvent e) { } //マウスボタンを押したとき
public void mouseReleased(MouseEvent e) { } //マウスボタンを離したとき
public void mouseEntered(MouseEvent e) { } //マウスポインタがコンポーネント内に//入ったとき
public void mouseExited(MouseEvent e) { } //マウスポインタがコンポーネント外に//出たとき
つまり、CanvasというコンポーネントにMouseListenerを組み込むことで、マウスクリックしたときに規定のメソッドが呼び出されるようになるということです。ただ、5つのメソッドを定義しますが、実際に機能を組み込む必要があるのは、もぐらたたきゲームの場合はmouseClickedだけです。あとはメソッドの定義だけはしておく必要がありますが、メソッドの処理は何も記述しないでおきます。
実際にマウスでクリックしたとき、たとえば、もぐらはキャンバスの下半分だけしか表示されていないかもしれません。そのときに上半分の位置をクリックしても、得点にしたくはなく、むしろミスタッチでマイナスにしたいと考えます。ここで、mouseClickメソッドのMouseEvent型の引数に対して、getX、getYというメソッドを利用すると、クリックした座標位置を得ることができます。この座標位置は、AnimatedCanvasErCl、すなわちCanvasの左上を原点とした座標です。そこで、グラフィックスが下からどこまで表示しているかと、クリックした位置とを突き合わせることで、もぐらをクリックしたか、それとももぐらの外をクリックしたかを判断するようにします。もっとも、もぐらのグラフィックス内でも全面がもぐらではなく、もぐらの外側の部分もあります。しかしながら、もぐらのグラフィックスの中で、もぐら画像の部分をクリックしたかどうかを判断する処理は非常に難しくなるので、ここでのプログラムでは問題を単純化して、Canvas内のある位置より下はOK、それより上はだめということにしておきます。
ここで、AnimatedCanvasコンポーネントをクリックすると、そのコンポーネントが消えるようにしたいと考えます。考え方としては、クリックが行われるとスレッドの処理を即座に終える、つまりrunメソッドを終了させるという具合です。そこで、mouseClickedメソッドで、クリックしたことを変数isClickedに記録します。スレッドで実行されるrunメソッドの中では、そのisClickedを常時監視しながら、その値がtrueになったら、アニメーションを途中でやめて、コンポーネントを取り除き、そしてrunメソッドを終了させます。こうした手法をプログラムに組み込むことにします。
クリックを受け付けるAnimatedCanvasはここまでの方針で作ります。そのAnimatedCanvasをランダムな時間間隔で、ランダムな位置にいくつか表示するという作業が必要になります。こうした作業を行うクラスMoguraProducerを作ることにします。
まず、ランダムな時間間隔、つまり、時間を待って作業をするという処理を組み込む場合、やはりスレッドを使うのがやりやすくなります。またある時間何もしないというプログラムを作るのに、sleepメソッドを使えば簡単にできますし、それによる他のプログラム部分への影響もあまりありません。自分で時間を測るような手法ではプログラムが大変な上に、ほかのプログラム部分へ悪影響を与えてしまうかもしれません。ここは、確実なスレッドの機能を使うことにします。したがって、MoguraProducerには、Runnableインタフェースをインプリメントすることになり、runメソッドの定義が必要です。また、コンストラクタでスレッドを生成するというのも、もぐらの場合と同じです。
そして、runメソッド中で、ランダムな時間間隔をおいてAnimatedCanvasを生成すればよいのです。AnimatedCanvasさえ生成すれば、アニメーションの作業はそちらで定義したプログラムで行われます。もぐらが消える前に、次のAnimatedCanvas生成があっても、並列に処理されるスレッドとして機能しているために、複数のもぐらのアニメーションが同時に行われることになります。スレッドを利用することで、こうした一見複雑そうな処理が実に簡単に行われるということになります。
また、AnimatedCanvasコンポーネントをランダムな位置に配置するのですから、ウインドウのレイアウト機能はnull、すなわち使用しないようにしておき、座標位置を自分で計算するのがいちばん手軽でしょう。
やはりゲームですので、何かしらのスコアは出てほしいものです。そこで、テキストフィールドを用いて、そこでスコアのカウントを行うことにします。もちろん、TextFieldクラスのコンポーネントをウインドウに配置するのですが、そこに表示する得点をアップさせたりダウンさせたりしなければなりません。それが、マウスのクリックのときに行われることになります。得点なので、アップやダウンは数値で処理しますが、テキストフィールドの設定や取得はテキストで行われます。まず、テキストと数値の変換方法を整理しておきましょう。
Stringをint型に変換するには、IntegerクラスのクラスメソッドであるparseIntを利用するのが1つの方法です。たとえば、
Integer.parseInt("12")
というのは、12というint型のデータになります。Integerクラスはjava.langパッケージにあるため、importでライブラリの読み込みをしなくても利用できます。
一方、int型をStringに変換するには、StringクラスのvalueOfというクラスメソッドを利用します。つまり、
String.valueOf(12)
というのが、"12"という文字列になるという具合です。
テキストフィールドからのテキストの取り出しはgetText、テキストの設定はsetTextメソッドを使えばよいでしょう。これらの処理を適切に組み合わせて、もぐらを正しくクリックしたら、得点をアップさせます。ここで、たとえば得点をインスタンス変数で管理するという手法もありますが、得点そのものは、テキストフィールドに表示するテキストそのもので記録するという方法でプログラムを作ることにします。
つまり、もぐらをクリックすると、テキストフィールドからテキストを取り出し、それをint型に変換して1を加え、さらにそれをテキストに変換して、テキストフィールドに書き戻すという手順を踏むことになります。
本来はこうした機能を1つ1つ組み込みながら、プログラムがどう成長していくかを示したいところではありますが、長くなってしまうので、まとめて示したいと思います。
今回のもぐらたたきは、1種類のもぐらしか出てきません。それをオブジェクトごとに管理するのもあまり効率的な方法ではないので、読み出したもぐらのGIF画像も共有化します。MoguraクラスにmoguraImageというインスタンス変数を用意して、ウインドウ内、つまりプログラムのどこにいても、もぐらのGIF画像を簡単に利用できるようにしておくことにします。もぐらの画像のサイズや、ウインドウのサイズなど、プログラムを通して同じ値になるものは、Moguraクラスのインスタンス変数として用意しておき、initメソッド内で一度求めておいて、あとは変数を参照して利用するようにも修正しています。ただ、いろいろなもぐらや、もぐらでないキャラクタも時々出てくるようなゲームにいずれはしたいというような場合には、やはりAnimatedCanvasクラスレベルで、それぞれ異なる画像を所持できるように工夫をしておく必要があるでしょう。
得点をカウントするテキストフィールドも、Moguraクラスのインスタンス変数scで管理します。そうすれば、スコアのデータを、クラス内のどこからでも簡単に利用できます。この場合、AnimatedCanvasErClクラスのmouseClickedで、クリックした場所に応じた得点の増減がまず必要です。さらに、ウインドウ上のもぐらも何もないところをクリックしたときに得点を減らしたいので、 Moguraクラス自体にもMouseListenerをインプリメントして、ウインドウ自体もクリックに応答するようにします。ウインドウにクリックのマウスイベントが発生した場合は、無条件に得点を減らすだけでかまわないでしょう。
こうして作成したのが次のリストです。これまでと大幅に変わっていますが、修正をしてください。重複しますが、細かくコメントを入れてありますので、参考にしてください。
package mogura;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.JFrame;
import javax.imageio.*;
import java.net.*;
import java.io.*;
import java.util.*;
public class Mogura extends JFrame implements MouseListener {
private static final long serialVersionUID = 1L;
private BufferedImage moguraImage;
int w, h; //もぐら画像の高さと幅
private TextField sc = new TextField("0"); //得点のテキストフィールド。最初は0点が入るよう//に、インスタンスを生成しておく
private int fieldW = 500, fieldH = 400; //ウインドウの幅と高さ
public Mogura() {
try {
URL url = new URL("http://msyk.net/keio/JavaBook/eclipse-indigo/mogura.gif"); //もぐらの画像を指定
moguraImage = ImageIO.read(url); //画像を読み出す
} catch (IOException e) {
throw new RuntimeException(e);
}
w = moguraImage.getWidth(this); //画像のサイズをメンバ変数に記録する
h = moguraImage.getHeight(this);
this.addMouseListener(this); //マウス操作を受け付ける
this.getContentPane().setLayout(null); //レイアウト機能はなし
sc.setBounds(0 ,0, 50, 24); //スコア用のテキストフィールドの場所と大きさを指定する
this.getContentPane().add(sc); //スコア用のテキストフィールドをウインドウに追加
this.setBounds(0 ,0, fieldW, fieldH); //ウインドウの大きさを指定する
this.setVisible(true); //ウインドウを表示
new MoguraProducer(); //MoguraProducerクラスを生成して実行
}
public void mouseClicked(MouseEvent e) { //ウインドウをクリックしたとき
int currentScore = Integer.parseInt(sc.getText()); //現在の得点を取得
sc.setText(String.valueOf(currentScore - 1)); //1を引いた得点をテキストフィールドに戻す
System.out.println("Fail!"); //コンソールにFail!と表示
}
/* 以下はMouseListenerをインプリメントしているので、定義だけは必要 */
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
class AnimatedCanvas extends Canvas implements Runnable,MouseListener
{
private static final long serialVersionUID = 1L;
Container parent;
Thread myTh;
Color backColor = Color.pink;
float visibleArea;
boolean isClicked = false;
public AnimatedCanvas() {
//コンストラクタにsuperがない場合、この場合、new Canvas()が呼び出されたのと同等のことが自動的に行われる
this.setSize(w, h); //コンポーネントをもぐらGIFのサイズに設定する
Random r = new Random(); //乱数生成機能を呼び出す
int dispX = (int)(r.nextFloat() * (fieldW - w)); //横座標を乱数で指定
int dispY = (int)(r.nextFloat() * (fieldH - h)); //縦座標を乱数で指定
this.setLocation(dispX, dispY); //乱数で指定した位置に配置する
Mogura.this.getContentPane().add(this); //このコンポーネントをウインドウに追加する
this.addMouseListener(this); //マウスイベントに対応するようにする
myTh = new Thread(this); //このコンポーネントを管理するスレッドを生成
myTh.start(); //スレッドを開始する
}
public void run() { //スレッドの実行により呼び出されるメソッド
Graphics g = getGraphics(); //コンポーネントのGraphicsを取得する
if(g != null) {
for(visibleArea = 0; visibleArea < 1.0; visibleArea += 0.1) { //変数visibleAreaを0から1まで0.1ずつ増加させる
g.setColor(backColor); //背景色を設定
g.fillRect(0,0,w,h); //背景色でまずは塗りつぶしてクリアする
g.drawImage(moguraImage, 0, (int)(h*(1-visibleArea)), this);
//もぐらの画像を描くが、visibleAreaに応じてコンポーネント下部に一部分だけが描かれる
if(isClicked) { //クリックしたのなら
System.out.println("Click!"); //Javaコンソールにメッセージ
break; //繰り返しを中止する
}
try {
Thread.sleep(100); //100ミリ秒、動作を停止する(早すぎてゲームにならないのなら、この数値を大きめに)
}
catch(Exception e) { //Thread.sleepには例外を組み込む必要//がある
System.out.println(e.getMessage());
}
}
}else{
System.out.println("g is null."); //Graphicsがnullならエラーメッセージを出しておく
}
Mogura.this.remove(this); //このコンポーネントをウインドウから取り除く。Mogura.thisがウインドウを示す
}
public void mouseClicked(MouseEvent e) { //マウスでクリックされたとき
int currentScore = Integer.parseInt(sc.getText()); //テキストフィールドの現在の得点を取得
if(e.getY() > (int)(h*(1-visibleArea))) //クリックした位置がグラフィックス描画領域内なら
sc.setText(String.valueOf(currentScore + 1)); //得点を1だけ加えてテキストフィールドに書き戻す
else //クリックした位置がグラフィックス描画領域外なら
sc.setText(String.valueOf(currentScore - 1)); //得点から1を引き、テキストフィールドに書き戻す
isClicked = true; //クリックしたことを示す変数をセット
System.out.println("Mouse Event!");
}
/* 以下はMouseListenerをインプリメントしているので、定義だけは必要 */
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
}
class MoguraProducer implements Runnable //もぐらをどんどん作成するスレッド
{
Thread myTh; //スレッドの管理
int maxMogura = 30; //もぐらの数
public MoguraProducer() {
myTh = new Thread(this); //スレッドを生成して
myTh.start(); //実行する
}
public void run() {
AnimatedCanvas popMogura;
Random r = new Random(); //乱数を初期化
for(int i=0 ; i < maxMogura ; i++) { //指定した数だけもぐらを生成する
popMogura = new AnimatedCanvas(); //もぐらを生成
try {
Thread.sleep((long)(2000 * r.nextFloat()));
//乱数で指定した時間だけ待つ。0〜2000のいずれかの値がsleepの引数になる
}
catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
}
public static void main(String[] args) {
// TODO 自動生成されたメソッド・スタブ
new Mogura();
}
}
実行してみましょう。一生懸命もぐらをクリックしてみてください。クリックしたつもりでもだめな場合には、もぐらがせり出てくる時間を長くして、ゲームをしてみてください。稼動させるマシンにもよるのですが、ちょっとクリックの取得がスムーズでないような気がしますが、ゲームなのでそんなものだと思っておけばいいでしょう。得点が変化したり、コンソールにprintlnで書き出したメッセージが表示されるのを見てください。
実行結果をみていると、ときどきもぐらが欠けて表示されますが、これはAnimatedCanvasErClコンポーネントが長方形で描かれるため、コンポーネントの背後に隠れてしまっている別のコンポーネントの一部が表示されないだけです。少しかっこうが悪いですが、AWTのコンポーネントを使ってシンプルにプログラムをまとめるため、このあたりもとりあえず目をつむっておくことにします。