継承・Vectorによるキャラ管理・サウンド - Javaでシューティングゲームを作ろう
目次
- Javaでシューティングゲームを作ろう
- 自機の左右移動
- 弾発射・はみ出しチェック
- 敵登場・仮想画面
- 当たり判定(敵と弾)・スコア
- 当たり判定(自機と敵)・シーン切り替え
- クラス化
- 継承・Vectorによるキャラ管理・サウンド
- パワーアップ・複数種類の敵
- ペンギンシューティング
今回の題材
- 継承
- Vectorによるキャラ管理
- サウンド
一式ダウンロード
ソースの解説
前回のプログラムは、自機・敵・弾クラスで同じような処理があり、無駄が多くありました。そこで今回は同じような処理をまとめることを考えてみます。共通部分を関数化する方法も考えられますが、せっかくオブジェクト指向を使っているので継承を使います。今回は、自機・敵・弾クラスのスーパークラスとしてキャラクタクラス(Chr)を作り、共通的なフィールドである画像・座標・大きさ、共通的なメソッドである移動・当たり判定・描画などをキャラクタクラスに記述します。もうひとつ、今回複数の敵や弾を出現させるので、それらの管理が必要になってきます。リンクリストで管理するのが良いのですが、古いJavaではリンクリストを標準クラスに持っていないので今回は古いJavaでも使えるjava.util.Vectorクラスを使います。
Vectorクラスは可変長配列クラスで、ようするに配列なのですが、サイズを増減したり、途中の要素を削除して後を詰めたりといったことが簡単にできます。
今回はVectorでキャラクタを管理するのに合わせて、オブジェクトのライフサイクルとキャラクタのライフサイクルを同期させました。つまり、オブジェクトが生まれたときにキャラクタが画面に表示され、オブジェクトが削除されたときにキャラクタが画面から消えます。この方法は速度的に少し不利なのですが、オブジェクト指向的に自然な方法だと思います。
Chrクラス
自機・敵・弾などのキャラクタのスーパークラスで、このクラス単体ではオブジェクトを生成できない抽象クラスですChrクラス フィールド
protected static Shooting7 app;
アプレットの参照を保持します。ChrクラスとChrクラスから継承したクラスで共通なのでstatic宣言を付けてクラス変数としています。protected修飾子は、自分自身と、自分から継承したクラスのオブジェクトでアクセスできるメンバを宣言します。
protected Image img;
protected int x, y;
protected int w, h;
上記の、画像・座標・大きさという属性は自機・敵・弾クラスで共通なのでスーパークラスで定義しています。
private boolean dead;
敵が弾に当たるなどして死亡したときにこのフラグがtrueになります。キャラが死亡したときはキャラ管理リストからオブジェクトが削除されるのですが、死亡した瞬間に削除するといろいろと問題があるので、とりあえずこのフラグを立てておいて、1フレームの処理が終わった時点で実際の削除を行います。Chrクラス コンストラクタ
後述するイメージトラッカーの関係で、キャラクタの画像読み込みはアプレットクラスで行い、ここでは引数としてImageオブジェクトを受け取ります。
w = img.getWidth(app);
h = img.getHeight(app);
これらのメソッドによって画像の幅、高さをフィールドに保存します。キャラクタのサイズを決め打ちしないことによって、異なるサイズのキャラクタを作っても各メソッドが正常に動くようになります。
abstract void move();
移動メソッドです。抽象メソッドとして記述してあり、継承したクラスでかならずメソッド内容を記述しなければいけません(記述しなければエラーになります)。Chrクラス checkHitメソッド
引数のChrオブジェクトと当たり判定を行い、当たったらtrue、外れたらfalseを返すメソッドです。Chrクラスのサブクラスでこのメソッドを共通で使います。Chrクラス drawメソッド
キャラクタ描画メソッドです。Chrクラスのサブクラスでこのメソッドを共通で使います。Jikiクラス
class Jiki extends Chr {
extendsによりChrクラスを継承しています。継承することにより、Chrクラスで定義したフィールドやメソッドを自機クラスでそのまま使うことができます。Jikiクラス フィールド
private int tamaIntCount;
今回、弾を連射できるのですが、常に弾を撃てるようにすると弾が連なって出てしまいます。そこでこの変数をカウンタとして使い、0になるまでは次の弾を撃てないようにします。Jikiクラス コンストラクタ
super(app.imgJiki);
superはスーパークラスのコンストラクタを呼び出す構文です。super.メソッド名とするとスーパークラスのメソッドを呼び出します。アプレットクラスで読み込んだ自機画像が入ったImageオブジェクトをChrクラスのコンストラクタに引数として渡しています。
x = (app.getw() - w) / 2;
y = app.geth() - h - 16;
Chrクラスの幅、高さフィールドを使って、自機画像の大きさが変わっても同じ初期位置に表示できるようにしています。Jikiクラス moveメソッド
app.addList(new Tama(x + w / 2, y, 0, -8));
アプレットクラスのキャラ管理リストに弾を新しく追加します。
app.sndShot.play();
弾ショット音を再生します。Jikiクラス checkHitメソッド
アプレットクラスでは、キャラクタ全ての組み合わせにおいて当たり判定を呼び出しているので、それぞれのクラスで無駄な当たり判定は省かなければなりません。
if (t instanceof Teki) {
instanceof演算子は、オブジェクトが指定したクラスのインスタンスならば(上記の場合はtがTekiクラスのインスタンスならば)trueを返します。自機が当たり判定をするのは敵のみなので、ここで対象が敵かどうかをチェックしています。Tekiクラス checkHitメソッド
敵は弾とのみ当たり判定をします。Tamaクラス checkHitメソッド
弾はだれとも当たり判定を行わないので何もせずに終了します。Shooting7クラス フィールド
private Vector clist, clistTmp;
キャラクタのリスト管理をするために可変長配列クラスであるVectorクラスを使用します。clistが実際のリスト、clistTmpが仮リストです。キャラクタを追加するときはまずclistTmpに追加し、毎フレームの最後にまとめてclistに移動します。
private MediaTracker mt;
メディアトラッカーはImageオブジェクトの状態を監視するオブジェクトです。Javaでは画像ファイルをImageオブジェクトに読み込んだだけではメモリに読み込まれません。そこでメディアトラッカーにImageオブジェクトを登録してメモリに読み込まれるまでを監視します。
private int tekiInterval;
敵の出現間隔です。ゲームが進むにつれこの間隔が短くなり難しくなります。
private int tekiIntCount;
敵の出現間隔カウンタです。毎フレームごとにデクリメントされます。
Image imgJiki, imgTeki, imgTama;
自機・敵・弾それぞれの画像が入るImageオブジェクトです。
AudioClip sndShot, sndBang;
弾ショット音、爆発音のサウンドが入るAudioClipオブジェクトです。読み込めるファイルはau形式(8000Hz,μ-law,8bit,Mono)のみです。Javaの新しいバージョンではwaveやmidiも読み込めるようです。Shooting7クラス initメソッド
scene = SCENE_INIT;
タイトル画面に移行する前に、画像ファイルが読み込み終わるまで待つ初期化画面に移行します。
Chr.app = this;
自機・敵・弾クラス共通で使うアプレットの参照をここで設定しています。クラス変数なのでどのクラスからも共通でアクセスすることができます。
mt.addImage(imgJiki, 0);
メディアトラッカーに自機のImageオブジェクトを登録します。
sndShot = getAudioClip(getDocumentBase(), "shot.au");
sndBang = getAudioClip(getDocumentBase(), "bang.au");
サウンドファイルをAudioClipオブジェクトに読み込みます。現在のところ、メディアトラッカーでサウンドの読み込みを監視することはできないようです。Shooting7クラス gameInitメソッド
画像が読み込み終わるまで、ロード中のメッセージを出す画面です。
if (mt.statusAll(true) == MediaTracker.COMPLETE) {
画像の読み込みが全て完了したらtrueになります。Shooting7クラス readyメソッド
initメソッドはプログラム開始時だけ呼ばれますが、このメソッドはプログラム開始時と、ゲームオーバーになってリトライしたときに呼ばれます。ここで主に変数の初期化などを行います。
clist.setSize(0);
clistTmp.setSize(0);
キャラクタ管理リストのサイズを0にしてクリアしています。
addList(new Jiki());
キャラクタ管理リストに自機を追加しています。自機は最初に一度だけ追加されて、削除されるときはゲームオーバーのときです。Shooting7クラス gameMainメソッド
for (i = 0; i < clist.size(); i++) {
変数iをループカウンタとしてキャラクタ管理リストの要素を最初から最後までアクセスします。Vectorクラスのsizeメソッドは配列のサイズを得るメソッドです。
c = (Chr)(clist.elementAt(i));
c.move();
elementAt(i)メソッドはi番目の要素を取得するメソッドです。i番目の要素であるChrオブジェクトの参照をcに代入し、移動メソッドを呼び出しています。
for (i = 0; i < clist.size(); i++) {
c = (Chr)(clist.elementAt(i));
for (j = 0; j < clist.size(); j++) {
c2 = (Chr)(clist.elementAt(j));
c.checkHit(c2);
}
}
キャラクタ管理リストに登録してあるキャラクタ全てについて総当りで当たり判定を行っています。当たり判定をしても意味がない組み合わせはそれぞれのクラス内で排除しています。
if (c.isDead()) clist.removeElementAt(i);
それぞれのキャラクタについて死亡フラグが立っていたら管理リストから削除します。