見出し画像

FALさんの「Processingでゲーム制作」で学ぼう! 2

Processing Community Day 東京 でのワークショップ講師の FAL さんによって公開された「Processingでゲーム制作」の資料で勉強させてもらってます。

前回はちょっと変更を加えるだけで、敵機に当たった自機が破壊されるようにすることができました。
前回の記事はこちら。

FALさんの「Processingでゲーム制作」で学ぼう!

爆発したい!

オリジナルでは敵機に弾が当たっても消滅するだけで爆発はしません。

やっぱり敵機に弾が当たったり、自機が衝突してしまったりしたらドカーン!と派手に爆発させたいですよね。
やってみましょう!

当たり判定をしているのはどこ?

弾や自機の当たり判定をしてるのは前回見た通り ElementList.pde 中の class ElementList にある collide メソッドでした。

 /**
  * Runs collision process between two groups.
  */
 void collide(ElementList otherGroup) {
   for (Element element : this) {
     for (Element other: otherGroup) {
       float distance = element.position.dist(other.position);
   
       if (distance < 50) {
         element.isDead = true;
         other.isDead = true;
       }
     }
   }
 }

そして、衝突したという判断後にオブジェクトを消しているのは同じく class ElementList にある update メソッドです。

 /**
  * Updates each element and removes each dead element.
  */
 void update() {
   Iterator<Element> iterator = this.iterator();
   while (iterator.hasNext()) {
     Element element = iterator.next();
     element.update();
     if (element.isDead) iterator.remove();
   }
 }

collide メソッドで element.isDead を true にして、update メソッドで element.isDead が true のものを remove しています。

じゃあ、 remove する前に爆発の描画を入れればよさそうです。

     if (element.isDead) iterator.remove();

ここを例えばこう。

     if (element.isDead) {
       ellipse(element.position.x, element.position.y, 200, 200);
       iterator.remove();
     }

おお!出来ました! 簡単じゃん!

自機の爆発を派手にしたい

出来ましたけど、今のままだとどの爆発も同じ描画ですね。
自機がやられたときはショック大なので、自機の爆発は派手にドラマチックにしたいですよね。

これを今の場所でやるとしたら?
その時の衝突が敵機か自機かを判定して爆発の処理を分けるということになりそうです。

Java だと instanceof でそのオブジェクトが何クラスなのかを判定できます。
でも敵機も自機もクラスは同じ Element だからこの方法ではダメですね。

じゃあ、敵機と自機が持ってる ElementComponent の違いはどうでしょう?

自機の持ってる ElementComponent
 new SquareDisplayer(),
 new PlayerUpdater()

敵機の持ってる ElementComponent
 new CircleDisplayer(),
 new DefaultUpdater()

これで判別できたとしても、例えば自機は SquareDisplayer() を持ってると言えるけど、SquareDisplayer() を持っていたら自機とは限らないのでは?
今後 SquareDisplayer() を使った別のオブジェクトを作ったらもう判定に使えなくなりますよね。

う〜ん。

そもそも敵機と自機の描き分けに if 文出てきてないぞ?

そもそも、敵機は丸、自機は四角ですけど、このコードでの描画は if 文で分けたりしてません。
どうやって描き分けしてるんでしょう?

それが先程出てきた ElementComponent です。

自機
 new SquareDisplayer(),
敵機
 new CircleDisplayer(),

それぞれのクラスのコードは run メソッドでそれぞれの図形を描画するようになっていて、

class SquareDisplayer implements ElementComponent {
 void run(Element element) {
   rect(element.position.x, element.position.y, 50, 50);
 }
}
class CircleDisplayer implements ElementComponent {
 void run(Element element) {
   ellipse(element.position.x, element.position.y, 50, 50);
 }
}

それを Element.pde 中の class Element にある display メソッドから呼んでいます。

 void display() {
   displayer.run(this);
 }

全然敵機なのか自機なのかの判断文出てきてませんね。

