絶対値と二乗の平方根と当たり判定
2020/07/05

本記事の説明に使う画像を悪戦苦闘しながら作っていてだいぶできたところでアプリケーションが落ちるという事故に遭遇しました。。。 1時間くらいの作業が無駄に。 同じことをやりなおすのはさすがに早くて30分もかからずに済んだけれど、こまめにセーブしよう。。。
さて本日は平成31年度 センター試験 数学I 第1問に出てくる以下の数式がちょっと面白いなと思ったので、これを使ってお話してみようと思います。
\( | a+2 | + \sqrt{ (2a-3)^2 } \)
※問題の解答を含んでしまうので若干変えてあります。
絶対値
| と | に囲まれた部分は絶対値(ぜったいち)と言って、同じ大きさのプラスの値を表します。 つまり \( | 3 | \) は \( 3 \)、\( | -3 | \) も \( 3 \)、\( | -256 | \) は \( 256 \)です。 数式を使うと \( |x| \) は、\(x\)がプラスならそのまま、マイナスなら \(-x\)ですね。 ややこしいのですがマイナスのマイナスはプラスで、\( x = -3 \) のときの \( -x \) は(プラスの) \( 3 \) です。
平方根
次の√はルートと読みます。 日本語で平方根(へいほうこん)と言い、二乗するとその中の数になる数値です。 例えば \( \sqrt{9} \) は \(3\)、\( \sqrt{16} \) は \(4\)です。 なので \( \sqrt{(2a-3)^2} = (2a-3) \) …と思ってしまいますがひっかかってはダメです!
というのは例えば \( a = 0 \) のとき\( \sqrt{(2a-3)^2} \) は \( \sqrt{(-3)^2} \) になりますが、それは \(3\) すなわち \( -(2a-3) \) になるからです。 二乗する前の値がマイナスなら前にマイナスをつけてプラスにしないとですね。ややこしい。 一般化すると \( \sqrt{x^2} \) は、\(x\)がプラスならそのまま、マイナスなら \(-x\)ということになります。
絶対値と二乗の平方根
私はこれが面白いと思うのですが、そうなんですよね。絶対値と二乗の平方根って同じなのです。 だから問題の数式は \( |a+2| + |2a-3| \) と置き換えてしまった方がわかりやすいでしょう。
・\( a \ge \frac{3}{2} \) のときはどちらの中もプラス
・\( a \lt -2 \) ならどちらも中がマイナス
・\( -2 \le a \lt \frac{3}{2} \) のときは \( |2a-3| \) の中だけがマイナス
と3パターンに分けられるというのがわかりやすくなるかなと。
そして \( -2 \le a \lt \frac{3}{2} | \) のときの \( | a+2 | + \sqrt{ (2a-3)^2 } \) を求めよと言われたら、 右の中だけプラスマイナスを反転する形で \( (a+2)+(-(2a-3)) = -a + 5 \) と計算できます。
絶対値による当たり判定

その中では2Dゲームの当たり判定がわかりやすそうかなと思いますので紹介します。 図のボールは半径が16、キャラクターは横幅32、縦幅64くらいです。 ですので横については\(|ballX-playerX|\)が32以下なら、縦は\(|ballY-playerY|\)が48以下なら当たっていると判定することができます。 これが当たり判定ですね。
そしてそれは、横なら\(\sqrt{(ballX-playerX)^2}\)が32以下かどうかで判定するのと同じです。
プログラミングして試してみよう
ではこれらを考慮して当たり判定のプログラムを試してみましょう。 プログラミングできる場所を用意してみました。

