運動と位置の計算
グラフィック要素の位置を変えることで、アニメーションのように動いて見えます。リスト2-2は、円のx座標を1ずつ増やすことで、円が水平に運動しました。グラフィック要素に運動させる時には、要素の座標位置を計算する必要があります。その時、運動の法則の基本が役に立ちます。
等速運動
リスト2-2は、座標の変化が一定の等速運動でした。等速運動では、位置の変化(スピード)を現在位置に加えることで、新しい位置を計算します。新しい位置 = 現在位置 + 速度 【例】 float x; float s = 1; //速度は1 x = x + s; //位置を計算速度が負の場合は、座標値が減り(水平運動の場合左へ動き)ます。次のリストは、リスト2-2を変更し、左右の壁に来たらスピードの正負を逆転させて、壁に跳ね返らせています。
クリックで開始⇔停止
【リスト6-1】 float x= 5; float s = 1; //速度 void setup() { size(250, 125); } void draw() { background(255); x = x + s; ellipse(x, height/2, 10, 10); if (x >= width || x < 0) { s = -s; } }
加速運動
速度が一定の割合で変化するのが、加速運動です。新しい速度 = 現在速度 + 加速度 新しい位置 = 現在位置 + 速度 【例】 float x; float s = 1; //速度は1 float a = 0.1; //加速度は0.1 s = s + a; //速度を計算 x = x + s; //位置を計算等速運動のリスト2-2で、速度sが変化するようにしたのが、リスト6-2です。画面右に消えて、左に現れる時、最初の速度に戻るようにしました。
クリックで開始⇔停止
【リスト6-2】 float x= 5; float s = 1; //速度 float a = 0.1; //加速度 void setup() { size(250, 125); } void draw() { background(255); s = s + a; x = x + s; ellipse(x, height/2, 10, 10); if (x >= width) { s = 1; //速度を元に戻す x = 5; //円を左壁上に置く } }
減衰する運動
実世界では、ボールが壁に当たった場合、当たる前スピードがそのまま保たれるのではなく、減衰します。新しい速度 = 現在速度 * 減衰率 ←減衰率は0-1.0の間の値 新しい位置 = 現在位置 + 速度 【例】 float x; float s = 1; //速度は1 float f = 0.95; //減衰率は0.95 s = s * 0.9; //速度を計算 x = x + s; //位置を計算ボールを落下させた時のように円が下に移動し、下の壁で跳ね返るようにしたのが、リスト6-3です。床にバウンドして跳ね返るときに、速度が減衰します。このリストでは、速度が小さくなってほとんど動かなくなった時に、また画面の上部にもっていって再び落下させています。
draw()関数の2行目で呼出している関数ballFall()は、このプログラムのために定義した関数で、y座標の位置を計算する機能をもたせています。関数を定義する代わりに、ballFall()内の命令を、draw()関数内に記述しても同じですが、draw()関数が長くなるような場合は、このように特定の仕事をする部分を関数として独立させると、見通しのいいプログラムになります。
円の中心のy座標が250-rより大きくなった時、下の壁に当ったと判定し、速度を95%に減じています。rは円の半径です。 スピードが大きくなると、下の壁に当たったと判定された時、円がすでに壁の内側に入り込んでいることがあります。その場合円が壁にめり込んで見えるのを防ぐため、壁に当たったと判定されたら y = height-r; として円を壁の表面に置きなおしています。
クリックで開始⇔停止
【リスト6-3】 float g =1.8; //加速度 float damping=0.95; //減衰 float y; float dy; //y方向の速度 int r = 5; // 円の半径 void setup() { size(250, 250); fill(0); //黒塗り } void draw() { background(255); //背景塗り直し ballFall(); //yの値を計算する関数の呼出し ellipse(width/2, y, r*2, r*2); //円を描く } void ballFall() { //yの値を計算する関数の定義 dy = dy + g; //加速度による速度の変化を計算 y= y + dy; //y座標を動かす if (y>height-r) { //下の壁 y = height-r; //めり込み調整 dy = -dy * damping; if (abs(dy)<=g/2) { //ほぼ止まった dy=0; y=0; } } }
ばねの運動
ばねやスプリングの端に物体がぶら下がっている場合の運動を考えてみましょう。 物体を引っ張って離すと、振動して元の位置に戻ります。この時の物体の位置は次のように計算できます。ばねに働く力 = (ばねの元の長さ - ばねの今の長さ) * ばねの強さ ばねによる加速度 = ばねに働く力 / 物体の重さ 新しい速度 = (現在速度 + 加速度) * 減衰率 新しい位置 = 現在位置 + 速度次の例では、ボールがゴムにぶら下がっています。ボールをマウスでドラッグして動かし、離すと振動してやがて止まります。 リスト6-4は、簡単にするために、垂直方向だけに振動するようにしました。
物体の位置を計算する関数calcPos()を定義し、振動中の物体の位置を計算しています。 マウスでボールをドラッグ中はボールはマウスの位置に置くので、位置を計算する必要はありません。そのため、システム変数mousePressedがfalseの時だけ、calcPos()関数を実行しています。
ドラッグでボール移動
【リスト6-4】 float dy; // 速度 float y; // 位置 float g = 0.9; //重力加速度 float mass = 5;//質量 float stiffness = 0.15;//ばねの強さ float damping = 0.98; //減衰 int r = 5; // 円の半径 float mY = 50; //ばねの長さ void setup() { size(125, 250); fill(0); y = mY; } void draw() { background(255); if (mousePressed) { y = mouseY; //ドラッグ中はボールはマウスの位置 }else{ calcPos(); //ボールの位置を計算 } line(width/2, 0, width/2, y); //ゴムの線 ellipse(width/2, y, r*2, r*2); //ボール } void calcPos() { //位置の計算 float forceY = (mY - y) * stiffness; //ばねに働く力 float ay = forceY / mass; //ばねによる加速度を計算 dy = damping * (dy + ay + g); //速度計算 y = y + dy; //y座標を動かす }
リスト6-4では、画面上のどの位置でマウスを押しても、ボールは反応します。draw()関数の中で、マウスが押されているかどうかだけを調べていているからです。マウスでボールを掴んで上下するというインタラクションをもう少しリアルにするためには、次のようにマウスカーソルがボールので押されている時だけ、ボールをマウスにくっつけます。
if (mousePressed && dist(width/2, y, mouseX, mouseY)<r*3) { y = mouseY; }else{ calcPos(); }dist(width/2, y, mouseX, mouseY)はマウスの位置とボールの中央の間の距離を計算します。それが半径rより小さければマウスはボールの上にあります。厳密に距離が半径より小さい時だけ、マウスで動くようにすると、ボールをマウスでつかむのが難しくなるので、余裕をみて半径の3倍より小さいかどうかを調べています。
演習問題
クリックで開始⇔停止
【問題6-1】加速度運動のリスト6-2を修正して、円が左右の壁で跳ね返るようにしてください。速度が大きくなりすぎるのを防ぐため、壁に当たった時に、速度を元の値(例えば1)に戻すようにします。
クリックで開始⇔停止
【問題6-2】250X250の画面上をコートとし、直径10のボールが壁に跳ね返って動くプログラムを作ってください。
(1)ボールは等速運動で進み、左右の壁に来たらx方向の進む方向を、上下の壁に来たらy方向の進む方向を変えます。x座標は次のように計算できます。
float x; //ボールのx座標 float speedX = 2; // x方向の速度の絶対値 int directionX = 1; // 進む向き(1:右、-1:左) とすると、 x = x + speedX * directionX; //新しい位置
(2)壁に当たった時、速度をランダムに変えて、動きに変化をつけます。 2から5の間の乱数を生成するには、random(2, 5)とします。
(3)ボールの速度が大きくなると、ボールの位置が壁の中に入り、壁にめり込んで見えることがあります。これを修正するために、跳ね返る際に(運動の方向を逆転させるときに)ボールの位置を壁の表面に置きます。 ボールの半径をradiusとすると、右の壁での処理は次のようになります。
if (x > width-radius) { // 右の壁 x = width - radius; //壁の表面にボールを置く directionX = -directionX; // 方向を変える }
クリックで開始、マウスでラケット移動
【問題6-3】問題6-2を機能拡張して、ラケットボールのようなゲームプログラムにしてください。
(1)棒状のラケットを描画し、マウスの左右の移動に応じて、ラケットの位置が左右に移動するようにします。 例えば、ラケットのx座標をrx、y座標をry、ラケットの幅をracketWとすると、ラケットを表す四角形は、次のように描くきます(ラケットのy座標は変化しないものとします)。
constrain()関数は、マウスの位置が画面からはずれても、ラケットは画面内から外れないようにするための制限です。
rx = constrain(mouseX, 0, width-racketW); rect(rx, ry, racketW, 20); //ラケットの高さは20
(2)ボールがラケットの上に当たったらはねかえり、受け損なったら動きが止まり、GAME OVERと表示します。文字の表示をするには、フォントデータを作り、text()関数を使います。
(3)GAME OVER後、マウスボタンのリリースで、再びボールが上から現れ、ゲームが始まるようにします。
(4)上部にゴールを設け、そこにボールが入ったら、pointが加算され、pointを表示します。