その92。
tmlib.js 怒濤の 100 サンプル!! – Graphics(HTML5 Canvas) 編
今回試すサンプルは「図形(上級)」の「描画した図形を動かしてみよう」です。
ではサンプルが掲載されているのでまずは見てみましょう。
中央のボタンを押してみて下さい。
大小様々な円が画面端に当っては跳ね返る動作が描画されたと思います。
/* * 定数 */ var SCREEN_WIDTH = 465; var SCREEN_HEIGHT= 465; var SHAPE_NUM = 64; tm.main(function() { // canvas インスタンス生成 var canvas = tm.graphics.Canvas("#world"); // 幅, 高さを指定 canvas.resize(SCREEN_WIDTH, SCREEN_HEIGHT); // 画面にフィットさせる canvas.fitWindow(); // 黒でクリア canvas.clearColor("black"); // サークルを生成 var circleList = []; for (var i=0; i<SHAPE_NUM; ++i) { var circle = {}; circle.radius = tm.util.Random.randint(10, 32); circle.x = tm.util.Random.randint(circle.radius, canvas.width-circle.radius); // X 座標値; circle.y = tm.util.Random.randint(circle.radius, canvas.height-circle.radius); // Y 座標値; circle.vx = tm.util.Random.randint(-4, 4); circle.vy = tm.util.Random.randint(-4, 4); var hue = tm.util.Random.randint(0, 360); // 色相 var color = "hsl({0}, 50%, 50%)".format(hue); circle.color = color; circle.update = function() { // 移動 this.x += this.vx; this.y += this.vy; // 画面からはみ出ないよう制御 if (this.x < 0) { this.x = 0; this.vx*=-1; } else if (this.x > canvas.width) { this.x = canvas.width; this.vx*=-1; } if (this.y < 0) { this.y = 0; this.vy*=-1; } else if (this.y > canvas.height) { this.y = canvas.height; this.vy*=-1; } } circle.draw = function() { // 塗りつぶしスタイルを設定 canvas.fillStyle = this.color; // 塗りつぶし描画 canvas.fillCircle(this.x, this.y, this.radius); }; circleList.push(circle); } // アニメーション tm.setLoop(function() { // 画面クリア canvas.clearColor("black"); // 更新 for (var i=0,len=circleList.length; i<len; ++i) { var circle = circleList[i]; circle.update(); } // ブレンドを乗算に設定 canvas.save(); canvas.globalCompositeOperation = "lighter"; // 描画 for (var i=0,len=circleList.length; i<len; ++i) { var circle = circleList[i]; circle.draw(); } canvas.restore(); }, 1000/60); });
このサンプルでは大きく分けてサークルを生成している部分と動かしている(アニメーション)部分にわけて記述されていますのでそれに沿った形で解説もやってみます。
サークルを生成
まず6行目で作成する円の数を定義しています。
20行目で今後処理するのに必要な「 circleList 」という配列を用意しています。
21行目で「 for文 」を使って6行目に定義した円の数だけ繰り返すように指示しています。
22行目で「 circle 」という変数をオブジェクトという型で用意しています。
型やオブジェクトについてはこちらを参照ください。
23行目で「 circle 」というオブジェクト変数に「 radius 」という新しいプロパティを作成し値として10から32の間の値をランダムで取得したものを代入しています。
24行目でX座標を取得しています。その際に円が画面からはみ出ないよう最小値に23行目で取得した半径を、最大値に画面の横幅から半径を引いたものを指定して取得しています。
25行目でY座標を取得しています。こちらもX座標と同じように画面からはみ出ないよう最小値と最大値を指定しています。
27行目で「 vx 」というプロパティを追加し円が移動するX方向の向きと速さを取得しています。
28行目で「 vy 」というプロパティを追加し円が移動するY方向の向きと速さを取得しています。
移動処理の部分で詳しく説明しますのでここでは値を取得しているということだけ理解してください。
30~32行目の処理は以前のサンプルでも出て来ましたね。
ですので簡単に説明しますと、
30行目で色を決める値をランダムで取得し、
31行目で取得した値を用いて使えるように整形し、
32行目で整形した内容で色を指示しています。
34行目で「 update 」というプロパティを追加し関数を定義しています。
以降「 circle.update 」と呼び出すだけで定義した関数を実行出来るようになります。
関数の内容はといいますと、
35~36行目で移動処理を行なっています。
「 this.x += this.vx; 」を訳すならば「 this.x 」でアクセスしたオブジェクトのX座標に、27行目で取得した「 vx 」の値を足して代入するということになります。
略した記述方法ですので略さないで記述するなら以下のようになります。
「 this.x = this.x + this.vx; 」
「 vx 」の値を取得するとき向きと速さを取得すると言いましたが、上の記述方法を見るとなんとなくわかるのではないでしょうか。
例えば「 this.x 」の値が100だったとし、取得した「 vx 」が「 2 」だったとします。
そうすると次の式が出来ます。
「 102 = 100 + 2 」
X座標が100から102に変化したということは右に移動したということですよね。
「 vx 」が「 -2 」だったとしたら
「 98 = 100 + (-2) 」となりX座標が100から98に変化したので左に移動したということになります。
値がプラスかマイナスかで方向を決めているわけです。
そして速さですが単純に2が4になれば移動量も増えますよね?そしてこのプロパティが呼び出されるごとに取得した値だけ移動しますから小さければゆっくり、大きければ早く移動することになります。
「 vy 」はY座標ということなり、移動する方向が違うだけでほぼ同時に動きますので斜め移動しているように見えるわけです。
「 0 」という値を「 vx 」「 vy 」のどちらかが取得した場合は片方の方向だけ移動します。両方同時に取得した場合は停止した状態のままになります。
続いて画面端からはみ出ないよう制御ですが、
40行目で最初にX座標が左端である「 0 」より小さいか判定しています。
0より小さかった場合はX座標を0とし、「 vx 」の値に「 -1 」を乗算し負数(マイナスの値)にしています。
0より大きかった場合は「 else if 」で示されている条件判定に移ります。
else ifでの条件はX座標が「 canvas.width 」画面の横幅より大きかった時となります。
横幅より大きかったらX座標に横幅を指定し、こちらも「 vx 」の値に「 -1 」を乗算しています。
何のために負数にしているかというと最初の条件式が0より小さいかの判定ですのでX座標が0だった場合はこの条件に当てはまらないですし画面端に出てしまいますのでこのようになっています。
条件や数値を変えて変化を確認するとわかりやすいかと思います。
Y座標も同じ要領で行われていますので違いを比較し見てみて下さい。
ここまでが「 update 」で呼び出された時に処理される内容です。
45行目で「 draw 」というプロパティを追加し関数を定義しています。
47行目で30~32行目で決定した塗りつぶしの設定を指示しています。
49行目でランダムで取得したX座標、Y座標、半径を用いて円を描画しています。
ここまでが「 draw 」で呼び出された時に処理される内容です。
52行目で20行目で用意した配列に生成したサークルを格納しています。
アニメーション
57行目でループ処理を定義しています。
繰り返し処理とは異なります。繰り返し処理は条件が満たされた時や満たされている間といった条件が必要ですが、ループ処理には条件は必要ありません。
tmlib.js で定義されている「 setLoop()
」というメソッドを用いれば実行させる関数とディレイと呼ばれる何秒ごとに実行させるかの時間をパラメータに指示するだけでループ処理が行えます。
59行目で17行目でも行なっている画面を黒にクリアしています。
これを行わないと画面がどんどん白に塗り替えられることになります。
なぜそうなるかはこの後の処理を追っていくとわかりますよ。
62~65行目で更新処理を行なっています。
for文で「 circleList 」の配列の長さ(円の数)を取得し、配列の長さだけ繰り返し処理を行なっています。
63行目で22行目の変数と同名ですが別物の「 circle 」という変数を定義し「 circleList 」の「 i 」番目の円を格納しています。
円の数だけ繰り返されるので繰り返す回数に合わせて配列の中から順番に取り出しているわけです。
64行目で取り出した円を34行目で定義した「 update 」という関数を用いて円を移動させています。
68行目は以前のサンプルにも登場している「 save 」です。
現在の状態保存するものでしたね。
69行目でその56で勉強した合成タイプを指示しています。
71~74行目で円を実際に描画する処理を行なっています。
71行目は62行目と同じようにfor文で配列の長さだけ繰り返し処理を行うよう指示されています。
72行目も同様に繰り返す回数と取り出す配列の順番をリンクさせています。
73行目で45行目に定義した「 draw 」という関数を用いて円を描画しています。
75行目で68行目に保存していた状態へと戻しています。
76行目には「 setLoop()
」の二つ目のパラメータであるディレイを「 1000/60 」と指示しています。
普通に計算すると16.666…と割り切れない数値になります。
細かく説明するとややこしくなるので単純にブラウザが1回処理する時間を表す計算式と思って下さい。
知っている人であれば1フレームごとにというとピンとくるでしょうか。
詳しく知りたい方はこちらを参照してください。
文章が長すぎて書くのも読むのも大変なので短くなるよう考えてみます。