私たちが日本の教育で高校で習うと思われる三角関数、1:2:√3 だとか、1.41421356(ひとよひとよにひとみごろ)だとか、1.7320508(ひとなみにおごれや)だとか、こんなもん覚えて何の役に立つんだと憤慨したそこのあなた!具体的な勉強理由を教えましょう。それは漢の浪漫!弾幕を作るためです!美しく広がる敵弾に惚れ惚れして見とれて撃沈してしまった、あの処理を作るために三角関数は存在しているのです!(誇張表現


  • 最初の壁はラジアン
我々の理解を最初に挫く、忌まわしの言葉ラジアン。だが、安心して欲しい。ヤツは四天王の中でも最jy(ry。…ラジアン、略して RAD ですが、これはもうコンピュータの世界はラジアンでなぜか構築されているので、ここは残念ながら従うより仕方がありません。だがしかーぁし!学校の勉強とは違い、我々の目の前にあるのはなんだ?文明の利器コンピュータさまであーる!この機械に面倒は押しつけ任せてしまえば良いのです。私たちがよく使うのは一周360度です。これはデグリーと呼ばれています。略して DEG です。変換はコンピュータに任せてしまいましょう。

DEG と RAD は、360 = 2×3.1415926 という関係にあります。そのため…

  RAD = DEG * 2 * 3.1415926 / 360
  DEG = RAD * 360 / 2 / 3.1415926
このような関係にあります。剰除算は順番を変えても問題ないので、

  RAD = DEG * (2 * 3.1415926 / 360) = DEG * 0.01745329
  DEG = RAD * (360 / 2 / 3.1415926) = RAD * 57.2957805
このように約す事が出来ます。C# や C++ 等のコンパイラにはほぼ必ず 3.1415926 のπは定数定義されているので、それを用いると

  const double DegToRad = 2 * Math.PI / 360;
  const double RadToDeg = 360 / 2 / Math.PI;
このように定数定義しておけば。いつでも簡単に RAD と DEG に相互変換が可能になります。これ、Unity では最初から定数として定義されていたりします。137度をラジアンにしたければ、137 * DegToRad と記述するだけです。

  double radAngle = 137 * DegToRad;



  • ベクトルは方向と長さを表す
もう名前からして全てを拒絶していそうなベクトル。略して Vec は絶対分かり合えないとか、そんな風に思ってた頃が私にもありました。まずはこれをご覧ください。
ベクトル
円の中心から矢印が伸びています。この矢印がベクトルです。長さが変わらないという事は、円の半径を意味します。そのため方向だけ変えると、矢印の先端は円を描く事になります。なんとなく弾幕が見えてきていないですか?速度が速くなれば、この矢印は長くなります。遅くなれば短くなります。ここに直角三角形を描いてみます。

なんか学校で習ったような、なんとなく見覚えのあるような、そんな図形になってきました。ゲームの座標は直交座標系です。これは、横と縦を X,Y方向としています(2Dの場合)。3DはここにZ軸が入るのですが、今回は説明を簡易とするために XY座標だけで話を進めます。ピタゴラスとか三平方の定理とか習いませんでしたか? X の二乗と Y の二乗を足すと長さの二乗に等しいというアレです。

  double length = sqrt(cptX * cptX + cptY * cptY);
この計算式が成り立つのであれば、長さと角度から XY の長さを求める事が出来るはずです。

  double X = sqrt(length * length - cptY * cptY);
  double Y = sqrt(length * length - cptX * cptX);
コンピュータの世界ではルート計算は「異様に重い」んです。そこで偉い人は思いました。X成分とY成分は、長さが2倍になれば成分も2倍になるから単純な比率で出る。だったら、角度に応じた定数で掛け算するだけで、XY成分は取り出せんじゃねぇの?…そう、これが三角関数です。三角関数とは元々計算をラクにするために生み出されたモノなのです。
※ 諸説きっとあります。
ゲームを動かす数学・物理 R

堂前 嘉樹
ボーンデジタル
2020-12-26


  • SIN サイン
苦しくったってー、悲しくったってー、と始まるサイン。略して SIN はどういう時に使うのか。まずはこの図をご覧ください。
SIN
sinθは Y方向÷長さの値が取り出せます。という事は、sinθ × 長さとすると、Y成分が取り出せる事になります。どうでも良いですが、高校の時にいきなり説明で角度の事をθ(シータ)と言われて面食らいませんでしたか?あ、今はお受験で中学校でもう知ってる?あ、そうですか…。しーたあああぁああああ!(天空の城)…げほごほ、さ、さて気をとりなおして、C言語的には以下のような記述となります。

  double cptY = sin(angle * DegToRad) * length;
あと、この図に SIN というラクガキがありますが、それ、私が高校の時に数学の担当教師が「SINの覚え方だー、いいかー、S を筆記体で書くとこうだろー、長さ分の高さだーわかったかー」と
あまりにも分かりづらくて理不尽だったので、ここに晒しておきますw


  • COS コサイン
コサイン…、先に図を見ちゃいましょう。
COS
cosθは見ての通り X方向÷長さの比率です。ゲームプログラミング的に結論言っちゃうと、長さにcosθ掛けちゃうとX成分が取り出せます。えっ、長さの方向が逆になってるって? そ、そうね、だいたいね…(ちっ、気がつかなきゃいいのに)。長さ分のX方向と示すためで、長さは長さとして変わらないので、こまけーこたぁいぃんだよ、この際!(ぉ

  double cptX = cos(angle * DegToRad) * length;
なお、この図にも理不尽筆記体 COSのラクガキがあります
ゲーム開発のための数学・物理学入門 改訂版 (Professional game programming)

ウェンディ・スターラー
SBクリエイティブ
2009-11-26



  • TAN タンジェント
そして、実はゲームでは一番使うんじゃないかというタンジェントのお出ましです。ラスポスです。強い(便利な)んです。何が強いのかと言えば、X成分とY成分から角度が出ちゃうんです。例えて言うならポムじいさんが穴掘ってたら、地中からいきなりシータが出てきたようなモノです(絶対違う)。しーたあああぁああああ!(本日二回目)。
TAN
tanθは Y方向÷X方向の比率を表します。…が、ゲーム制作においてはそんな事実はどうでもよくて(例の tanのラクガキもどうでもよくて)、次の図をご覧ください。
敵の位置
自分から見て敵の位置方向を、ベクトルとして捉える事が出来ますよね。自分の座標 X1,Y1 と敵の位置 X2, Y2 は当然分かると思いますので、X成分と Y成分は X2 - X1, Y2 - Y1 で得られるのは分かるかと思います。ということは!なんと、タンジェントを逆引きすれば、今度は角度が得られる事になります。敵の位置を基準とすれば、敵から自機を攻撃する方向を得る事が出来ます。

そして、これが重要なのですが、C言語とか Unityで使用されている C# にはこの逆引きを行う ATAN(アークタンジェント)という関数が最初から用意されているのです。しかぁも!まるでゲームで使用する事を前提としたような ATAN2 いう関数は、なんとなんと X成分と Y成分を引数として与えるだけで、相手方向の角度が返ってくるのです。例えば、敵から自機を狙う方向は

  double angle = atan2(playerX - enemyX, playerY - enemyY);
たったこれだけです。ただ注意点が一つ。コンパイラの種類によって、この ATANの引数が X,Y だったり Y,X だったりで統一されていません。これは注意してください。


  • 丸く広がる弾の移動の原理
コンバスは丸い線を書く道具として有名です。丸く広がるという事は、このコンバスの半径の長さを 1cm, 2cm, 3cm と広げていく様とよく似ています。発射した位置が中心点です。この中心点から同心円的に位置が移動していくのが丸く広がる弾幕の理屈となります。
丸く広がる
例えば、32方向に弾を同時に射出したい場合は、32個分の角度から計算した X,Y成分を持つ構造体を用意します。この構造体こそベクトルなのです。2次元のベクトルなので、C言語系ではよく Vector2 クラスとしてシステムに定義済みだったりします。

  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);
  }
あとは動かすタイミング毎に弾の位置 posBullets を更新していきます。

  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 に丸められた数値を正規化表現と呼んでいます。


  • 角度について
実は角度には右回り系と左回り系があります。角度が 0 から増えていくと、方向が時計方向に変化していくと右回りです。逆に反時計方向に変化した場合は左回りです。システムによって、この設定が異なる事があります。また、三角関数テーブルを作成する際にエクセルを利用すると思いますが、その場合はおそらく右回り系になります。理由は角度によって得られる三角関数の返却値が最初はプラス方向だからです。多くの座標系は、右下方向がプラス方向なので、右回りとなります。この辺りは説明されるよりも、ご自分で確認したほうが納得しやすいでしょう。
ゲームを動かす技術と発想 R

堂前 嘉樹
ボーンデジタル
2020-04-18