情報学部 菅沼ホーム JavaScript 目次 基礎技術目次 索引

跳ね返りと乱数

  1. 跳ね返り

      アニメーションやゲームにおいて,壁に当たって跳ね返る動作は良く使用されます.以下は,それを実現したプログラムです.壁に当たったか否かの判定は,ある一定時間間隔で行われますので,判定が行われる時間と壁に当たる瞬間が必ずしも一致しません.一般的には,物体が壁にめり込んだ状態で行われることになります.そこで,以下のプログラムにおいては,物体の位置を右図のように修正しています.

      また,一般的には,壁に当たることによってエネルギーが失われ,速度の大きさが変化します.それを実現するために,赤い丸においては,係数 0.8 を乗じて速度の大きさを小さくしています.また,壁との摩擦などによって跳ね返る方向も理想的にはなりませんが,このプログラムでは理想的な方向に跳ね返るものとしています.

      いずれにしろ,以下のプログラムは,物理的な現象を忠実にシミュレーションしたものにはなっていません.しかし,アニメーションやゲームにとっては十分であると思います.
    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>跳ね返り</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
    07		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    08		<SCRIPT TYPE="text/javascript">
    09			canvas  = null;
    10			ctx     = null;
    11			timerID = -1;
    12			x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0;
    13			vx1 = 50, vy1 = 55, vx2 = 50, vy2 = 55, dt = 0.1, width = 40;
    14	
    15			function start() {
    16				canvas = document.getElementById('canvas_e');
    17				canvas.width  = 600;   // キャンバス要素の幅
    18				canvas.height = 400;   // キャンバス要素の高さ
    19				ctx = canvas.getContext('2d');
    20				x1 = width / 2;
    21				y1 = width / 2;
    22				x2 = x1;
    23				y2 = y1;
    24				timerID = setInterval('draw()', 33);
    25			}
    26						// 描画
    27			function draw()
    28			{
    29								// 摩擦なし(緑)
    30				if (y1 + width / 2 > canvas.height) {
    31					vy1 *= -1.0;
    32					y1   = canvas.height - width / 2;   // 位置の修正
    33				}
    34				else if (y1 - width / 2 < 0) {
    35					vy1 *= -1.0;
    36					y1   = width / 2;   // 位置の修正
    37				}
    38				else if (x1 + width / 2 > canvas.width) {
    39					vx1 *= -1.0;
    40					x1   = canvas.width - width / 2;   // 位置の修正
    41				}
    42				else if (x1 - width / 2 < 0) {
    43					vx1 *= -1.0;
    44					x1   = width / 2;   // 位置の修正
    45				}
    46				x1 += vx1 * dt;
    47				y1 += vy1 * dt;
    48								// 摩擦あり(赤)
    49				if (y2 + width / 2 > canvas.height) {
    50					vx2 *= 0.8;
    51					vy2 *= -0.8;
    52					y2   = canvas.height - width / 2;   // 位置の修正
    53				}
    54				else if (y2 - width / 2 < 0) {
    55					vx2 *= 0.8;
    56					vy2 *= -0.8;
    57					y2   = width / 2;   // 位置の修正
    58				}
    59				else if (x2 + width / 2 > canvas.width) {
    60					vx2 *= -0.8;
    61					vy2 *= 0.8;
    62					x2   = canvas.width - width / 2;   // 位置の修正
    63				}
    64				else if (x2 - width / 2 < 0) {
    65					vx2 *= -0.8;
    66					vy2 *= 0.8;
    67					x2   = width / 2;   // 位置の修正
    68				}
    69				x2 += vx2 * dt;
    70				y2 += vy2 * dt;
    71	
    72				ctx.clearRect(0, 0, canvas.width, canvas.height);
    73	
    74				ctx.beginPath();
    75				ctx.fillStyle = "rgb(0, 255, 0)";
    76				ctx.arc(Math.floor(x1), Math.floor(y1), 20, 0, 2*Math.PI, false);
    77				ctx.fill();
    78	
    79				ctx.beginPath();
    80				ctx.fillStyle = "rgb(255, 0, 0)";
    81				ctx.arc(Math.floor(x2), Math.floor(y2), 20, 0, 2*Math.PI, false);
    82				ctx.fill();
    83			}
    84		</SCRIPT>
    85	</HEAD>
    86	<BODY CLASS="white" STYLE="text-align: center" onLoad="start()">
    87		<H1>跳ね返り</H1>
    88		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="600" HEIGHT="400"></CANVAS>
    89	</BODY>
    90	</HTML>
    			
    30 行目~ 33 行目

      摩擦が無い場合において,円が下の壁に衝突したときの処理です.y 軸方向の速度を逆方向にし( 31 行目),y 軸方向の位置を修正しています( 32 行目).

    34 行目~ 37 行目

      上と同様,円が上の壁に衝突したときの処理です.y 軸方向の速度を逆方向にし( 35 行目),y 軸方向の位置を修正しています( 36 行目).

    38 行目~ 41 行目

      上と同様,円が右の壁に衝突したときの処理です.x 軸方向の速度を逆方向にし( 39 行目),x 軸方向の位置を修正しています( 40 行目).

    42 行目~ 45 行目

      上と同様,円が左の壁に衝突したときの処理です.x 軸方向の速度を逆方向にし( 43 行目),x 軸方向の位置を修正しています( 44 行目).

    46 行目~ 47 行目

      上で変更した速度を使用して,円の現在位置を再計算しています.

    49 行目~ 70 行目

      摩擦がある場合に対して,上と同様の処理を行っています.速度の方向だけではなく,その大きさも変更しています( 0.8 をかける処理).そのため,いずれの壁に衝突した場合も,x 軸及び y 軸方向の速度の修正が必要な点に注意して下さい.片方の速度の修正だけでは,反射方向が理想的な反射方向とは異なってしまいます.

  2. 乱数

      乱数は,Math オブジェクトrandom メソッドを利用して実現できます.random メソッドは,[0, 1] 区間の一様乱数を返しますので,適当な定数を乗じることによって任意の区間の一様乱数を生成可能です.以下に示すプログラムは,画面上部のランダムな位置から,ランダムな方向にスタートした物体が,壁に当たってランダムな方向に跳ね返る現象を表現しています.
    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>跳ね返り(乱数)</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
    07		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    08		<SCRIPT TYPE="text/javascript">
    09			canvas  = null;
    10			ctx     = null;
    11			timerID = -1;
    12			x = 0.0, y = 0.0, vx = 0.0, vy = 0.0, a = 0.0, dt = 0.1, width = 40;
    13	
    14			function start() {
    15				canvas = document.getElementById('canvas_e');
    16				canvas.width  = 600;   // キャンバス要素の幅
    17				canvas.height = 400;   // キャンバス要素の高さ
    18				ctx = canvas.getContext('2d');
    19				x  = width / 2 + Math.random() * (canvas.width - width);
    20				y  = width / 2;
    21				a  = Math.random() * Math.PI;
    22				vx = 70 * Math.cos(a);
    23				vy = 70 * Math.sin(a);
    24				timerID = setInterval('draw()', 33);
    25			}
    26						// 描画
    27			function draw()
    28			{
    29				if (y + width / 2 > canvas.height) {
    30					a  = Math.random() * Math.PI;
    31					vx = 70 * Math.cos(a);
    32					vy = -70 * Math.sin(a);
    33					y  = canvas.height - width / 2;   // 位置の修正
    34				}
    35				else if (y - width / 2 < 0) {
    36					a  = Math.random() * Math.PI;
    37					vx = 70 * Math.cos(a);
    38					vy = 70 * Math.sin(a);
    39					y  = width / 2;   // 位置の修正
    40				}
    41				else if (x + width / 2 > canvas.width) {
    42					a  = Math.random() * Math.PI - 0.5 * Math.PI;
    43					vx = -70 * Math.cos(a);
    44					vy = 70 * Math.sin(a);
    45					x  = canvas.width - width / 2;   // 位置の修正
    46				}
    47				else if (x - width / 2 < 0) {
    48					a  = Math.random() * Math.PI - 0.5 * Math.PI;
    49					vx = 70 * Math.cos(a);
    50					vy = 70 * Math.sin(a);
    51					x  = width / 2;   // 位置の修正
    52				}
    53				x += vx * dt;
    54				y += vy * dt;
    55	
    56				ctx.clearRect(0, 0, canvas.width, canvas.height);
    57	
    58				ctx.beginPath();
    59				ctx.fillStyle = "rgb(0, 255, 0)";
    60				ctx.arc(Math.floor(x), Math.floor(y), 20, 0, 2*Math.PI, false);
    61				ctx.fill();
    62			}
    63		</SCRIPT>
    64	</HEAD>
    65	<BODY CLASS="white" STYLE="text-align: center" onLoad="start()">
    66		<H1>跳ね返り(乱数)</H1>
    67		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="600" HEIGHT="400"></CANVAS>
    68	</BODY>
    69	</HTML>
    			
    19 行目

      円の x 軸方向の初期位置は [円の半径, キャンバスの幅-円の半径] 区間の一様乱数によって設定されます.

    21 行目

      a は円の初期進行方向を表し,[0.π] 区間の一様乱数によって設定されます.

    22 行目~ 23 行目

      x 軸方向及び y 軸方向の初期速度です.

    29 行目~ 34 行目

      円が下の壁に衝突したときの処理です.初期進行方向や初期速度を決める処理とほぼ同様ですが,y 軸方向の速度を逆方向に設置しています( 32 行目).また,33 行目において,壁にめり込んだ位置を修正しています.これらの点は,以下の処理においても同様です.

  3. もう一つの例

      この例では,四方のランダムの位置から,ランダムな進行方向で円が出現し,壁に当たると正確に反射します.ただし,反射後の速度は,元の速度に [0.5, 2.0] 一様区間の乱数をかけた値に変化します.そのため,最終的には,速度が大きくなりすぎ描画できなくなってしまいます.
    001	<!DOCTYPE HTML>
    002	<HTML>
    003	<HEAD>
    004		<TITLE>跳ね返り(乱数)</TITLE>
    005		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    006		<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
    007		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    008		<SCRIPT TYPE="text/javascript">
    009						// Ball オブジェクト
    010			function Ball(x, y, ang, v)
    011			{
    012				this.x  = x;   // 現在の位置(x)
    013				this.y  = y;   // 現在の位置(y)
    014				this.vx = v * Math.cos(ang);   // 現在の速度(x)
    015				this.vy = v * Math.sin(ang);   // 現在の速度(y)
    016			}
    017						// CANVAS
    018			canvas  = null;
    019			ctx     = null;
    020			timerID = -1;
    021			dt      = 0.1;   // 時間刻み幅
    022			r       = 20;   // 円の半径
    023			n       = 3;   // 円の数
    024			ball    = new Array();   // Ball オブジェクトの配列
    025	
    026			function start() {
    027				canvas = document.getElementById('canvas_e');
    028				canvas.width  = 600;   // キャンバス要素の幅
    029				canvas.height = 400;   // キャンバス要素の高さ
    030				ctx   = canvas.getContext('2d');
    031				let v = 50;   // 初期速度
    032	
    033				for (let i1 = 0; i1 < n; i1++) {
    034					let p = Math.random() * 2 * (canvas.width + canvas.height);
    035					let ang;
    036					let x;
    037					let y;
    038					if (p < canvas.width) {   // 上
    039						ang = Math.random() * Math.PI;
    040						x   = r + Math.random() * (canvas.width - 2 * r);
    041						y   = r;
    042					}
    043					else if (p < canvas.width + canvas.height) {   // 右
    044						ang = 0.5 * Math.PI + Math.random() * Math.PI;
    045						x   = canvas.width - r;
    046						y   = r + Math.random() * (canvas.height - 2 * r);
    047					}
    048					else if (p < 2 * canvas.width + canvas.height) {   // 下
    049						ang = -Math.random() * Math.PI;
    050						x   = r + Math.random() * (canvas.width - 2 * r);
    051						y   = canvas.height - r;
    052					}
    053					else {   // 左
    054						ang = -0.5 * Math.PI + Math.random() * Math.PI;
    055						x   = r;
    056						y   = r + Math.random() * (canvas.height - 2 * r);
    057					}
    058					ball[i1] = new Ball(x, y, ang, v);
    059				}
    060	
    061				timerID = setInterval('draw()', 33);
    062			}
    063						// 描画
    064			function draw()
    065			{
    066				ctx.clearRect(0, 0, canvas.width, canvas.height);
    067	
    068				for (let i1 = 0; i1 < n; i1++) {
    069					if (ball[i1].y + r > canvas.height) {   // 下
    070						let a = 0.5 + 1.5 * Math.random();
    071						ball[i1].vx = a * ball[i1].vx;
    072						ball[i1].vy = -a * ball[i1].vy;
    073						ball[i1].y  = canvas.height - r;   // 位置の修正
    074					}
    075					else if (ball[i1].y - r < 0) {   // 上
    076						let a = 0.5 + 1.5 * Math.random();
    077						ball[i1].vx = a * ball[i1].vx;
    078						ball[i1].vy = -a * ball[i1].vy;
    079						ball[i1].y  = r;   // 位置の修正
    080					}
    081					else if (ball[i1].x + r > canvas.width) {   // 右
    082						let a = 0.5 + 1.5 * Math.random();
    083						ball[i1].vx = -a * ball[i1].vx;
    084						ball[i1].vy = a * ball[i1].vy;
    085						ball[i1].x  = canvas.width - r;   // 位置の修正
    086					}
    087					else if (ball[i1].x - r < 0) {   // 左
    088						let a = 0.5 + 1.5 * Math.random();
    089						ball[i1].vx = -a * ball[i1].vx;
    090						ball[i1].vy = a * ball[i1].vy;
    091						ball[i1].x  = r;   // 位置の修正
    092					}
    093					ball[i1].x += ball[i1].vx * dt;
    094					ball[i1].y += ball[i1].vy * dt;
    095	
    096					ctx.beginPath();
    097					ctx.fillStyle = "rgb(0, 255, 0)";
    098					ctx.arc(ball[i1].x, ball[i1].y, 20, 0, 2*Math.PI, false);
    099					ctx.fill();
    100				}
    101			}
    102		</SCRIPT>
    103	</HEAD>
    104	<BODY CLASS="white" STYLE="text-align: center" onLoad="start()">
    105		<H1>跳ね返り(乱数)</H1>
    106		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="600" HEIGHT="400"></CANVAS>
    107	</BODY>
    108	</HTML>
    			
    010 行目~ 016 行目

      Ball オブジェクト( Ball クラス?)の定義です.ここに示すように,Ball オブジェクトは,その位置と速度をプロパティとして所有しています.これらのプロパティは,「 x 」や「 vx 」などのように直接参照することはできません.例えば,生成された Ball オブジェクト(インスタンス)の名前を b とすれば,「 b.x 」や「 b.vx 」などのように,オブジェクト名を付加して参照することになります.なお,this は,自分自身を指す演算子です.

    024 行目

      今までの例においては,複数の円を表示する場合,円毎に異なる変数を使用してきましたが,円の数が増加すれば,大変な作業になりますし,033 行目のような繰り返し文を有効に利用できなくなります.また,この例においては,表示する円の数を修正したい場合は,23 行目の値を変更するだけで対応可能ですが,円毎に異なる変数を使用していれば,このようなことは不可能です.

      そこで,ここでは,「ビットマップ,フィルタ,外部画像」の章におけるビットマップの節で説明した配列を使用します.この例の場合,各円の位置( x 座標と y 座標)と速度( x 軸方向及び y 軸方向)を表す 4 つの 1 次元配列をグローバル変数として定義しておけば十分です.勿論,多次元配列を使用して,変数の数を減らすことも可能です.一般的に,プログラムサイズが大きくなれば,必要とするグローバル変数の数も多くなります.さらに,関数の数も多くなりますので,同じ名前の変数や関数が使用されていないかを,プログラム全体に亘ってチェックする必要が出てきます.そこで,一つの例として,あえて新しいオブジェクトobject ) Ball の配列 ball をグローバル変数として定義し,各円の位置や速度はオブジェクトのプロパティとして所有させています.このような方法を使用すれば,グローバル変数の数は非常に少なくなり,管理も容易になるはずです(この例では,4 つのグローバル変数が 1 つになったに過ぎませんが).

    033 行目~ 059 行目

      この for 文によって,円の数だけに同様の処理を繰り返しています.

    034 行目

      [0,キャンバスの周囲の長さ] 区間の一様乱数を生成します.この値によって,キャンバスのどの位置から円を出現させるのかを決めています.

    038 行目~ 042 行目

      キャンバスの上の壁から円を出現させる場合の処理です.移動方向( 039 行目),及び,x 座標( 040 行目)はランダムに決定します.以下,043 行目~ 047 行目は右から,048 行目~ 052 行目は下から,及び,053 行目~ 057 行目は下から出現する場合に対して同様の処理を行っています.

    058 行目

      上で計算した位置及び速度に基づいて,新しい Ball オブジェクトを生成し,配列 ball の対応する要素に設定しています.

    069 行目~ 074 行目,075 行目~ 080 行目,081 行目~ 086 行目,087 行目~ 092 行目

      各々,下,上,右,及び,左の壁に衝突した場合の処理です.速度を変え,位置を修正しています.「 ball[i1].y 」のような形で Ball オブジェクトのプロパティを参照することに注意してください.

    093,094 行目

      上で計算した速度に従って,円の現在位置を計算しています.

      上の例では,Ball オブジェクトを使用していますが,下の例においては,オブジェクトを使用せずに記述しています.ただし,円の数を 5 に変更すると共に,円の色も変化させています.勿論,この例を,オブジェクトを使用して記述することも可能です.
    <!DOCTYPE HTML>
    <HTML>
    <HEAD>
    	<TITLE>跳ね返り(乱数)</TITLE>
    	<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    	<LINK REL="stylesheet" TYPE="text/css" HREF="../master.css">
    	<SCRIPT TYPE="text/javascript">
    		canvas  = null;
    		ctx     = null;
    		timerID = -1;
    		dt      = 0.1, width = 40, vi = 50;
    		n       = 5;
    		x       = new Array();
    		y       = new Array();
    		v       = new Array(); 
    		color   = new Array("rgb(255, 0, 0)", "rgb(0, 255, 0)", "rgb(0, 0, 255)", "rgb(0, 128, 128)", "rgb(128, 0, 255)");
    
    		function start() {
    			canvas = document.getElementById('canvas_e');
    			canvas.width  = 600;   // キャンバス要素の幅
    			canvas.height = 400;   // キャンバス要素の高さ
    			ctx = canvas.getContext('2d');
    							// 初期位置の決定
    			for (let i1 = 0; i1 < n; i1++) {
    				let p = Math.random();
    				v[i1] = new Array();
    				if (p < 0.25) {   // 上
    					x[i1] = width / 2 + Math.random() * (canvas.width - width);
    					y[i1] = width / 2;
    					let a = Math.random() * Math.PI;
    					v[i1][0] = vi * Math.cos(a);
    					v[i1][1] = vi * Math.sin(a);
    				}
    				else if (p < 0.5) {   // 下
    					x[i1] = width / 2 + Math.random() * (canvas.width - width);
    					y[i1] = canvas.height - width / 2;
    					let a = Math.random() * Math.PI;
    					v[i1][0] = vi * Math.cos(a);
    					v[i1][1] = -vi * Math.sin(a);
    				}
    				else if (p < 0.75) {   // 左
    					x[i1] = width / 2;
    					y[i1] = width / 2 + Math.random() * (canvas.height - width);
    					let a = Math.random() * Math.PI - 0.5 * Math.PI;
    					v[i1][0] = vi * Math.cos(a);
    					v[i1][1] = vi * Math.sin(a);
    				}
    				else {   // 右
    					x[i1] = canvas.width - width / 2;
    					y[i1] = width / 2 + Math.random() * (canvas.height - width);
    					let a = Math.random() * Math.PI - 0.5 * Math.PI;
    					v[i1][0] = -vi * Math.cos(a);
    					v[i1][1] = vi * Math.sin(a);
    				}
    			}
    							// 描画間隔
    			timerID = setInterval('draw()', 33);
    		}
    					// 描画
    		function draw()
    		{
    							// 跳ね返り
    			for (let i1 = 0; i1 < n; i1++) {
    				let r = 0.5 + Math.random() * 1.5;
    				if (y[i1] - width / 2 < 0) {   // 上
    					v[i1][0] *= r;
    					v[i1][1] *= -r;
    					y[i1]     = width / 2;   // 位置の修正
    				}
    				else if (y[i1] + width / 2 > canvas.height) {   // 下
    					v[i1][0] *= r;
    					v[i1][1] *= -r;
    					y[i1]     = canvas.height - width / 2;   // 位置の修正
    				}
    				else if (x[i1] - width / 2 < 0) {   // 左
    					v[i1][0] *= -r;
    					v[i1][1] *= r;
    					x[i1]     = width / 2;   // 位置の修正
    				}
    				else if (x[i1] + width / 2 > canvas.width) {   // 右
    					v[i1][0] *= -r;
    					v[i1][1] *= r;
    					x[i1]     = canvas.width - width / 2;   // 位置の修正
    				}
    				x[i1] += v[i1][0] * dt;
    				y[i1] += v[i1][1] * dt;
    			}
    							// クリア
    			ctx.clearRect(0, 0, canvas.width, canvas.height);
    							// 描画
    			for (let i1 = 0; i1 < n; i1++) {
    				ctx.beginPath();
    				ctx.fillStyle = color[i1];
    				ctx.arc(Math.floor(x[i1]), Math.floor(y[i1]), 20, 0, 2*Math.PI, false);
    				ctx.fill();
    			}
    		}
    	</SCRIPT>
    </HEAD>
    <BODY CLASS="white" STYLE="text-align: center" onLoad="start()">
    	<H1>跳ね返り(乱数)</H1>
    	<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="600" HEIGHT="400"></CANVAS>
    </BODY>
    </HTML>
    			

情報学部 菅沼ホーム JavaScript 目次 基礎技術目次 索引