FALさんの「Processingでゲーム制作」で学ぼう!
Processing Community Day 東京 でのワークショップ「Processingでゲーム制作」の資料が、講師の FAL さんによって公開されました。
資料とソースを合わせて読んでいくととてもわかり易くて、『ここのコードはそういう意味か!』と理解できるし、『ああ、だからこう書くといいんだ!』と納得できます。
でも…
自分で一からこのコードを書けと言われると、どうにも書けそうな気がしない…
ということで、今回はこちらのコードで遊びながら学ばせていただきます。
「Processingでゲーム制作」の資料紹介
FAL(@falworks_ja) さんはゲーム「Solid Aether」の作者であり、様々なクリエイティブ・コーディング作品も作ってらっしゃる方です。
2019/02/02 に行われた Processing Community Day 東京で講師を務められたワークショップ、その資料を下記で公開してくださいました。
ソースはこちら。
今回使わせていただくソースは、公開されている /GameSample/ の下記 4つのソースコードです。
Element.pde
ElementComponent.pde
ElementList.pde
GameSample.pde
GameSample.pde がお馴染み Processing での setup() draw() 等のあるコードです。
4つのコードを /GameSample/ 以下に保存し、GameSample.pde を実行すれば OK。
もちろん、4つのコードを一本にまとめて実行しても OK です。
マウスの移動が自機の移動、マウスクリックで弾発射という操作です。
コードを読み解くには改造するのが一番
私なりにこれらのコードを読み解いていきたいと思います。
まずはそれぞれをざっと見てみましょう。
GameSample.pde では最初に player(自機)を一個作って、Enemy(敵機)は 30フレーム毎に追加しています。弾の生成はここには出てきませんね。
自機は一個だけなのにリストにしてるのが気になります。
あとは毎フレーム敵機、自機、弾を「更新」して、衝突判断らしきことをして、敵機、自機、弾を「表示」することを繰り返しています。
Element.pde は自機、敵機、弾のクラスで、「更新」と「表示」のメソッドを持っていますが、具体的な処理はここには無く、次の ElementComponent.pde にある処理を使うようです。
ElementComponent.pde は自機、敵機、弾の具体的な動作一つ一つをそれぞれ別のクラスとして定義しています。
弾の生成はここの自機の「更新」の処理の一つとして書かれています。
ElementList.pde は複数の Element をリストとして扱うクラスで、まとめて一気に「更新」したり「表示」したりができるようになっています。
コードをより深く読み解くには自分で改造してみるのが一番です。
見るのはこれぐらいにして、コードをいじって動かしてみましょう。
その方が楽しいですしね。
まずはキー押下で弾発射
元のコードはマウスクリックで弾が発射されますが、これを何かキーが押されたら弾が発射されるようにしてみましょう。
弾を発射しているコードは、 ElementComponent.pde 中の class PlayerUpdater にあるここです。
// Shoot bullet with mouse button.
if (mousePressed && frameCount % 20 == 0) {
mousePressed が「マウスがクリックされたか?」の条件です。
frameCount % 20 == 0 は「今 20フレーム毎か?」の条件。弾が一度に複数発射されないように入れているのでしょう。
ここを「キーが押されたか?」の条件に変えてみます。
// Shoot bullet with mouse button.
if (keyPressed && frameCount % 20 == 0) {
いいですね。このほうが撃ちやすい気がします。
こうなると、もっとバンバン撃ちたくなります。
こうしてみましょう。
// Shoot bullet with mouse button.
if (keyPressed && frameCount % 10 == 0) {
10フレーム毎に弾が出るようにしてみました。
うん!こりゃいい!ストレス発散!
敵機に当たったら自機も破壊されるようにしたいけど?
元のコードだと自機は無敵です。
これを、敵機とぶつかったら自機が破壊されるように変えてみたいですね。
元のコードでは敵機に弾が当たったら敵機と弾が破壊されるようになっています。
その衝突判断をしているコードは、 ElementList.pde 中の class ElementList にあるここです。
/**
* 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;
}
}
}
}
this と otherGroup のリストの要素を一個ずつ見ていって、お互いの距離が 50 より近ければ当たったと判断… てこれだけだと何のこっちゃですね。
このメソッドを呼んでいるところを見てみましょう。
呼んでいるのは GameSample.pde 中の void draw() にあるこの一箇所だけです。
playerBulletElements.collide(enemyElements);
playerBulletElements が先程の this、 enemyElements が otherGroup になりますね。
playerBulletElements : 弾
enemyElements : 敵機
どちらも複数存在する可能性があります。
だからリストに入れてそれぞれ一個ずつ見ていってるんですね。
一個目の弾と、一個目の敵機との距離、二個目の敵機との距離 …
ニ個目の弾と、一個目の敵機との距離、二個目の敵機との距離 …
という具合。
たったこれだけの変更で!?
先程の衝突判断のコード、this と otherGroup はどちらも型が Element のリスト = ElementList となってますね。
/**
* 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;
}
}
}
}
自機とか敵機とか弾とかはここには出てきません。
具体的な物が出てこないので、ここだけ見ると「何のこっちゃ」なんですが、そこがミソなんです!
具体的な物が出てくるのはメソッドを呼び出しているこちらの方。
playerBulletElements.collide(enemyElements);
これで弾のリストが this、敵機のリストが otherGroup になります。
それぞれを new してる所をみると、確かにどちらも ElementList ですね。
enemyElements = new ElementList();
playerBulletElements = new ElementList();
てことは? もしかして this を自機のリストにしたら自機と敵機の衝突判断となるのでは?
コードに追加してみましょう。
playerBulletElements.collide(enemyElements);
playerElements.collide(enemyElements);
できた!スゴイ!
ついでにこうすると、自機と弾の衝突判断になります。
playerBulletElements.collide(playerElements);
弾を撃った瞬間に自機が消滅してしまいますが…
これぞオブジェクト指向プログラミング?
しかし、すごいですね。たった一行追加するだけで自機と敵機の衝突判断が出来てしまうなんて!
具体的に衝突判断をしているメソッドは何も変えること無く、そこに渡すものを自機や弾に変えるだけで適切な動作をしてくれる。
これが噂に聞くオブジェクト指向プログラミングの威力?
最初に「自機は一個なのにリストにしてるのが気になる」と言ってましたが、ここを共通化するためだったんですね。
なるほど〜。うーんこれはもっと知りたい…
ということで、続きます。
この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