ミソは CircleDisplayer、SquareDisplayer クラスが共通の ElementComponent インターフェースを implements して作られているところです。

Element.pde 中の class Element にある display メソッド中の displayer、

 void display() {
   displayer.run(this);
 }

これは CircleDisplayer や SquareDisplayer そのものではなく、 ElementComponent クラスになっています。
定義とセットしてるとこを見ると、

 ElementComponent displayer, updater;
 
 Element(ElementComponent displayer, ElementComponent updater) {
   position = new PVector();
   velocity = new PVector();
   // Set components
   this.displayer = displayer;
   this.updater = updater;
 }

ね?

そして、自機の Element オブジェクトを作っているのが、 setup() のここ。

 Element player = new Element(
   new SquareDisplayer(),
   new PlayerUpdater()
 );

敵機は addEnemy() のここ。

 Element newEnemy = new Element(
   new CircleDisplayer(),
   new DefaultUpdater()
 );

それぞれオブジェクト SquareDisplayer、 CircleDisplayer が Element のコンストラクタに渡されて、Element.displayer にセットされるわけです。
どちらも ElementComponent としてね!

つまり、オブジェクト Element.displayer は型としては ElementComponent だけど、オブジェクトとして自分が何者なのか(SquareDisplayer なのか、 CircleDisplayer なのか)を知っていて、 ElementComponent.run メソッドが呼ばれたときには判断も何も単に自分の run メソッドを実行してるだけ、というわけです。

オブジェクト指向プログラミングでの「ポリモーフィズム」ってやつですね。

爆発も同じ考えでやってみよう

この考えに沿ってコードを書けば、自機と敵機でも爆発を変えることができるのでは?

まずは ElementComponent に、破壊されたときの動作のためのクラスを追加。

ElementComponent.pde

class EnemyDestroyer implements ElementComponent {
 void run(Element element) {
   ellipse(element.position.x, element.position.y, 100, 100);
 }
}
class PlayerDestroyer implements ElementComponent {
 void run(Element element) {
   for (int i = 1; i <= 5; ++i) {
     rect(element.position.x, element.position.y, i * 80, 480 - i * 80);
   }
 }
}
class DummyDestroyer implements ElementComponent {
 void run(Element element) {
 }
}

EnemyDestroyer と PlayerDestroyer はそれぞれ敵機と自機の破壊時のためのクラス。
今回弾自体は破壊されても反応なしとしたいので、ダミーの動作を定義するために DummyDestroyer クラスを作りました。

次にこれらを呼び出す Element 中に destroyer を追加します。

Element.pde

ElementComponent displayer, updater, destroyer;
 
Element(ElementComponent displayer, ElementComponent updater, ElementComponent destroyer) {
   position = new PVector();
   velocity = new PVector();
   // Set components
   this.displayer = displayer;
   this.updater = updater;
   this.destroyer = destroyer;
}
void destroy() {
   destroyer.run(this);
}

そして、それを呼び出すコードを消滅時の判定中に追加。

ElementList.pde

if (element.isDead) {
   element.destroy();
   iterator.remove();
}

後は、自機、敵機、弾の生成それぞれに Destroyer を追加して出来上がりです!

GameSample.pde

Element player = new Element(
   new SquareDisplayer(),
   new PlayerUpdater(),
   new PlayerDestroyer()
);
Element newEnemy = new Element(
   new CircleDisplayer(),
   new DefaultUpdater(),
   new EnemyDestroyer()
);


ElementComponent.pde

Element newBullet = new Element(
   new BulletDisplayer(),
   new DefaultUpdater(),
   new DummyDestroyer()
);


やった! 出来たーっ!

私の知ってる爆発と違う…

出来たー!はいいんですが、よく見るゲームの爆発となんだか違うような?
確かもっとドーーーンと長いこと爆発してますよね?

あれ…?

ということで、続きます。



この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