前回の「携帯Javaゲームは実装の仕方とスレッドに注意が必要」からケータイJava(本連載では、携帯電話/PHS/スマートフォンなどの端末をまとめて「ケータイ」と表記します)で動作するゲームの実装を題材に、プログラミング手法を解説してきました。今回は「例外」と残りの実装について説明します。
実装の説明が今回は少し駆け足になりますが、最後に前回の分も含めて機能追加に対する変更個所の一覧を用意したので、それを見てどういうふうに実装しているのかをじっくり研究してみてください。
前回、自動落下まで実装しましたが、放っておくと例外が発生してプログラムが止まってしまいました。「例外」というものは何なのか、どうして例外が発生するのか、なぜプログラムが止まってしまったのかを順を追って説明します。
「例外」とは、言葉の意味では「普通の例からはずれていること。原則にあてはまらないこと。また、そういうもの(goo辞書から引用)」です。
Javaの例外は「JavaプログラムがJava言語の意味制約に違反するとき、Java VMがこのエラーをJavaプログラムに例外として通知する」と定義されています。分かりやすい例を挙げてみましょう。
前回出た例外は、配列のサイズを超えて要素にアクセスしようとしたの(ArrayIndexOutOfBoundsException)が原因です。
Javaには、例外のクラスの種類がたくさんありますが、大別すると「Exceptionクラスから派生しているかRuntimeExceptionクラスから派生しているか」の2種類に分類できます。RuntimeExceptionもExceptionから派生しているのですが、RuntimeExceptionだけはJava言語仕様で特別扱いされています。
● Exceptionクラスから派生した「検査例外」」
例えば、ThreadクラスのsleepメソッドはInterruptedExceptionを発生させる可能性があることがメソッド宣言のthrows節で宣言されています。
このように、throws節で書かれている例外を「検査例外」と呼び、呼び出す際にはtry文で囲まなければなりません。
void sleep(long millis) { |
Exceptionから派生している例外は、throws節とtry文を必ず使用しなければならない決まりになっています。
● RuntimeExceptionクラスから派生した「非検査例外」」
一方、RuntimeExceptionの場合はthrows節が必要なく、try文で必ず囲む必要もありません。例えば、引数の文字列から新しい文字列を生成するStringクラスのコンストラクタにはthrows節はありません。
しかし、引数のvalueがnullだと、NullPointerExceptionが発生します(NullPointerExceptionはRuntimeExceptionから派生しています)。
RuntimeExceptionを含むその派生クラスは「非検査例外」と呼びます。非検査例外は例外が発生する可能性があってもthrows節とtry文を省略できるということを覚えておいてください。
非検査例外を発生させる可能性があるメソッドの呼び出しはtry文で囲む必要がないのですが、そのとき例外が発生すると、例外が呼び出し元に伝播して、そのスレッドが終了してしまいます。
前回の自動落下が止まってしまったのは、自動落下を行っていたスレッドが終了したためです。プログラムが終了したわけではないのですが、スレッドが終了してしまっているため、もはや再描画などは行われません。
配列サイズを超えて要素にアクセスした場合、Javaなら例外が発生しますが、プログラミング言語によってはそのままアクセスできてしまい、その際の動作は不定であったり、アクセスしたことによってプログラムが即時終了したりするものもあります。
そうしたプログラミング言語と比較すると、例外が通知され復旧処理を施せるJavaはプログラミングしやすい言語だといえるでしょう。ですが、配列にアクセスする個所すべてをtry文で囲って、例外が発生した際の処理を記述するのは現実的ではありません。
例外が発生しないようにプログラムし、try文で囲むのは必要最小限にとどめるのが理想的なプログラミングのスタイルです。
DoJaとMIDPのキーイベントの取得方法は第6回の「テンプレートで学ぶJavaアプリのグラフィックの基本」で説明しました。Canvasクラスの特定のメソッドをオーバーライドするのでしたね。今回はキーイベントを受け取ったら、それをどのように処理としてゲームに反映させるかというところまで考えなければなりません。
第6回のテンプレートでは、キー処理を即時実行していましたが、今回のアプリは1秒間に20回ぐらい再描画を行い、その再描画のタイミングでキー処理を行う仕様にします。これは、キー処理が確実に画面に反映されるようにするためです。
つまり、押されたキーが何であるかをキーバッファに保持しておかなければなりません。
「キーバッファ」とは、押されたキーを順番に処理していくためのデータ構造で、一般的には「FIFO」と呼ばれます。キーが押されたら、そのタイミングでキーバッファの最後に追加し、描画のタイミングでキーバッファの先頭から1つずつ処理を行うわけです。
キーバッファは配列で実装しようとすると考慮しなければならないことがたくさんあって複雑になりがちですが、今回は「StringBuffer」というJavaの便利なクラスを利用して、シンプルに実装しています。
複雑なことをシンプルに実装できるのがJavaのだいご味の1つです。
キーが押された際に、落下や回転などを実装しますが、それらの実装については、キーバッファの使い方と併せて以下のソースコードを参照ください。
「当たり判定」というと、シューティングゲームや格闘ゲームを連想し、難しいもののように思われるかもしれませんが、今回のアプリに関していえばとても簡単です。当たり判定を行うメソッドを、今回は以下のように利用しています。
これはメソッドの再利用の良い例だと思います。
左右や下に移動できるかどうかのほかに、回転できるかどうかも当たり判定でチェックします。上図の回転できないケースは、回転後のブロックがすでにあるブロックに重なってしまうので回転できない、と判定します。
この当たり判定を利用すると、下にこれ以上移動できない場合はブロックを固定できるようになります。当たり判定と着地判定を実装したソースコードは以下からダウンロードしてください。
次のブロックが出現すると同時にほかのブロックに重なっている場合、ゲームオーバーになるのですが、これが当たり判定を実装したことで簡単に実現できます。以下はゲーム終了判定を実装したソースコードです。
次ページでは、内部状態とゲームらしさを演出する実装の解説を始めます。
Copyright © ITmedia, Inc. All Rights Reserved.