小学校教員のためのプログラミング入門
前回までで図形を描くことと,図形に色をつけることができるようになりました.今回は図形を動かす方法を学びます.
最初はボールを等速(速度v)で左から右へころがしたときの軌跡を描いてみます.
右方向が X 軸の正方向である座標系における t 秒後の x 座標は次のような式であらわされ,
スケッチコードは,次のようになります.
size(300,300); colorMode(RGB,255,255,255); ellipseMode(RADIUS); background(0,0,0); stroke(0,0,100); fill(0,200,255); int r = 10; float x0 = r, y0 = 150; float v = 30; float t = 0; float x = x0, y = y0; while( x < width-r ) { x = x0 + v*t; ellipse( x, y, 10, 10 ); t++; }
r はボールの半径を格納する変数でここでは 10 としています.ボールの初期位置 (x0, y0) は (r,150) としています.座標系は実数も利用でき,今回のスケッチでは実数計算も行うので,ほとんどの変数を実数(float)型としています.なお,同じ型の変数を宣言する場合には,カンマ(,)で区切って,一文で宣言をすることができます.もちろん初期化(値の代入)も同時に行えます.
x = x0 + v * t;
は,先の式に従った t 秒後の x 座標を計算します.* は乗算の演算子です.そして
while( x < width-r )・・・
で,ボールが表示ウィンドウ(以下,単に画面と書きます)から消えるまで,t の値を 1 づつ大きくしながら,座標の計算をし直しして,円を描いています.円は ellipseMode メソッドを使って描画モードを RADIUS に設定していますので,パラメータとして中心座標と半径を渡しています.ellipseMode の使い方は rectMode と同じです.
ボールが消えるまでの判定,つまり,繰り返しの条件は,y 座標がウィンドウの縦幅からボールの半径を引いたものより小さい間,要は,ボール全体が画面内に入っている間としています.
いろいろな方向にころがしたはどうなるでしょうか.下図は右斜め下にころがした場合です.
ころがす方向を x 軸正方向を基準として,y 軸正方向側回りの角度 θ として表すことにすると,t 秒後の座標は次のように計算できます.
まず投げる方向を格納する変数を宣言します.投げる方向は右下斜め(π/8)にしてみます.なお,Processing では π の値が格納されている定数 PI が用意されています.定数は変数に似ていますが,値を変えることができないものです.
float theta = PI / 8;
x 軸方向の初速度は v に cosθ をかけたものになりますので,t 秒後の x 座標も,横にころがしたときの式の v0 の部分に cosθ をかけたものになります.一方,y 軸方向にも v0 に sinθ をかけた初速度が発生します.よって,その分の移動量を表す項(v0 sinθ・t)が加えられています.
したがって,t 秒後の座標を計算する部分は
x = x0 + v*cos(theta)*t; y = y0 + v*sin(theta)*t;
となります.また,繰り返しの条件は,Y 軸方向にボールが消える可能性もありますので, y < width-r も成り立っている間とします.二つの条件式の両方が成り立っているかを条件にしたいときには,二つの条件式の論理積を取る演算子 && を用います.もし,どちらかの条件式が成り立っていれば成り立っていることにしたい場合は論理和 || を用います.完成したスケッチを次に示します.
size(300,300); colorMode(RGB,255,255,255); ellipseMode(RADIUS); background(0,0,0); stroke(0,0,100); fill(0,200,255); int r = 10; float x0 = r, y0 = r; float v = 30; float theta = PI/8; float t = 0; float x = x0; float y = y0; while( y < height-r && x < width-r ) { x = x0 + v*cos(theta)*t; y = y0 + v*sin(theta)*t; ellipse( x, y, 10, 10 ); t++; }
現在のスケッチは軌跡を描いているだけであって,図形を動かしているとは言えません.そこで,ここからは球が動いて見えるようにスケッチを変更していきます.
球が動いて見えるようにするためには,一つの球を描いたら,その球を消してから,次の球を描くということを繰り返せばよいはずです.このようなことをするために,Processing には連続モード(Continuous Mode)というプログラミングスタイルが用意されています.この連続モードに対して,今までのスケッチは基本モード(Basic Mode)というプログラミングスタイルを用いたものでした.
連続モードのスケッチは,
void setup() { 初期化コード } void draw() { 画面描画コード }
という形式にします.初期化コードにはスケッチを実行したときに最初に一度だけ実行させたいコードを書き,画面描画コードには実行を終わらせるまで繰り返し呼び出されるコードを書きます.なお,上記のコードは setup と draw という名前のメソッドの定義をしています.メソッドの定義についてはまだ説明していませんが,連続モードでは,必ずこう書けばよいと覚えておいてください.
今作っているスケッチの場合,画面描画コードには円を描くコードを入れることになります.ただし,今まで while 文で作り上げていた繰り返しは,draw メソッドが繰り返し呼び出されるという構造を利用することになります.
連続モードに変更したスケッチを次に示します.なお,このスケッチは終了をさせるための仕組みがまったくありませんので,実行停止ボタンを押すまで,永遠に動作するスケッチとなっています.このスケッチを実行すると,先ほど作ったスケッチと同じ結果が表示されると思います.
int r = 10; float x0 = r, y0 = r; float v = 30; float theta = PI/8; float t = 0; float x = x0, float y = y0; void setup() { size(300,300); colorMode(RGB,255,255,255); ellipseMode(RADIUS); background(0,0,0); stroke(0,0,100); fill(0,200,255); } void draw() { x = x0 + v0*cos(theta)*t; y = y0 + v0*sin(theta)*t; ellipse( x, y, 10, 10 ); t++; }
では,新しい位置にボールを描く前に,前回描いたボールを消すコードを入れてみます.前回描いたボールを消すためには,画面全体を黒で塗りつぶすのが簡単です.つまり,
stroke( 0, 0, 0 ); fill( 0, 0, 0 ); rect( 0, 0, width, height );
というコードを draw メソッドの先頭に入れればよいはずです.ただし,ellipse メソッドを呼び出す前には,線と塗りつぶしの色を再度設定する必要がありますので,次のコードを ellipse メソッドの前に入れます.
stroke( 0, 0, 100 ); fill( 0, 200, 255 );
この変更を行った後,実行してみてください.おそらく,一瞬でボールが落ちていくために,何も表示されていないように感じるはずです.これはコンピュータの処理が速すぎて,draw メソッドがすごい短い間隔で呼び出されてしまっているからです.そこで,draw メソッドを呼び出す間隔を遅くしてみます.draw メソッドを呼び出す間隔を変更するには,frameRate メソッドを利用します.使い方は次の通りです.
frameRate( 1秒間にdrawメソッドを呼び出す回数 );
とりあえず,
frameRate( 15 );
を setup メソッドの中に入れて,実行してみてください.おそらくボールが動いて見えたはずです.frameRate メソッドのパラメータをいろいろ変えてみると,スピードが変わってゆくのがわかると思います.
なお,プログラムの中では t の単位を秒として扱ってきましたが,今回の設定で t は 1/15 秒ごとに 1 づつ値が増えていきます.描画ウィンドウの中では現実よりも15倍の速度で時間が流れていると考えてください.
現在のスケッチは,どうも動きがカクカクしていて,現実のボールが動いている様子を表現しているという感じがありません.これは,1 回 1 回に球が移動しすぎていることが原因です.現在は t を 1 づつ増やしています.これは 1 秒ごとの球の位置を求め描いているということですので,この値を小さくすれば,球の移動量が小さくなるはずです.試しに,t++ を t=t+0.1 に変更してみてください.この場合,今度は球のスピードが遅すぎるかもしれませんので,先ほど設定した draw メソッドを呼び出す回数を 1 秒間に 30〜60 回くらいにしてみてください.これで,かなり球が落ちていく様子が表現できるスケッチになったと思います.
ここで,最後に,colorMode メソッドを
colorMode( RGB, 255, 255, 255, 100 );
と変更し,ボールを描くときの色の設定を
stroke( 0, 0, 100, 100 ); fill( 0, 200, 255, 100 );
画面を真黒にするときの色の設定を
stroke( 0, 0, 0, 20 ); fill( 0, 0, 0, 20 );
と変更してみてください.ボールの残像が表示され,なんとなくかっこよくなったと思います.これは,色の指定に不透明度を使っうことで残像効果を生み出しています.colorMode で 5 番目のパラメータとして 100 を設定しています.これで,色の指定の際には 4 つ目のパラメータとして不透明度を指定することになります.不透明度は 0 で完全な透明,100 で完全な不透明となります.
球を描くときには 100 を指定し,完全な不透明,つまり,不透明度を設定しないときと同じように描いています.一方,画面全体を黒で塗りつぶす際には 20 を指定し,かなり透明に近い黒で塗りつぶしています.こうすると,1 回の塗りつぶしではボールは完全には消えず,何度か塗りつぶしを繰り返すうちにだんだんと消えてゆくことになります.そのため,残像が見えるような効果が生まれるわけです.
完成したコードを次に示します.なお,setup の中にあった球を描くときの色設定は必要ないので,削除してあります.
int r = 10; float x0 = r, y0 = r; float v = 30; float theta = PI/8; float t = 0; float x = x0, y = y0; void setup() { size(300,300); colorMode(RGB,255,255,255,100); ellipseMode(RADIUS); background(0,0,0); frameRate(30); } void draw() { stroke( 0, 0, 0, 10 ); fill( 0, 0, 0, 10 ); rect( 0, 0, width, height ); x = x0 + v*cos(theta)*t; y = y0 + v*sin(theta)*t; stroke( 0, 0, 100, 100 ); fill( 0, 200, 255, 100 ); ellipse( x, y, 10, 10 ); t = t + 0.1; }
上記で作成したスケッチは,たとえばテーブルの上をボールを転がしたときの軌跡を描くものでした.では,本来の意味での上下(上は空,下は地面)の空間で,右斜め下に投げた時の軌跡を描くスケッチを作ってみましょう.
作ったスケッチは半角英数で 学籍番号_K5 という名前で保存し,pde ファイルを WebClass から提出してください.締め切りは来週の授業開始までです.
このような状況では下方向に重力加速度がかかることになります.よって,初速度 v で角度 θ の方向に投げた場合,t 秒後の位置は次のようになります.ここで,g は加速度です.画面上の 1pixel を m,t の単位を秒(s)と考えれば,この値は 9.8[m/s] となります.