絶対値を求める命令 Math.abs
(x+w/2) - (X+W/2) と (y+h/2) - (Y+H/2) は位置関係により、値がプラスになったりマイナスになったりしますので、
Math.abs命令を使って、それぞれの絶対値で調べます。
01 | <!DOCTYPE html> |
---|---|
02 | <html lang="ja"> |
03 | <head> |
04 | <meta charset="utf-8"> |
05 | <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0 user-scalable=no"> |
06 | <title>JavaScriptのテストプログラム</title> |
07 | </head> |
08 | <body> |
09 | <canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto;" id="bg"></canvas> |
10 | <script> |
11 | var winW = window.innerWidth; |
12 | var winH = window.innerHeight; |
13 | var canvas = document.getElementById("bg"); |
14 | canvas.width = winW; |
15 | canvas.height = winH; |
16 | var cnt = canvas.getContext("2d"); |
17 | cnt.font = "32px monospace"; |
18 | cnt.textAlign = "center"; |
19 | |
20 | var SCALE = winW / 800; |
21 | cnt.scale( SCALE, SCALE ); |
22 | |
23 | //マウスとタップの判定 |
24 | var tapX = 0, tapY = 0, tapC = 0; |
25 | |
26 | if( 'ontouchend' in document ) { |
27 | canvas.addEventListener( "touchstart", touchStart ); |
28 | canvas.addEventListener( "touchmove", touchMove ); |
29 | canvas.addEventListener( "touchend", touchEnd ); |
30 | function touchStart(event) { |
31 | event.preventDefault(); |
32 | var rect = event.target.getBoundingClientRect(); |
33 | tapX = event.touches[0].clientX-rect.left; |
34 | tapY = event.touches[0].clientY-rect.top; |
35 | transformXY(); |
36 | tapC = 1; |
37 | } |
38 | function touchMove(event) { |
39 | event.preventDefault(); |
40 | var rect = event.target.getBoundingClientRect(); |
41 | tapX = event.touches[0].clientX-rect.left; |
42 | tapY = event.touches[0].clientY-rect.top; |
43 | transformXY(); |
44 | } |
45 | function touchEnd(event) { |
46 | event.preventDefault(); |
47 | tapC = 0; |
48 | } |
49 | } |
50 | else { |
51 | canvas.addEventListener( "mousedown", mouseDown ); |
52 | canvas.addEventListener( "mousemove", mouseMove ); |
53 | canvas.addEventListener( "mouseup", mouseUp ); |
54 | function mouseDown(event) { |
55 | var rect = event.target.getBoundingClientRect(); |
56 | tapX = event.clientX-rect.left; |
57 | tapY = event.clientY-rect.top; |
58 | transformXY(); |
59 | tapC = 1; |
60 | } |
61 | function mouseMove(event) { |
62 | var rect = event.target.getBoundingClientRect(); |
63 | tapX = event.clientX-rect.left; |
64 | tapY = event.clientY-rect.top; |
65 | transformXY(); |
66 | } |
67 | function mouseUp(event) { |
68 | tapC = 0; |
69 | } |
70 | } |
71 | |
72 | function transformXY() { |
73 | tapX = toInt(tapX/SCALE); |
74 | tapY = toInt(tapY/SCALE); |
75 | } |
76 | |
77 | function toInt( val ) { |
78 | return parseInt(val); |
79 | } |
80 | |
81 | //描画用の関数 |
82 | function fText( str, x, y, col ) {//文字表示 |
83 | cnt.fillStyle = col; |
84 | cnt.fillText( str, x, y ); |
85 | } |
86 | |
87 | function fRect( x, y, w, h, col ) {//矩形 |
88 | cnt.fillStyle = col; |
89 | cnt.fillRect( x, y, w, h ); |
90 | } |
91 | |
92 | //ヒットチェックを行う関数 |
93 | function hitCheck( x1, y1, w1, h1, x2, y2, w2, h2 ) { |
94 | var dx = Math.abs( (x1+w1/2) - (x2+w2/2) ); |
95 | var dy = Math.abs( (y1+h1/2) - (y2+h2/2) ); |
96 | if( dx < w1/2+w2/2 && dy < h1/2+h2/2 ) return true; |
97 | return false; |
98 | } |
99 | |
100 | var x = 100, y = 100, w = 160, h = 240; |
101 | var X = 400, Y = 200, W = 360, H = 280; |
102 | |
103 | window.onload = mainProc(); |
104 | function mainProc() { |
105 | fRect( 0, 0, 800, 1200, "#000" ); |
106 | |
107 | if( tapC == 1 ) { |
108 | if( x < tapX && tapX < x+w && y < tapY && tapY < y+h ) { |
109 | x = tapX-w/2; |
110 | y = tapY-h/2; |
111 | } |
112 | else if( X < tapX && tapX < X+W && Y < tapY && tapY < Y+H ) { |
113 | X = tapX-W/2; |
114 | Y = tapY-H/2; |
115 | } |
116 | } |
117 | |
118 | fRect( x, y, w, h, "#00f" ); |
119 | fRect( X, Y, W, H, "#f00" ); |
120 | |
121 | if( hitCheck( x, y, w, h, X, Y, W, H ) == true ) fText( "2つの矩形は重なっている", 400, 200, "#ff0" ); |
122 | |
123 | setTimeout( mainProc, 50 ); |
124 | } |
125 | </script> |
126 | </body> |
127 | </html> |
function hitCheck( x1, y1, w1, h1, x2, y2, w2, h2 ) {
var dx = Math.abs( (x1+w1/2) - (x2+w2/2) );
var dy = Math.abs( (y1+h1/2) - (y2+h2/2) );
if( dx < w1/2+w2/2 && dy < h1/2+h2/2 ) return true;
return false;
}
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0 user-scalable=no">
<canvas style="position:absolute; top:0; bottom:0; left:0; right:0; margin:auto;" id="bg"></canvas>
var winW = window.innerWidth;
var winH = window.innerHeight;
及び
canvas.width = winW;
canvas.height = winH;
var SCALE = winW / 800;
cnt.scale( SCALE, SCALE );
function transformXY() {
tapX = toInt(tapX/SCALE);
tapY = toInt(tapY/SCALE);
}
toInt()は値を整数にする関数で、これもソース内で定義しています。
Math.floor と parseInt
プログラミングでは数値の少数点以下を切り捨て整数にして使うことがあります。
少数点以下を切る命令には Math.floor(値) と parseInt(値) があります。
Math.floorは1-3(3)で説明したようにマイナスの値の時に注意が必要です。
parseIntはプラスの値でもマイナスの値でも小数点以下を切り捨てます。
今回のソースコードのtoInt関数にはparseIntを用いています。
※厳密にはMath.floorは数値を扱い、parseIntは数値と文字列を扱うことができ、
これら2つの命令は挙動の詳細は違うのですが、parseIntは少数部分を切り捨てる命令と考えて問題ございません。
if( dx < w1/2+w2/2 && dy < h1/2+h2/2 ) return true;
↓
if( dx < (w1/2+w2/2)*0.9 && dy < (h1/2+h2/2)*0.9 ) return true;
√(x1-x2)^2 + (y1-y2)^2
平方根を求めるJavaScriptの命令は Math.sqrt() です。
n乗を書き表すのに使う ^ の記号は、JavaScriptでは別の意味(ビット演算子)になりますので、
上図のキャラクターの距離は次のように記述します。
Math.sqrt( (x-X)*(x-X) + (y-Y)*(y-Y) );
もしくはn乗を求める命令Math.pow( 底数, 指数 )を用い、Math.sqrt( Math.pow(x-X,2) + Math.pow(y-Y,2) );
ビット演算子
プログラミングの世界にはビット演算子といわれるものがあります。
・ビットをシフトする << 、 >>
・AND(論理積)を求める &
・OR(論理和)を求める |
・XOR(排他的論理和)を求める ^
・1との補数を求める ~
ビット演算子の説明は、現時点では本講座の目指すところ(みなさんがゲームを作れるようになるという目標)を超えた範囲ですので、割愛しますが、
プロのゲームプログラマーを目指す方にとってはビット演算子の知識も必要となりますので、そのような方はぜひネットで調べてみて下さい。
81 | //描画用の関数 |
---|---|
82 | function fText( str, x, y, col ) {//文字表示 |
83 | cnt.fillStyle = col; |
84 | cnt.fillText( str, x, y ); |
85 | } |
86 | |
87 | function fRect( x, y, w, h, col ) {//矩形 |
88 | cnt.fillStyle = col; |
89 | cnt.fillRect( x, y, w, h ); |
90 | } |
91 | |
92 | function fArc( x, y, r, col ) {//円 |
93 | cnt.fillStyle = col; |
94 | cnt.beginPath(); |
95 | cnt.arc( x, y, r, 0, Math.PI*2, false ); |
96 | cnt.fill(); |
97 | cnt.restore(); |
98 | } |
99 | |
100 | //ヒットチェックを行う関数 |
101 | function hitCheck2( x1, y1, r1, x2, y2, r2 ) { |
102 | var dis = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) ); |
103 | if( dis < r1+r2 ) return true; |
104 | return false; |
105 | } |
106 | |
107 | var x = 200, y = 200, r = 120; |
108 | var X = 600, Y = 300, R = 180; |
109 | |
110 | window.onload = mainProc(); |
111 | function mainProc() { |
112 | fRect( 0, 0, 800, 1200, "#000" ); |
113 | |
114 | if( tapC == 1 ) { |
115 | if( x-r < tapX && tapX < x+r && y-r < tapY && tapY < y+r ) { |
116 | x = tapX; |
117 | y = tapY; |
118 | } |
119 | else if( X-R < tapX && tapX < X+R && Y-R < tapY && tapY < Y+R ) { |
120 | X = tapX; |
121 | Y = tapY; |
122 | } |
123 | } |
124 | |
125 | fArc( x, y, r, "#0f0" ); |
126 | fArc( X, Y, R, "#a0f" ); |
127 | |
128 | if( hitCheck2( x, y, r, X, Y, R ) == true ) fText( "2つの円は重なっている", 400, 200, "#ff0" ); |
129 | |
130 | setTimeout( mainProc, 50 ); |
131 | } |
132 | </script> |
133 | </body> |
134 | </html> |
function hitCheck2( x1, y1, r1, x2, y2, r2 ) {
var dis = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
if( dis < r1+r2 ) return true;
return false;
}
◇コラム◇ ヒットチェックは甘めで行きましょう
無料のアプリが大量に配信される現在では、ユーザーが遊ぶゲームはいくらでもあります。
これはクリアできないと感じるゲームはすぐに削除されるのが現実です。
ですので今のゲームは甘いくらいがちょうど良いでしょう。
多くのユーザーが先まで遊べるやさしめの難易度が良いというわけです。
◇コラム◇ プログラマーを目指す中高生へ、今回は真面目な話です
ゲームの開発環境によっては便利な命令が用意されており、ここで触れたように、命令の使い方さえ覚えれば理屈を知らなくとも、三次元のヒットチェックもできるようになっています。
例えばUnityというツールは使い方を学べば、どなたでも高度なゲームを完成させることができます。
学校であるいは独学でUnityを学び、ゲーム会社の募集にUnityで作ったゲームを送り、面接に進めたとします。
企業はプログラマーとして本当に使える人材であるか試すために技術的な質問をします。
プログラミングの根底にある技術を知らないと、正しい受け答えは難しく、残念ながら不採用となってしまうでしょう。
その技術とはずばり“数学”と“物理”です。
また世の中に普及しているプログラミング言語は“英語”です。
コンピューターの最新の技術情報はまず英語のサイトに掲載されることが多く、
プログラマーは英語で書かれた情報を調べることもあります。
プログラマーを目指す諸君は理系の教科と英語を頑張って下さい。
・・・と難しいことを書きましたが、趣味のゲーム制作ではそこまでシビアに考える必要はございません。
本講座はプログラミング初心者の方に理解して頂ける内容で連載を続けて参ります。