PRESS START
絶対値で横の当たり判定
まずは横の座標だけを見ることにします。以下のプログラムをコピーして、白い枠内に貼り付けて、「ゲームスタート」を実行してみてください。 キャラクターとボールの横の位置が重なったら左上に「HIT!」と表示されれば成功です。 縦の位置はここでは関係ありません。
g.dX = g.ballX - g.playerX; if ( g.dX < 0 ) { g.dX = -g.dX; } if ( g.dX < 32 ) { g.hitS = "HIT!"; } else { g.hitS = ""; }
=は代入
最初の行には「=」(イコール)がありますが、これは数学と異なり、右辺を左辺に代入することを意味します。
g.dX = g.ballX - g.playerX;
この行はすなわち、 g.dX に g.ballX-g.playerX の計算結果を入れる処理です。 このg.dXのような、値を入れておく場所を変数と呼びます。
ifで条件分岐
次の3行が横の座標の差分を絶対値を取得するプログラムです。
if ( g.dX < 0 ) { g.dX = -g.dX; }
if文は条件分岐で、()内が成立すれば、続く{}内を実施します。成立しなければ実行しません。 つまりこのプログラムは、変数g.dXの値がマイナスなら、g.dXの符号を反転する処理になります。
結果をg.hitSに入れて表示!
本記事では変数 g.hitS に文字列を入れると、それをスクリーン左上に表示するようにしました。
if ( g.dX < 32 ) { g.hitS = "HIT!"; } else { g.hitS = ""; }
つまりこのプログラムで、g.dXが32未満なら、すなわち \(|g.ballX - g.playerX|\)が32未満なら、 つまり横の位置が重なっていたら、HIT! という文字列を表示するプログラムになります。
「"」(ダブルクォーテーション)で囲まれたものが文字列です。 当たっていないときには、表示を消すために "" という「何も無い文字列」を代入しています。 elseの後の{}は、ifの()内の条件が成立していないときに処理されます。
絶対値で縦も当たり判定
次に縦の当たり判定も加えてみます。基本は同じですが、両方とも満たした場合のみHIT!にします。 まず「ストップ」押して動きを止めてから、プログラム枠内を以下に書き換えて「ゲームスタート」を押してみてください。
g.dX = g.ballX - g.playerX; if ( g.dX < 0 ) { g.dX = -g.dX; } g.dY = g.ballY - g.playerY; if ( g.dY < 0 ) { g.dY = -g.dY; } if ( g.dX < 32 && g.dY < 48 ) { g.hitS = "HIT!"; } else { g.hitS = ""; }
絶対値で縦座標比較
以下のプログラムで、縦の座標の差分を絶対値で取得し、g.dYに代入しています。
g.dY = g.ballY - g.playerY; if ( g.dY < 0 ) { g.dY = -g.dY; }
&&で「且つ」
ifの()内にある「&&」(この読みは…普段はアンドアンドと読んでいますがたぶん正式にはアンパサンドアンパサンド)は論理積を表します。 「且つ」ですね。両方とも成立して初めて成立することを意味します。
if ( g.dX < 32 && g.dY < 48 ) { g.hitS = "HIT!"; } else { g.hitS = ""; }
すなわちこのプログラムは、g.dXが32未満で、g.dYが48未満、その両方の条件を満たしたときに次の{}内を処理します。 これにより、縦にも横にも当たっているときに限り "HIT!" と表示されるようになりました!
二乗で当たり判定
では今度は、二乗の平方根でやってみましょう。 ただし平方根は現在のコンピューターではまだ処理が遅いので実際には行わず、 比較する値を二乗することで同じ意味になるようにします。
g.dX = g.ballX - g.playerX; g.dX = g.dX * g.dX; g.dY = g.ballY - g.playerY; g.dY = g.dY * g.dY; if ( g.dX < 32*32 && g.dY < 48*48 ) { g.hitS = "HIT!"; } else { g.hitS = ""; }
*は掛け算
式に登場した「*」(アスタリスク)は乗算、掛け算を表します。 つまり、
g.dX = g.dX * g.dX;
この行は、g.dXを二乗する処理になります。
ちなみに 32*32 の方は*の前後にスペースを開けていませんが、開けても開けていなくても変わりません。 本記事で使用しているJavaScriptは、そのあたりを区別しないプログラミング言語です。 JavaScriptはWeb業界で一般的に使われているものですし、他の言語もスペースの有無は関係ない場合が多いです。
二乗の方がスッキリ?
こうしてプログラムを比較すると、二乗を使用した方が行数が減りました。 そのぶんスッキリ読みやすいプログラムに見えると思います。 しかし、2Dゲームが主流の時代は掛け算はかなり遅い処理でしたので、掛け算を使用しないプログラミングが普通だったと思います。 要は絶対値の方がベターでしたね。
今のコンピューターは掛け算が高速になりました。 またifのような条件分岐の処理が遅いマシンもあります。 まあ分岐が遅いというより、分岐が無ければチョッパヤということなのですが。 ですのでむしろ二乗を使った方が高速になる場合があります。
円を使用した当たり判定

図のような形で円に見立て、円が重なるかどうかで判定します。 円が重なるかどうかは、中心同士の距離が一定数より近いかどうかで判定できます。 一定数というのはそれぞれの円の半径の和ですね。
実際のプログラムを以下に記しました。 プログラムはさらに短くなり、とてもスッキリしていますよね。 あ、こちらも平方根は省略して、比較する値の方を二乗しています。
g.dX = g.ballX - g.playerX; g.dY = g.ballY - g.playerY; g.d = g.dX*g.dX + g.dY*g.dY; if ( g.d < 40*40 ) { g.hitS = "HIT!"; } else { g.hitS = ""; }
この考え方は3次元の球でも同様にできます。 しかも変わらずスッキリしています。 ゲームの主流が3Dになり、掛け算が高速になって以降は、球できる当たり判定は球で行うことが多いと思います。
まとめ
絶対値と二乗の平方根および当たり判定について解説しました。
絶対値の、この場合はこう、この場合はこう、という考え方は、プログラムに通じるものがあるなあと思います。
補足
・センター試験の問題は独立学校法人 大学入試センターさんからです。・\( |x| \) も \( \sqrt{x^2} \) も \( x = 0 \) なら\( 0 \)です。
・本記事は\(a\)や\(x\)が実数であること前提にしています。センター試験の問題でも実数と明記されています。
・実際には2Dゲームの当たり判定を絶対値でプログラミングはしないかも知れません。より高速な計算方法があればそちらを使います。
・数式表現にMathJaxを使用しております。助かります!
・画像内のラスタライズ文字フォントにOpen Font LicenseのZen Antiqueを使用しております。
カテゴリー:メガホンDEポン,ゲームの数学
Copyright (C) Logic Lovers Inc.