Processingで簡単なラケットゲームを作る
Background
課題学習の一つにラケットを用いた簡単なゲームをつくることになった。
私はPythonの経験とSwiftを軽く触れた経験があるが、似てる部分がありつつも
なんでも同じ要領ではいかないことを実感していた。
コードと解説
まず最初に座標を定義する。
なぜここでfloatを使うのかというと、多くのプログラミング言語) において、小数点を含む数値(浮動小数点数)を扱うためだ。ボールの移動点は必ずしも整数値とは限らないのでfloatである必要がある。次にProcessingの標準ウィンドウは小さいので、大きくする。
なぜならsetup() は初期設定を行うための関数であり、setup() 関数はプログラムの開始時に一度だけ実行されるからだ。ウィンドウの大きさは基本的に頻繁に変更する必要のあるものではないので、最初に一度設定するのが自然。
次にボールの動きを記述していく。
このコードは、ボールの位置を更新し、画面の端に達した際の反射処理を行っている。1:x = x + dx; と y = y + dy;
これは、ボールの現在のX座標 (x) にX方向の移動量 (dx) を加えて、ボールの新しいX座標を計算しており、同様にしてボールの現在のY座標 (y) にY方向の移動量 (dy) を加えて、ボールの新しいY座標を計算している。簡単に言ってしまえば、この2行でボールを現在の速度 (dx, dy) に従って移動させているのだ。
2:if (x + a_w >= width || x < 0) { dx = -dx; }
この if 文は、ボールが左右の壁に衝突したかどうかをチェックするコードになる。
x + a_w >= width は、「ボールが右の壁にぶつかったか、または通り過ぎようとしている状態であり、そのどちらなのかを判定している。
同様にx < 0 は、「ボールの左端 (x) がウィンドウの左端 (0) より左にある」
つまり、ボールが左の壁にぶつかったか、または通り過ぎようとしている状態だ。
|| は論理的な「または」を意味し、上記のどちらかの条件が真(true)の場合、
中括弧 {} 内のコードが実行されるようになっている。
dx = -dx; は、ボールのX方向の移動量 (dx) の符号を反転させている。
例えば、dx が正の値(右方向への移動)だった場合、-dx によって負の値(左方向への移動)になる。これにより、ボールが左右の壁に衝突した際に、X方向の移動方向が反転し、
跳ね返るような動きになる。
3:if (y < 0) { dy = -dy; }
この if 文は、ボールが上側の壁に衝突したかどうかをチェックしている。
2と同様の考え方を用いている。
4:if (y + a_h >= height) { x = 0; y = 0; dx = 1; dy = 2; }
これもボールが下側の壁に衝突したかどうかをチェックしており、考え方は2と同様だ。
しかしここだけは他のコードとは異なり、{}の中の処理をする場合、
x = 0; y = 0; dx = 1; dy = 2; は、ボールの位置を初期位置 (x = 0, y = 0) にリセットし、
X方向とY方向の移動量も初期値 (dx = 1, dy = 2) に戻すことになっている。
これは、ボールが下の壁に到達したら、
画面の中央上部に戻してゲームをリスタートさせるようになっているのだ。
まとめると、この draw() 関数内の部分は、毎フレームごとにボールを移動させ、
ボールが画面の左右や上の端にぶつかった場合は進行方向を反転させて跳ね返らせる処理、
そしてボールが下の端に到達した場合は初期状態に戻す処理を行っていることになる。
最後にラケットの動きを記述する。
1:float racketX = mouseX;
ここでは新しい浮動小数点型の変数 racketX を宣言し、
現在のマウスのX座標 (mouseX) の値を代入させることで、
ラケットのX座標をマウスの水平方向の位置に追従させるようにしている。
これで自分がマウスでラケットを操作できるようになる。
しかし、事前にfloat racketY = 250;を定義しているので、ラケットの高さは変更できない。
(変更したければracketXと同様のことを記述する必要がある。)
2:if (racketX + r_w > width) { racketX = width - r_w; }
この if 文は、ラケットが右側の壁を超えてしまわないように制御するものだ。
racketX + r_w は、ラケットの右端のX座標を計算しており、
width は、ウィンドウの幅を表すシステム変数であるから、
racketX + r_w > width は、「ラケットの右端がウィンドウの右端よりも右にある」か
どうかを判定することができる。
もしこの条件が真(true)の場合、中括弧 {} 内のコードが実行され、
racketX の値を「ウィンドウの幅からラケットの幅を引いた値」に設定することで、
ラケットの右端がちょうどウィンドウの右端に接する位置にラケットの左端を移動させ、
ラケットが壁を突き抜けるのを防いでいる。
3:if (y + a_h >= racketY && y <= racketY + r_h && x + a_w >= racketX && x <= racketX + r_w) { dy = -abs(dy); }
このコードだが、ボールがラケットに衝突したかどうかを判定している。
衝突の条件は、ボールとラケットが縦方向と横方向の両方で重なっているかだ。
y + a_h >= racketY:ボールの下端がラケットの上端以上にある(縦方向の重なりがあるか)。
y <= racketY + r_h:ボールの上端がラケットの下端以下にある(縦方向の重なりがあるか)。
x + a_w >= racketX:ボールの右端がラケットの左端以上にある(横方向の重なりがあるか)。
x <= racketX + r_w:ボールの左端がラケットの右端以下にある(横方向の重なりがあるか)。
&& は論理的な「かつ」を意味している。したがって、
上記の4つの条件がすべて真(true)の場合、中括弧 {} 内のコードが実行される。
ここで肝心なところがdy = -abs(dy)だ。
このコードはボールのY方向の移動量 (dy) の符号を反転させる役割を担う。
abs(dy) は dy の絶対値(正の値)を返すため、-abs(dy) は常に負の値になる。
これにより、ボールがラケットに衝突した際に、Y方向の移動方向が常に上向きに反転し、
跳ね返るような動きになる。しかし、ボールの速度の絶対値は変わらないので、
跳ね返る際の速さは維持されている。
4:background(128, 128, 255);
rect(x, y, a_w, a_h); // ボール
rect(racketX, racketY, r_w, r_h); // ラケット
}
いよいよ最後のパートだ
まずbackgroundは背景の色をしている。今回は青みがかった紫のような色にしてみた。
draw()により毎回呼び出しを喰らっているので、毎フレームごとに背景が塗り替えられ
残像が残らないようにしている。またボールは今回はrect(今回は正方形)にした。
rect(racketX, racketY, r_w, r_h); // ラケット
このコードでいよいよこれまで書いてきた様々な関数が呼び出され、
(x座標,y座標,ラケットの幅,ラケットの高さの順で表記されている。)
ラケットとして本領を発揮する。
ここまで書いてきたが読み返したら内容が思い出せるだろうか。
不安だがここに書き残しておく。
Discussion