静岡理工科大学 菅沼ホーム JavaScript 目次 基礎技術目次 索引

衝突判定

  1. パスと点

      二つの物体の衝突判定を行う一つの方法は,「 DOM 及び CANVAS 関係の JavaScript のプロパティとメソッド」における isPointInPath メソッドを利用する方法です.isPointInPath は既に描いたパス内に与えられた点が入っているか否かを判定します.この例では,矩形内に円の最も左側のが入ったか否かを判定しています.

    001	<!DOCTYPE HTML>
    002	<HTML>
    003	<HEAD>
    004		<TITLE>衝突判定(パスと点)</TITLE>
    005		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    006		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    007		<SCRIPT TYPE="text/javascript">
    008			canvas  = null;
    009			ctx     = null;
    010			timerID = -1;
    011			x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0, t = 0.0, v = 20.0, hit = false;
    012	
    013			function start() {
    014				canvas = document.getElementById('canvas_e');
    015				canvas.width  = 600;   // キャンバス要素の幅
    016				canvas.height = 400;   // キャンバス要素の高さ
    017				ctx = canvas.getContext('2d');
    018				x1 = 0;
    019				x2 = canvas.width - 40;
    020				y1 = canvas.height / 2 - 40;
    021				y2 = canvas.height / 2 - 40;
    022				timerID = setInterval('draw()', 33);
    023			}
    024						// 描画
    025			function draw()
    026			{
    027				if (!hit) {
    028								// 移動
    029					t += 0.1;
    030					x1 = v * t;
    031					x2 = canvas.width - 40 - v * t;
    032								// 領域のクリア
    033					ctx.clearRect(0, 0, canvas.width, canvas.height);
    034								// 矩形の描画
    035					ctx.beginPath();
    036					ctx.fillStyle = "rgb(0, 255, 0)";
    037					ctx.moveTo(x1, y1);
    038					ctx.lineTo(x1+80, y1);
    039					ctx.lineTo(x1+80, y1+80);
    040					ctx.lineTo(x1, y1+80);
    041					ctx.closePath();
    042					ctx.fill();
    043								// 以下に示すいずれの方法でも良い
    044	//				ctx.beginPath();
    045	//				ctx.lineWidth = 5;
    046	//				ctx.strokeStyle = "rgb(0, 255, 0)";
    047	//				ctx.moveTo(x1, y1);
    048	//				ctx.lineTo(x1+80, y1);
    049	//				ctx.lineTo(x1+80, y1+80);
    050	//				ctx.lineTo(x1, y1+80);
    051	//				ctx.closePath();
    052	//				ctx.stroke();
    053	
    054	//				ctx.beginPath();
    055	//				ctx.fillStyle = "rgb(0, 255, 0)";
    056	//				ctx.rect(x1, y1, 80, 80);
    057	//				ctx.fill();
    058	
    059	//				ctx.beginPath();
    060	//				ctx.lineWidth = 5;
    061	//				ctx.strokeStyle = "rgb(0, 255, 0)";
    062	//				ctx.rect(x1, y1, 80, 80);
    063	//				ctx.stroke();
    064								// 円の場合も可能
    065	//				ctx.beginPath();
    066	//				ctx.fillStyle = "rgb(0, 255, 0)";
    067	//				ctx.arc(x1, y1, 40, 0, 2*Math.PI, false);
    068	//				ctx.fill();
    069	
    070	//				ctx.beginPath();
    071	//				ctx.lineWidth = 5;
    072	//				ctx.strokeStyle = "rgb(0, 255, 0)";
    073	//				ctx.arc(x1, y1, 40, 0, 2*Math.PI, false);
    074	//				ctx.stroke();
    075								// 以下のいずれの方法でも isPointInPath による衝突判定は不可能
    076	//				ctx.beginPath();
    077	//				ctx.lineWidth = 5;
    078	//				ctx.strokeStyle = "rgb(0, 255, 0)";
    079	//				ctx.strokeRect(x1, y1, 80, 80);
    080	//				ctx.stroke();
    081	
    082	//				ctx.beginPath();
    083	//				ctx.fillStyle = "rgb(0, 255, 0)";
    084	//				ctx.fillRect(x1, y1, 80, 80);
    085	//				ctx.fill();
    086								// 衝突判定(判定位置に注意)
    087					hit = ctx.isPointInPath(x2-40, y2);
    088								// 円の描画
    089					ctx.beginPath();
    090					ctx.fillStyle = "rgb(255, 0, 0)";
    091					ctx.arc(x2, y2, 40, 0, 2*Math.PI, false);
    092					ctx.fill();
    093				}
    094			}
    095		</SCRIPT>
    096	</HEAD>
    097	<BODY CLASS="white" STYLE="text-align: center" onLoad="start()">
    098		<H1>衝突判定(パスと点)</H1>
    099		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="600" HEIGHT="400"></CANVAS>
    100	</BODY>
    101	</HTML>
    			

    011 行目

      変数 x1,y1 は,矩形の左上頂点の座標,変数 x2,y2 は,円の中心の座標です.また,変数 hit は,衝突すると true に設定されます.

    035 行目~ 042 行目,87 行目

      035 行目~ 042 行目で描画したパスを対象として,87 行目において,そのパス内に点 (x2-40, y2) が入ったか否かを判定します.044 行目~ 085 行目に示してあるように,パスの描き方は任意ですが,なぜか strokeRect や fillRect メソッドを使用すると正しく判定してくれません.

  2. パスと複数の点

      一般的には,一つの点だけで衝突判定を行っても正確な結果が得られません.この例では,円と円との衝突判定を行っています.ここでは,右図に示すように,赤色の円周上に 12 個の点を設定し,それらの点が緑色の円内に入るか否かの判定を行っています.多くの点を設定すれば,それだけ正確な衝突判定が可能になりますし,また,この考え方は任意の図形に対して適用可能です.

    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>衝突判定(パスと複数の点)</TITLE>
    05		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    06		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    07		<SCRIPT TYPE="text/javascript">
    08			canvas  = null;
    09			ctx     = null;
    10			timerID = -1;
    11			x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0, t = 0.0, v = 20.0, hit = false;
    12			x = new Array (40, 40*Math.cos(Math.PI/6), 40*Math.cos(Math.PI/3), 0, 40*Math.cos(2*Math.PI/3), 40*Math.cos(5*Math.PI/6), -40, 40*Math.cos(7*Math.PI/6), 40*Math.cos(4*Math.PI/3), 0, 40*Math.cos(5*Math.PI/3), 40*Math.cos(11*Math.PI/6));
    13			y = new Array (0, 40*Math.sin(Math.PI/6), 40*Math.sin(Math.PI/3), 40, 40*Math.sin(2*Math.PI/3), 40*Math.sin(5*Math.PI/6), 0, 40*Math.sin(7*Math.PI/6), 40*Math.sin(4*Math.PI/3), -40, 40*Math.sin(5*Math.PI/3), 40*Math.sin(11*Math.PI/6));
    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 = 40;
    21				x2 = canvas.width - 40;
    22				y1 = canvas.height / 2;
    23				y2 = canvas.height / 2 - 40;
    24				timerID = setInterval('draw()', 33);
    25			}
    26						// 描画
    27			function draw()
    28			{
    29				if (!hit) {
    30								// 移動
    31					t += 0.1;
    32					x1 = 40 + v * t;
    33					x2 = canvas.width - 40 - v * t;
    34								// 領域のクリア
    35					ctx.clearRect(0, 0, canvas.width, canvas.height);
    36								// 円の描画
    37					ctx.beginPath();
    38					ctx.fillStyle = "rgb(0, 255, 0)";
    39					ctx.arc(x1, y1, 40, 0, 2*Math.PI, false);
    40					ctx.fill();
    41								// 衝突判定(判定位置に注意)
    42					for (var i1 = 0; i1 < 12; i1++) {
    43						hit = ctx.isPointInPath(x2+x[i1], y2+y[i1]);
    44						if (hit)
    45							break;
    46					}
    47								// 円の描画
    48					ctx.beginPath();
    49					ctx.fillStyle = "rgb(255, 0, 0)";
    50					ctx.arc(x2, y2, 40, 0, 2*Math.PI, false);
    51					ctx.fill();
    52				}
    53			}
    54		</SCRIPT>
    55	</HEAD>
    56	<BODY CLASS="white" STYLE="text-align: center" onLoad="start()">
    57		<H1>衝突判定(パスと複数点)</H1>
    58		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="600" HEIGHT="400"></CANVAS>
    59	</BODY>
    60	</HTML>
    			

    12 行目~ 13 行目

      配列に,12 個の点の座標を設定しています.このような形で,配列の初期設定を行うことができます.

    42 行目~ 46 行目

      緑色の円と 12 個の各点との衝突判定を行っています.なお,break 文が実行されると,break 文が入っている最も内側のループの外に出て(ループの処理を中止して),そのループの次の文が実行されます.

  3. 距離

      最後に示す方法は,2 つの図形間の距離を計算し,その距離によって衝突を判定する方法です.この例に示すように,2 つの図形が円や点の場合は問題ありませんが,他の図形の場合は,図形の端点間の距離の最小値が,図形やその向きによって異なるため,簡単に衝突判定ができるとは限りません.

    <!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;
    		x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0, t = 0.0, v = 20.0, hit = false;
    
    		function start() {
    			canvas = document.getElementById('canvas_e');
    			canvas.width  = 600;   // キャンバス要素の幅
    			canvas.height = 400;   // キャンバス要素の高さ
    			ctx = canvas.getContext('2d');
    			x1 = 40.0;
    			x2 = canvas.width - 40;
    			y1 = canvas.height / 2;
    			y2 = canvas.height / 2 - 40;
    			timerID = setInterval('draw()', 33);
    		}
    					// 描画
    		function draw()
    		{
    			if (!hit) {
    							// 移動
    				t += 0.1;
    				x1 = 40 + v * t;
    				x2 = canvas.width - 40 - v * t;
    							// 描画
    				ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    				ctx.beginPath();
    				ctx.fillStyle = "rgb(0, 255, 0)";
    				ctx.arc(x1, y1, 40, 0, 2*Math.PI, false);
    				ctx.fill();
    
    				ctx.beginPath();
    				ctx.fillStyle = "rgb(255, 0, 0)";
    				ctx.arc(x2, y2, 40, 0, 2*Math.PI, false);
    				ctx.fill();
    							// 衝突判定
    				var x = x1 - x2;
    				var y = y1 - y2;
    				if (Math.sqrt(x*x+y*y) <= 80)
    					hit = true;
    			}
    		}
    	</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>
    			

  4. もう一つの例

      この例では,四方のランダムな位置からが出現し,中央の三角形に向かって進みます.三角形と衝突後,しばらくすると,次の円が別の位置から出現します.衝突判定を行う点は,三角形の頂点及び各辺に設定された 4 つの,合計 15 個です.プログラムとしては,先に述べた例のように,円に衝突判定を行う点を設定した方が簡単です.点の数が少ないため,衝突する位置によっては,三角形に多少めり込んだ位置で衝突判定が行われます.

    001	<!DOCTYPE HTML>
    002	<HTML>
    003	<HEAD>
    004		<TITLE>衝突判定</TITLE>
    005		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
    006		<LINK REL="stylesheet" TYPE="text/css" HREF="../../master.css">
    007		<SCRIPT TYPE="text/javascript">
    008			canvas  = null;
    009			ctx     = null;
    010			timerID = -1;
    011			x       = 0.0;   // 位置(x)
    012			y       = 0.0;   // 位置(y)
    013			dt      = 0.1;   // 時間刻み幅
    014			hit     = false;   // 命中中か否か
    015			ang     = 0.0;   // 時間
    016			var v   = 50;   // 速度
    017			var vx  = 0.0;   // 速度(x)
    018			var vy  = 0.0;   // 速度(y)
    019			r       = 20;   // 円の半径
    020			x1 = 0;   // 三角形の頂点
    021			y1 = 0;
    022			x2 = 0;
    023			y2 = 0;
    024			x3 = 0;
    025			y3 = 0;
    026			px = new Array();   // 衝突判定位置
    027			py = new Array();
    028			count = 0;
    029	
    030			function start() {
    031				canvas = document.getElementById('canvas_e');
    032				canvas.width  = 600;   // キャンバス要素の幅
    033				canvas.height = 400;   // キャンバス要素の高さ
    034				ctx = canvas.getContext('2d');
    035						// 衝突判定位置
    036				var cx = canvas.width / 2;
    037				var cy = canvas.height / 2;
    038				x1 = cx;
    039				y1 = cy - 50.0 / Math.cos(Math.PI/6);
    040				x2 = cx - 50.0;
    041				y2 = cy + 50.0 * Math.tan(Math.PI/6);
    042				x3 = cx + 50.0;
    043				y3 = cy + 50.0 * Math.tan(Math.PI/6);
    044	
    045				var k = 0;
    046				px[k] = x1;
    047				py[k] = y1;
    048				k++;
    049				var sx = (x2 - x1) / 5;
    050				var sy = (y2 - y1) / 5;
    051				for (var i1 = 1; i1 < 5; i1++) {
    052					px[k] = x1 + i1 * sx;
    053					py[k] = y1 + i1 * sy;
    054					k++;
    055				}
    056				px[k] = x2;
    057				py[k] = y2;
    058				k++;
    059				sx = (x3 - x2) / 5;
    060				sy = (y3 - y2) / 5;
    061				for (var i1 = 1; i1 < 5; i1++) {
    062					px[k] = x2 + i1 * sx;
    063					py[k] = y2 + i1 * sy;
    064					k++;
    065				}
    066				px[k] = x3;
    067				py[k] = y3;
    068				k++;
    069				sx = (x1 - x3) / 5;
    070				sy = (y1 - y3) / 5;
    071				for (var i1 = 1; i1 < 5; i1++) {
    072					px[k] = x3 + i1 * sx;
    073					py[k] = y3 + i1 * sy;
    074					k++;
    075				}
    076						// 初期設定
    077				ini_set();
    078	
    079				timerID = setInterval('draw()', 33);
    080			}
    081						// 初期設定
    082			function ini_set()
    083			{
    084				var p = Math.random() * 2 * (canvas.width + canvas.height);
    085				if (p < canvas.width) {   // 上
    086					x   = r + Math.random() * (canvas.width - 2 * r);
    087					y   = r;
    088				}
    089				else if (p < canvas.width + canvas.height) {   // 右
    090					x   = canvas.width - r;
    091					y   = r + Math.random() * (canvas.height - 2 * r);
    092				}
    093				else if (p < 2 * canvas.width + canvas.height) {   // 下
    094					x   = r + Math.random() * (canvas.width - 2 * r);
    095					y   = canvas.height - r;
    096				}
    097				else {   // 左
    098					x   = r;
    099					y   = r + Math.random() * (canvas.height - 2 * r);
    100				}
    101				ang = Math.atan2(canvas.height/2-y, canvas.width/2-x);
    102				vx  = v * Math.cos(ang);
    103				vy  = v * Math.sin(ang);
    104			}
    105						// 描画
    106			function draw()
    107			{
    108				if (count == 0) {
    109								// 領域のクリア
    110					ctx.clearRect(0, 0, canvas.width, canvas.height);
    111								// 円の描画
    112					ctx.beginPath();
    113					ctx.fillStyle = "rgb(0, 255, 0)";
    114					ctx.arc(x, y, r, 0, 2*Math.PI, false);
    115					ctx.fill();
    116								// 衝突判定
    117					for (var i1 = 0; i1 < 15; i1++) {
    118						hit = ctx.isPointInPath(px[i1], py[i1]);
    119						if (hit)
    120							break;
    121					}
    122					if (hit)
    123						count = 1;
    124					else {
    125						x += vx * dt;
    126						y += vy * dt;
    127					}
    128								// 三角形の描画
    129					ctx.beginPath();
    130					ctx.fillStyle = "rgb(255, 0, 0)";
    131					ctx.moveTo(x1, y1);
    132					ctx.lineTo(x2, y2);
    133					ctx.lineTo(x3, y3);
    134					ctx.closePath();
    135					ctx.fill();
    136				}
    137				else {
    138					count++;
    139					if (count == 30) {
    140						count = 0;
    141						ini_set();
    142					}
    143				}
    144			}
    145		</SCRIPT>
    146	</HEAD>
    147	<BODY CLASS="white" STYLE="text-align: center" onLoad="start()">
    148		<H1>衝突判定(円と三角形)</H1>
    149		<CANVAS ID="canvas_e" STYLE="background-color: #eeffee;" WIDTH="600" HEIGHT="400"></CANVAS>
    150	</BODY>
    151	</HTML>
    			
    036 行目~ 043 行目

      三角形の 3 つの頂点(上,下左,下右)の座標を設定しています.

    045 行目~ 075 行目

      三角形上の衝突判定を行う点の座標を計算し,配列 px 及び py に設定しています.

    082 行目~ 104 行目

      初期設定を行う関数であり,出現位置,及び,進行方向をランダムに決めています.atan2 は,Math オブジェクトのメソッドであり,逆正接の計算,つまり,atan2(y, x) は,tan(a) = y/x となるような角度 a をラジアン単位で返します.従って,101 行目の計算により,すべての円は,キャンバスの中心に向かって移動することになります.

    108 行目

      この if 文によって,衝突していない場合は( count が 0 の場合は),109 行目~ 135 行目を実行します.117 行目~ 121 行目の衝突判定によって,衝突が判明した場合は変数 count を 1 に設定し,さもなければ,円の移動を続行します( 125,126 行目).

    137 行目

      count が 0 でない場合は,138 行目~ 142 行目を実行します.count を 1 だけ増加させ,その値が 30 になった場合は,count を 0 に設定し,初期設定を行う関数 ini_set によって,新しい円を出現させます.count が 30 になるまでは何も行いませんので,ほぼ 1 秒間( 33×30,079 行目),衝突した状態が表示されます.

静岡理工科大学 菅沼ホーム JavaScript 目次 基礎技術目次 索引