データベースとは?|初心者でもわかるプログラミング学習入門
GEEK JOB編集部
前章までで、自機と弾と敵が出る、ごくシンプルなシューティングを作ることはできました。
しかし実は、先ほど作ってもらったプログラムには、致命的なバグがあります。
objects内でBulletやEnemyの区別を行っていないため、もしBullet同士、Enemy同士がぶつかり合うことがあれば、衝突判定を行って相殺してしまうのです。
これを防ぐには、どうすればよいのでしょうか。
1つの方法は4章のように、BulletとEnemyを別のListに格納することです。
シューティングを作るうえでは、どちらかというとこちらの方が賢いやり方かもしれません。
もう1つの方法が、Player、Bullet、Enemyにそれぞれ「Playerサイド」「Enemyサイド」という属性を付けて、同属性のオブジェクトとは衝突判定を行わないようにすることです。
この属性を付けるときに便利なのが、列挙型enumというものです。
列挙型と聞いてピンと来ない方もいると思いますが、実はみなさんはすでに列挙型を使っています。
FieldクラスのpaintComponent()メソッドにある以下の1文です。
1 |
g.setColor(Color.red); |
このColor.redというのが列挙型の要素です。
列挙型は、ある特定の値を示す定数のように使うことができます。
Javaにおける列挙型は、以下のように宣言します。
1 2 3 4 |
ObjectType.java public enum ObjectType { player, enemy } |
これを使って、GameObjectクラスにtype変数およびアクセサを導入してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
GameObject.java public class GameObject { protected int x; protected int y; protected int width; protected int height; private ObjectType type; private boolean isAlive = true; public boolean isAlive() { return isAlive; } public void delete() { this.isAlive = false; } public ObjectType getType() { return this.type; } public void setType(ObjectType type) { this.type = type; } public boolean collideWith(GameObject object) { if(!object.isAlive()) { return false; } if(this == object) { return false; } return x < object.x + object.width && object.x < x + width && y < object.y + object.height && object.y < y + height; } public void update() { } public void draw(Graphics g) { } } |
あわせて、衝突判定のメソッドも、同属性との衝突判定を行わないように書き換えます。
1 2 3 4 5 6 |
public boolean collideWith(GameObject object) { if(!object.isAlive()) { return false; } if(this.type == object.getType()) { return false; } return x < object.x + object.width && object.x < x + width && y < object.y + object.height && object.y < y + height; } |
最後に、Enemyなどの各クラスで、自分の属性を設定するようにすれば完了です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
Enemy.java public Enemy(int x, int y) { this.x = x; this.y = y; this.width = 20; this.height = 20; this.setType(ObjectType.enemy); } Player.java public Player() { this.x = 300; this.y = 300; this.width = 50; this.height = 50; this.setType(ObjectType.player); } Bullet.java public Bullet(int x, int y) { this.x = x; this.y = y; this.width = 10; this.height = 10; this.setType(ObjectType.player); } |
列挙型を用いるメリットとしては、名前が付けられるので読んだときに意味が分かりやすいことや、単に定数変数を用いるのと違い、自分で型を作れるので予期せぬ動作を減らせること、などがあります。
いかがだったでしょうか。
オブジェクト指向(とイベントリスナーの続きと列挙型)の説明は以上になります。
しかし、残念ながら今回の記事では、オブジェクト指向、特に多態性(ポリモーフィズム)の素晴らしさを伝えきることはできていません。
オブジェクト指向の便利さを体感するには、Enemyクラスを継承していろいろな種類の敵を作って、それぞれのメソッドを書き換えてみてください。
今回は時間計測にActionListenerを使ってしまったので、あまり複雑な動きを指定することはできませんが、挙動の変更はupdate()メソッド、見た目の変更はdraw()メソッドを書き換えることで、非常にシンプルに実装できることが分かるはずです。
たとえば「playerをホーミングする敵」を作りたかったら、以下のように書き換えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
EnemyHoming.java public class EnemyHoming extends Enemy { private Player player; public EnemyHoming(int x, int y, Player player) { super(x, y); this.player = player; } @Override public void update(){ this.x += 0.05 * (player.x - this.x); this.y += 0.05 * (player.y - this.y); } } |
インスタンスの生成も、Fieldクラスのコンストラクタに、以下の1文を加えれば完了です。
1 |
objects.add(new EnemyHoming(0, 0, player)); |
ちなみに、super()というのは、親クラスのコンストラクタを呼び出すという意味で、この場合はnew Enemy(x,y)が使われています。
こんな感じで、皆さんもぜひ多態性を応用していろいろ遊んでみてください!
ひとまず、これでオブジェクト指向の説明を終わります。
お疲れさまでした。