クラスの継承を理解する

前章までで、シューティングに必要なPlayer、Bullet、Enemyといったクラスを作成し、複数の要素に対してはListで管理することを学びました。

本章では、上記の3つのクラスの親として、GameObjectというスーパークラスを作成し、それを継承することで、コードを簡略化する方法について学びたいと思います。

以下が完成イメージです。

polymorphism

それでは、まずはGameObjectというクラスを作り、Player、Bullet、Enemyに共通する部分を抽出しましょう。

こうすることで、たとえば先ほどまで書いていたEnemyクラスは、以下のように書くことができます。

width、heightという変数は、前の3つのクラスにはありませんでしたが、のちのち衝突判定に使うことを考えて入れてあります。

同様にして、Playerクラス、Bulletクラスも書き換えます。

 

アクセス修飾子

ここで、これまでprivateで宣言していた変数が、GameObjectクラスの中ではprotectedになっていることに気が付いたでしょうか。

これらはアクセス修飾子と呼ばれ、以下のような違いがあります。

修飾子 アクセス権
public 全てのクラスからアクセス可能
protected 自クラス内、同じパッケージ、サブクラスのからならアクセス可能
なし 自クラス内、同じパッケージからならアクセス可能
private 自クラスからのみアクセス可能

アクセス権をそれほど意識しなくても、ある程度であればプログラムを書くことはできますが、より安全なコードを書くためには、このような形で適切にアクセス権を制限し、予期せぬ操作によるデータ改ざんを防ぐ必要があります。

先ほどまでは、自クラスからのみ参照していたので、private修飾子を用いていましたが、今回はGameObjectを継承したPlayer、Bullet、Enemyといったサブクラス内で変数を参照しているので、protected修飾子に変更する必要があります。

アクセサの設定

ところで、このように変数を自クラス以外から参照できるようにする方法には、もう1つやりかたがあります。

それが、private変数にアクセサを設定するという方法です。

説明するより実際に見てもらったほうが早いと思うので、以下のコードをご覧ください。

x、y、width、heightそれぞれに対して、getやsetというメソッドが追加されています。これらはゲッタ、セッタといい、あわせてアクセサと呼びます。

それぞれ該当の変数の値の参照や代入というシンプルな機能しかありませんが、これによってインスタンスの変数の値を自クラスの外から直接参照・変更できないようにでき、コードの安全性を高めることができます。

変数をpublicやprotectedにして、「enemy.width」のような形で簡単にアクセスできるようにするか、変数をprivateにして、アクセサを設定して安全性を高めるかは、常にトレードオフの関係にあり、プログラマは状況に応じて両者を使い分ける必要があります。

今回は読みやすさと簡略さを優先して、前者の方法を取りたいと思います。

親クラスへのメソッドの追加

以上で、3つのクラスに共通する処理をGameObjectに抽出したことで、いくらかコードがシンプルになったかと思います。

今度は、オブジェクトの生死を意味するisAliveという変数と、そのアクセサを設定してみましょう。

ただし、isAliveはboolean変数なので、Javaの標準のコーディング規約に則って、ゲッタにはisAlive()というメソッドを用います。

また、今回のシューティングゲームの中でオブジェクトが生き返ることはないため、セッタの代わりにdelete()というメソッドを作り、意味を明確にしておきます。

GameObjectクラスに以上の処理を書いておけば、Enemyなどの各クラスでは、「自分が生きている場合に描画する」ことを意味する以下の部分だけ書き加えればいいことになります。

続いて、GameObjectに衝突判定のメソッドと、オブジェクトの生死を格納する変数を追加してみましょう。

collideWith()が衝突判定で、引数として渡されたオブジェクトと自分が衝突している場合にtrueを返すメソッドになっています。

まず1つめのif文で、対象のobjectが死んでいたら、衝突することはありえないので、falseを返すようにしています。

2つめのif文では、自分自身と衝突判定を行わないように、falseを返しています。

return文の中身が実際の衝突判定になりますが、具体的にどのような判定を行っているかは、本筋とするオブジェクト指向の説明から逸れるので割愛します。

このcollideWith()を利用して、定期的に呼ばれるField.javaのactionPerformed()の中で、「もしenemyがbulletと衝突していたら、衝突した二つのオブジェクトを削除する」という処理を追加すると、以下のようになります。

このように、これまでクラスごとに個別に設定していたメソッドが、親クラスであるGameObjectに追加するだけで、Player、Bullet、Enemyのどのクラスでも利用できるようになるのです。

以上で、ひとまず弾を出して敵を倒せるという、シューティングの最低限の機能は実装できました。

superclass

[転職率95.1%]最短22日で未経験からプログラマーへ『GEEK JOB 転職コース』
geekjob_to_gol_banner_sp_06

『GEEKJOB 転職コース』では未経験からでもプログラミングや仕事の進め方を学習でき、プログラマーとして転職/就職できるまでサポートします。

  • 短期集中のプログラミング学習で未経験から最短22日での転職/就職ができる

  • IT業界の採用を熟知したメンターが就活サポート

  • わからないことは現役プログラマに質問できる

  • オフィスで働く上で必要なスキルを身に付けられる

  • 紹介可能企業は500社以上


文系出身だから、全くプログラミング経験がないから、プログラミングの学習に挫折してしまったから、といった方も関係ありません。

未経験からプログラマーを目指せる環境を用意しています。

詳しく見る

この記事の内容について報告する