ボールがキャンバス内を跳ね回る仕組みを理解しよう- ”ブロック崩しゲーム”の基本 - JavaScriptでやる1分間プログラミング
前回のp5.jsの基本解説では、簡単に四角や円などの図形をキャンバス内に描き、それを一方向に動かす仕組みについて説明しました。
簡単におさらいすると、まず図形を描く場合はXとYの値、そしてその大きさを指定するだけです。p5.jsエディタを立ち上げたら、draw関数のところに次の2行を加えるだけです。
//図形をピンク色で塗りつぶす
fill(200, 0, 200);
//円(circle)を、X:100、Y:100、大きさ80で指定
circle(100, 100, 80);
fillは「塗りつぶす」という意味で、図形の色を決めるものです。図形を描くには必要ありませんが、色を付けると見やすくなるのでここではこの一行を加えています。渡している200, 0, 200というのは色を指定する数値でこの場合は濃いピンクになります。
これでピンクの円をポンと一つ描くことができます。
次にこの円を右方向に動かそうとしたら、Xの値を100と決め打ちせず、Xという「変数 variable」に入れ、それを x += 10 として”パラパラ漫画”の各回で10ずつ増やす(つまり右に移動していく)ようにします。
sketch.jsのコードを次のようにしてみてください。4行加えるだけです。
let x=100; //Xの変数にして100を入れる
function setup() {
createCanvas(600, 400);
}
function draw() {
background(220);
fill(200, 0, 200);
circle(x, 100, 80);
x += 10; //Xが10ずつ増える
}
これで円が右にスーッと動いていきます。
このコードで x+=10のところの数値を20とか30にするとスピードがぐっと速くなります。反対に3などに減らすとかなり遅くなります。こんなことを前回やりました。
ボールが跳ね返るようにしたい!
ところが、ボールが右に動くようになったのはよいのですが、右の端を越えて消えていってしまいます。これではつまらないので、ブロック崩しゲームのように、ボールが右の”壁”に到達したら跳ね返ってきてほしいですよね。
そこでScratchなどでは「もし端についたら跳ね返る」という便利な関数ブロックが用意されています。
ここではそれをプログラミングで実装してみます!
今回は次のチュートリアルで解説されているように、物体がキャンバスの壁を跳ね返りながら動き続けるようにするコードを書いてみます。
今回のCoding ChallengeではShiffman先生のコーディング解説がちょっとゴチャゴチャしています。それはそれで見ていてとても楽しいのですが、きちんとしたコードは後半まで見ないと完成しません。ここではかいつまんで説明していきます。物体が跳ね返るロジックが理解できたら是非このビデオを見てください。英語の学習にはもってこいの内容です!
Xが増えると「右に移動」、Xが減ると「左に移動」
前回も説明しましたが、draw関数は「パラパラ漫画」のようにずっとループ(繰り返し)しています。このため、この中で「円のヨコの位置を1つ増やして」というコード(x += 1)を書くとそれを何度も繰り返すので永遠にヨコの位置が右方向に1ずつずれていき、最終的にキャンバスから飛び出してしまします。
逆に12行目のコードを次のようにしたら、今度は左に移動していくというのはわかりますよね。
x -= 10;
最初のXの位置が100なのですぐに左サイトに消えてしまいますが、とにかくXの数字を増やすか減らすかでボールの位置が右に行ったり左にいったりする仕組みは理解できると思います。
では跳ね返るとはどういうことか?
右に動いていた(つまりXが増えていく)ボールが壁に当たると、今度は左に動く(つまりXが減っていく)ように変えるということ。別の言い方をすればXが動く量をプラスからマイナスに変えればよいということです。
そこで次のようなコードに変更してみてください。
let x=100; //Xの変数にして100を入れる
let xdirection = 10; //右に10ずつ動く
function setup() {
createCanvas(600, 400);
}
function draw() {
background(220);
fill(200, 0, 200);
circle(x, 100, 80);
x += xdirection; //Xをxdirection分動かす
}
これまでは動く量(スピードと考えてもよいです)を10のように決め打ちしていましたが、これをxdirection(Xの方向)という変数にして、そこに10を入れてみます。そしてパラパラ漫画で増やしていく量を10ではなくその値が入ったxdirectionにします。x
x += xdirection;
これをこのまま実行すると前と全く同じで、円が右に消えていきます。要は10と決め打ちしていたのを、xdirectionという変数を作ってそこに10を入れたわけです。10ずつ進む状況には変わりはありません。
そこで次のようなことをするコードを加えてみます。
もし円が右の端をタッチしたらxdirectionをマイナスの値に変更する
「こうなったら〇〇してほしい」というのがIF文
直径が80の円が、横幅600のキャンバスの右端に到達するというのはどういうことかというと、円の位置(円の中心となります)が、横の位置のXでいうと560までいくということです。ここで「Xが600になったら…」ではない理由はわかりますか?それは円のヨコの位置、Xというのは円の中心。円の中心が600になると円が半分はみでてしまします。
そこでそれを正確に指定するとこうなります。
もし円のXの位置がキャンバスの横幅から40少ない地点まで行ったら
これをコードで書くとこうなります。
if (x > width - 40) {
・・・壁をヒットした時にどうするかを書く
}
widthというのは常にキャンバスの横幅となります。40は円の直径80の半分、つまり半径となります。
値の±を逆にするには-1を掛ければよい
では壁にぶつかったら、xdirectionがプラス(右に移動)だったのをマイナス(左に移動)にすればよいので、これは-1を掛けてやると符号だけが変わります。x += 1 だとXに1がプラスされますが、x *= -1 だとXに-1を掛けるという意味になります。
xdirection *= -1;
そこでコードを次のようにします。
let x=100; //Xの変数にして100を入れる
let xdirection = 10; //右に10ずつ動く
function setup() {
createCanvas(600, 400);
}
function draw() {
background(220);
fill(200, 0, 200);
circle(x, 100, 80);
x += xdirection; //Xをxdirection分動かす
//跳ね返るロジック
if (x > width - 40)
xdirection *= -1;
}
すると見事に右側で跳ね返ります!!
ところが、右から跳ね返ってきたボールは、今度は左側に消えていきます。つまり左側で跳ね返ったらどうなるかというコードも追加しないといけません。そこでIF文のところを次のようにします。
もしXが横幅-40より大きくなるか、もしくは40より小さくなったら
例えば、「もし値段が1万円以上か、もしくは配達に一週間以上かかる場合は買わない」というように条件が二つある場合、それを||(縦棒のパイプを二つ並べる)で指定できます。英語ではor(もしくは)に当たります。
//跳ね返るロジック
if (x > width - 40 || x < 40)
xdirection *= -1;
この二つ目の条件だけを変えて実行するとどうですか?ボールが永遠と左右にバウンスし続けますよね!
縦横無尽に跳ね回るようにするには
ここまででボールのバウンスが”無限ループ”で続くようになりましたが、当然のことながら横に行ったり来たりだけです。ボールがいろんな方向にバウンスするためにはどうすればよいのでしょう。
今のコードで横方向にしかバウンスしないのはXの値だけを変えて動かしているからです。ヨコだけでなくタテにも動かしたい場合はYの値も変えないといけません。そこで、変数にYも加えてみます。Xに対して作った変数を同じようにYにも用意します。こんな感じになります。
let x=100; //Xの変数にして100を入れる
let y=100; //yの変数にして100を入れる
let xdirection = 10; //右に10ずつ動く
let ydirection = 10; //下に10ずつ動く
function setup() {
createCanvas(600, 400);
}
function draw() {
background(220);
fill(200, 0, 200);
circle(x, y, 80);
x += xdirection; //Xをxdirection分動かす
y += ydirection; //Yをydirection分動かす
//跳ね返るロジック
if (x > width - 40 || x < 40)
xdirection *= -1;
}
これを実行すると、あれ? ボールが下の方に消えていきました。
これはXでやった通りの「跳ね返る」ロジックをYにも用意しないとこうなります。そこで跳ね返るロジックに次のコードを追加してください。
//跳ね返るロジック
if (x > width - 40 || x < 40)
xdirection *= -1;
else if (y > height - 40 || y < 40)
ydirection *= -1;
Xに対して書いたコードと同じで、半径の40を差し引きますが、下の端のチェックではwidthではなく今度はheight(キャンバスの高さ)を使います。ししてydirectionに-1を変えてやるとその方向が逆になります。これで跳ね返るロジックは完成です。
跳ね返りの瞬間にちょっとした問題が・・・
しかしここには一つ問題があります。
例えばタテのYの位置ですが、y += ydrectionでタテの位置が10ずれます。この瞬間で下のラインを越えてしまうこともあります。ちなみにその瞬間をとらえたのが次のスクリーンキャプチャです。下のラインに埋まった状態になっていますよね。
コードではこの後すぐにydirectionにマイナスを掛けて上方向に動くようにしているので実際には問題ないように見えますが、実は一瞬だけラインを越えてしまっているのです。
これを解決するために、「XとYの値は左右と上下の値を超えないようにする」というコードを入れておけば解決します。それがこれです。
//ラインオーバーの修正
x = constrain(x, 40, width - 40);
y = constrain(y, 40, height - 40);
このconstrainは「制約」という意味で、つまり「Xの値が下は40、上はwidth - 40を超えないようにする」という意味で、強制的にその範囲内に戻されます。これで跳ね返りの瞬間できれいにライン上でバウンスするようになります。
最終的なコードは次のようになりました。
let x=100; //Xの変数にして100を入れる
let y=100; //yの変数にして100を入れる
let xdirection = 10; //右に10ずつ動く
let ydirection = 10; //下に10ずつ動く
function setup() {
createCanvas(600, 400);
}
function draw() {
background(220);
fill(200, 0, 200);
circle(x, y, 80);
x += xdirection; //Xをxdirection分動かす
y += ydirection; //Yをydirection分動かす
//跳ね返るロジック
if (x > width - 40 || x < 40)
xdirection *= -1;
else if (y > height - 40 || y < 40)
ydirection *= -1;
//ラインオーバーの修正
x = constrain(x, 40, width - 40);
y = constrain(y, 40, height - 40);
}
跳ね返りロジックの別のアプローチ
Coding Challengeのビデオではconstrainは使わずに、XとYの値をそれぞれのケースで一番端の値に強制的に戻しています。
if (x > width - 40) {
xdirection *= -1;
x = width - 40; //強制的にXを端の値にもどす
}
else if (x < 40) {
xdirection *= -1;
x = 40; //強制的にXを端の値にもどす
}
else if (y > height - 40) {
ydirection *= -1;
y = height - 40; //強制的にYを端の値にもどす
}
else if (y < 40) {
ydirection *= -1;
y = 40; //強制的にYを端の値にもどす
}
それぞれの”端っこチェック”をするIF文で、方向の±を変換した後に、XとYの値に一番端っこの値をセットして、強制的に位置を戻しています。もちろんこれで問題ありませんが、IF文が4つに分かれてコードが長くなります。constrainを利用した方がコードがすっきりするのはおわかりだと思います。
ちなみにこのスクリーンでは円がずっと描画されたままになっていますが、これは12行目の、background(220)の行の先頭に//を入れてコメントアウトすると、パラパラ漫画の各回で背景色を塗り変えないので、円が消えないままずっと描画された状態になります。皆さんも是非やってみてください。
この記事が気に入ったらサポートをしてみませんか?