UnityでSTGの敵弾発射処理を作ろうとした際に、レガシーな技術である「三角関数のテーブル化」は今でも高速化の手段として有効なのか?という疑問が沸き、それを検証すべく作ったものです。
当方の環境だとMathfの関数を普通に呼ぶのに比べて約60%程度の処理時間で済むみたいです。もちろん環境によって差はあると思いますが、ある程度目的に適ったものが出来たと思います。
(入力パラメータや戻り値の関係で、Mathfの関数からそのまま置き換えられるようにはなっていないのでご留意を。)
コードでは1周の分周比(roundDivision)を1024にしています。ここを細かくすればもっと精度を上げることができますが、テーブルに要するメモリが倍増していくので、2Dゲームに使う程度であればこの辺までで十分かな、と思います。
Atan2のテーブル化の考え方がわからなかったのですが、ここのページが参考になりました。
https://mitoshiropj.blogspot.com/2013/08/blog-post_9340.html
コードもここに掲載されているものをベースに、必要な修正、カスタマイズを加えたものです。
UnityでSTGの敵弾発射処理を作ろうとした際に、レガシーな技術である「三角関数のテーブル化」は今でも高速化の手段として有効なのか?という疑問が沸き、それを検証すべく作ったものです。
当方の環境だとMathfの関数を普通に呼ぶのに比べて約60%程度の処理時間で済むみたいです。もちろん環境によって差はあると思いますが、ある程度目的に適ったものが出来たと思います。
(入力パラメータや戻り値の関係で、Mathfの関数からそのまま置き換えられるようにはなっていないのでご留意を。)
コードでは1周の分周比(roundDivision)を1024にしています。ここを細かくすればもっと精度を上げることができますが、テーブルに要するメモリが倍増していくので、2Dゲームに使う程度であればこの辺までで十分かな、と思います。
Atan2のテーブル化の考え方がわからなかったのですが、ここのページが参考になりました。
https://mitoshiropj.blogspot.com/2013/08/blog-post_9340.html
コードもここに掲載されているものをベースに、必要な修正、カスタマイズを加えたものです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 自前の三角関数を置くクラス
public class DeltaFunc : MonoBehaviour
{
private float[] sinTable; // サインテーブル用配列
public const int roundDivision = 1024; // 360°を何分割するか(2の累乗数)
private const int roundDiv3_4 = roundDivision * 3 / 4;
private const int roundDiv1_2 = roundDivision / 2;
private const int roundDiv1_4 = roundDivision / 4;
private Vector2 sinCos; // GetSinCos関数内でXY方向の移動ベクトルの値を入れる変数
private float[] atanTable; // アークタンジェントテーブル用配列
private const int atanDevide = roundDiv1_4; // 0-45°を何分割するか
//————————————————————
// スタート時に三角関数テーブル作成
//————————————————————
void Start () {
//Sinテーブル作成(0~90°(0~(分周比の1/4 +1))までをテーブル化)
sinTable = new float[roundDiv1_4 + 1];
for (int i = 0; i < sinTable.Length; i++)
{
float rad = 360 * i / roundDivision * Mathf.Deg2Rad;
sinTable[i] = Mathf.Sin(rad);
}
//Atanテーブル作成(0~45°をatanDevideで分割しテーブル化)
atanTable = new float[atanDevide + 1];
for (int i = 0; i <= atanDevide; i++)
{
float r = (float)i / atanDevide;
atanTable[i] = Mathf.Atan(r)* Mathf.Rad2Deg;
}
}
//————————————————————
// テーブルを用いてSin、Cosの近似値を求める関数
// パラメータ:方向(roundDivisionで設定した分周比の範囲の整数)
// 戻り値:Vector2型(y要素にsin、x要素にcosが入る)
//————————————————————
public Vector2 GetSinCos(int direction)
{
// 三角関数テーブル引き(directionは0から(分周比-1)の範囲)
if (direction <= roundDiv1_4) // 第1象限(0°<= 方向 <=90°)
{
sinCos.y = sinTable[direction];
sinCos.x = sinTable[roundDiv1_4 – direction];
}
else if (direction <= roundDiv1_2) // 第2象限(90°< 方向 <=180°)
{
sinCos.y = sinTable[roundDiv1_2 – direction];
sinCos.x = sinTable[direction – roundDiv1_4] * -1;
}
else if (direction <= roundDiv3_4) // 第3象限(180°< 方向 <=270°)
{
sinCos.y = sinTable[direction – roundDiv1_2] * -1;
sinCos.x = sinTable[roundDiv3_4 – direction] * -1;
}
else // 第4象限(270°< 方向 <360°)
{
sinCos.y = sinTable[roundDivision – direction] * -1;
sinCos.x = sinTable[direction – roundDiv3_4];
}
return sinCos;
}
//————————————————————
// テーブルを用いて二点間の角度の近似値(オイラー角)を求める関数
// パラメータ:dYに二点間のY方向差分、dXに二点間のX方向差分
// 戻り値:0~360°(atan2とは異なり、負の値は取らない)
//————————————————————
public float GetAtan2(float dY, float dX)
{
// Y差分、X差分ともにゼロだった場合の例外処理
if (dY == 0 && dX == 0) return 0;
if (dY >= 0) // 起点側から見てターゲットが第1or第2象限に存在
{
if (dX >= 0) // 第1象限
{
if (dX >= dY) // 0-45°
{
return atanTable[(int)(dY / dX * atanDevide)];
}
else // 45-90°
{
return 90.0f – atanTable[(int)(dX / dY * atanDevide)];
}
}
else // 第2象限
{
if (dY >= -dX) // 90-135°
{
return 90.0f + atanTable[(int)(-dX / dY * atanDevide)];
}
else // 135-180°
{
return 180.0f – atanTable[(int)(dY / -dX * atanDevide)];
}
}
}
else // 起点側から見てターゲットが第3or第4象限に存在
{
if (dX < 0) // 第3象限
{
if (-dX >= -dY) // 180-225°
{
return 180.0f + atanTable[(int)(-dY / -dX * atanDevide)];
}
else // 225-270°
{
return 270.0f – atanTable[(int)(-dX / -dY * atanDevide)];
}
}
else // 第4象限
{
if (-dY >= dX) // 270-315°
{
return 270.0f + atanTable[(int)(dX / -dY * atanDevide)];
}
else // 315-360°
{
return 360.0f – atanTable[(int)(-dY / dX * atanDevide)];
}
}
}
}
}