私たちが日本の教育で高校で習うと思われる三角関数、1:2:√3 だとか、1.41421356(ひとよひとよにひとみごろ)だとか、1.7320508(ひとなみにおごれや)だとか、こんなもん覚えて何の役に立つんだと憤慨したそこのあなた!具体的な勉強理由を教えましょう。それは漢の浪漫!弾幕を作るためです!美しく広がる敵弾に惚れ惚れして見とれて撃沈してしまった、あの処理を作るために三角関数は存在しているのです!(誇張表現
- 最初の壁はラジアン
DEG と RAD は、360 = 2×3.1415926 という関係にあります。そのため…
このような関係にあります。剰除算は順番を変えても問題ないので、
RAD = DEG * 2 * 3.1415926 / 360
DEG = RAD * 360 / 2 / 3.1415926
このように約す事が出来ます。C# や C++ 等のコンパイラにはほぼ必ず 3.1415926 のπは定数定義されているので、それを用いると
RAD = DEG * (2 * 3.1415926 / 360) = DEG * 0.01745329
DEG = RAD * (360 / 2 / 3.1415926) = RAD * 57.2957805
const double DegToRad = 2 * Math.PI / 360;
const double RadToDeg = 360 / 2 / Math.PI;
このように定数定義しておけば。いつでも簡単に RAD と DEG に相互変換が可能になります。これ、Unity では最初から定数として定義されていたりします。137度をラジアンにしたければ、137 * DegToRad と記述するだけです。
double radAngle = 137 * DegToRad;
- ベクトルは方向と長さを表す

円の中心から矢印が伸びています。この矢印がベクトルです。長さが変わらないという事は、円の半径を意味します。そのため方向だけ変えると、矢印の先端は円を描く事になります。なんとなく弾幕が見えてきていないですか?速度が速くなれば、この矢印は長くなります。遅くなれば短くなります。ここに直角三角形を描いてみます。
なんか学校で習ったような、なんとなく見覚えのあるような、そんな図形になってきました。ゲームの座標は直交座標系です。これは、横と縦を X,Y方向としています(2Dの場合)。3DはここにZ軸が入るのですが、今回は説明を簡易とするために XY座標だけで話を進めます。ピタゴラスとか三平方の定理とか習いませんでしたか? X の二乗と Y の二乗を足すと長さの二乗に等しいというアレです。
この計算式が成り立つのであれば、長さと角度から XY の長さを求める事が出来るはずです。
double length = sqrt(cptX * cptX + cptY * cptY);
コンピュータの世界ではルート計算は「異様に重い」んです。そこで偉い人は思いました。X成分とY成分は、長さが2倍になれば成分も2倍になるから単純な比率で出る。だったら、角度に応じた定数で掛け算するだけで、XY成分は取り出せんじゃねぇの?…そう、これが三角関数です。三角関数とは元々計算をラクにするために生み出されたモノなのです。
double X = sqrt(length * length - cptY * cptY);
double Y = sqrt(length * length - cptX * cptX);
※ 諸説きっとあります。
- SIN サイン

sinθは Y方向÷長さの値が取り出せます。という事は、sinθ × 長さとすると、Y成分が取り出せる事になります。どうでも良いですが、高校の時にいきなり説明で角度の事をθ(シータ)と言われて面食らいませんでしたか?あ、今はお受験で中学校でもう知ってる?あ、そうですか…。しーたあああぁああああ!(天空の城)…げほごほ、さ、さて気をとりなおして、C言語的には以下のような記述となります。
double cptY = sin(angle * DegToRad) * length;
あと、この図に SIN というラクガキがありますが、それ、私が高校の時に数学の担当教師が「SINの覚え方だー、いいかー、S を筆記体で書くとこうだろー、長さ分の高さだーわかったかー」と
あまりにも分かりづらくて理不尽だったので、ここに晒しておきますw

あまりにも分かりづらくて理不尽だったので、ここに晒しておきますw
- COS コサイン

cosθは見ての通り X方向÷長さの比率です。ゲームプログラミング的に結論言っちゃうと、長さにcosθ掛けちゃうとX成分が取り出せます。えっ、長さの方向が逆になってるって? そ、そうね、だいたいね…(ちっ、気がつかなきゃいいのに)。長さ分のX方向と示すためで、長さは長さとして変わらないので、こまけーこたぁいぃんだよ、この際!(ぉ
double cptX = cos(angle * DegToRad) * length;
なお、この図にも理不尽筆記体 COSのラクガキがあります

- TAN タンジェント

tanθは Y方向÷X方向の比率を表します。…が、ゲーム制作においてはそんな事実はどうでもよくて(例の tanのラクガキもどうでもよくて)、次の図をご覧ください。

自分から見て敵の位置方向を、ベクトルとして捉える事が出来ますよね。自分の座標 X1,Y1 と敵の位置 X2, Y2 は当然分かると思いますので、X成分と Y成分は X2 - X1, Y2 - Y1 で得られるのは分かるかと思います。ということは!なんと、タンジェントを逆引きすれば、今度は角度が得られる事になります。敵の位置を基準とすれば、敵から自機を攻撃する方向を得る事が出来ます。
そして、これが重要なのですが、C言語とか Unityで使用されている C# にはこの逆引きを行う ATAN(アークタンジェント)という関数が最初から用意されているのです。しかぁも!まるでゲームで使用する事を前提としたような ATAN2 いう関数は、なんとなんと X成分と Y成分を引数として与えるだけで、相手方向の角度が返ってくるのです。例えば、敵から自機を狙う方向は
たったこれだけです。ただ注意点が一つ。コンパイラの種類によって、この ATANの引数が X,Y だったり Y,X だったりで統一されていません。これは注意してください。
double angle = atan2(playerX - enemyX, playerY - enemyY);
コンバスは丸い線を書く道具として有名です。丸く広がるという事は、このコンバスの半径の長さを 1cm, 2cm, 3cm と広げていく様とよく似ています。発射した位置が中心点です。この中心点から同心円的に位置が移動していくのが丸く広がる弾幕の理屈となります。

例えば、32方向に弾を同時に射出したい場合は、32個分の角度から計算した X,Y成分を持つ構造体を用意します。この構造体こそベクトルなのです。2次元のベクトルなので、C言語系ではよく Vector2 クラスとしてシステムに定義済みだったりします。
あとは動かすタイミング毎に弾の位置 posBullets を更新していきます。
const int MaxBullets = 32;
Point posBullets = new Point[MaxBullets];
Vector2 vecBullets = new Vector2[MaxBullets];
for (int idx = 0; idx < MaxBullets; ++idx)
{
double angle = 2 * Math.PI * idx / 32;
vecBullets[idx] = new Vector2(Math.Sin(angle), Math.Cos(angle));
posBullets[idx] = new Point(startPos);
}
for (int idx = 0; idx < MaxBullets; ++idx)
{
posBullets[idx].X += vecBullets[idx].X * speed;
posBullets[idx].Y += vecBullets[idx].Y * speed;
}
ちなみに sin や cos で得られた値は最大で -1.0 ~ +1.0 の間の数値となります。そのため、単純に掛け算する事で長さを自由に変更する事が出来ます。その最大最小が 1.0 に丸められた数値を正規化表現と呼んでいます。
- 角度について
コメント