Unity C#で学ぶ数学

## はじめに

６年ほど産業向けUnityエンジニアとして経験を重ね、その中で知ってると少し役に立つ数学の知識を紹介したいと思い記事にしました。

また、高校数学の知識がどんなことに使われているのか、少しでも興味を持っていただけると嬉しいです。

## Unityの概要

Unityはゲームエンジンで2D/3Dゲームや産業向けデジタルツインやxR・メタバースなど幅広く利用されています。


## Unity と 数学

Unityをはじめとしたゲームや3D制御アプリでは数学を意識した実装を行うことがよくあります。

本記事では、簡単な利用例を紹介します。



### 三角関数

Sin関数を使った例を紹介します。
まず、sin関数は以下のような式・グラフで表します。

#####  f(x) = sin x
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654066/25a9a327-075e-da3b-86a0-bdde52d2ea58.png)

プログラムで考えた場合、右辺（横軸）を入力、左辺（縦軸）を出力として扱うのが一般的です。


その前提で考えると、sin関数では一定の範囲内で周期的な動きを再現したい場合に利用できます。


プログラムで書くと以下のようになります。
Update関数は毎フレーム実行される関数です。

Unityなどのゲームエンジンでは、経過時間を変数として代入することが多いです。

```C#
    void Update()
    {
        // cubeをY軸（上下方向）で周期的に移動させる
        cube.transform.position = new Vector3(0.0f, Mathf.Sin(Time.time), 0.0f);
    }
```
![Sinカーブ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654066/e2879732-860d-53f3-e7be-3cbaa0761e44.gif)

時間経過毎にY軸 -1 ～ 1 の座標を行き来することが分かります。

この状態でも良さそうですが、秒数との整合性をとるために円周率（π）を利用します。
```C#
    void Update()
    {
        // cubeをY軸（上下方向）で周期的に移動させる
        cube.transform.position = new Vector3(0.0f, Mathf.Sin(Time.time) * Mathf.PI, 0.0f);
    }
```


### 線形補間
始点と終点等の2点が与えられた場合に、その2点の間の数値を近似的に算出することを指します。

Unityでは Lerp関数とSLerp関数があり、それぞれ線形補完間、球面線形補間を実現します。

下図は、Lerp関数のイメージです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654066/d1a07318-2b4e-f291-c621-d014aed4c39d.png)


プログラムでは以下のように記述します。
```C#
    // 始点
    static Vector3 startPoint = new Vector3(0.0f, 5.0f, 0f);

    // 終点
    static Vector3 endPoint = new Vector3(5.0f, 0.0f, 0f);

    // 始点から終点までの距離
    float distance = Vector3.Distance(startPoint, endPoint);
    
    void Update()
    {
        // 2点間の球面線形補間した値で移動
        cubes[0].transform.position = Vector3.Lerp(startPoint, endPoint, Time.time / distance);

        // 2点間の球面線形補間した値で移動
        cubes[1].transform.position = Vector3.Slerp(startPoint, endPoint, Time.time / distance);
    }
```

##### 実行結果 
赤：Lerp / 青：SLerp
![Lerp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654066/f4b24b29-09b6-41dd-b298-ba533ce84341.gif)


Lerp関数は直線的な動き、SLerpは弧を描くような挙動となります。



また、Lerp関数とSin関数と組み合わせると、2点間を往復するような挙動となります。

似たような関数で、Mathf.PingPongがありますが、こちらは等速で往復するような挙動となります。


```C#
    void Update()
    {
        // 赤いキューブ: Sin関数で2点間を往復
        cubes[0].transform.position = Vector3.Lerp(startPoint, endPoint, Mathf.Abs(Mathf.Sin((Time.time / distance) * Mathf.PI)));

        // 青いキューブ: PingPong関数(等速直線運動)で2点間を往復
        cubes[1].transform.position = Vector3.Lerp(startPoint, endPoint, Mathf.PingPong(Time.time * 2.0f / distance, 1.0f));
    }
```
Lerp関数の第３引数には 0f ～ 1f の範囲の値を指定したいので、絶対値（Mathf.Abs）を付けています。


##### 実行結果 
![Lerp２.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654066/f8c463b0-8836-6a36-1faa-d82e30917bfe.gif)


以上のようなことは、例えばアイコンを明滅させるアニメーションをScriptで実現する際に利用します。

色の場合、2つ色を線形補間するようなイメージです。

ColorでもLerp関数が使えるので、第１・第２引数にはそれぞれ開始の色・変更後の色を指定します。

```C#
    [SerializeField]
    Color startColor = Color.white;

    [SerializeField]
    Color endColor = Color.black;

    [SerializeField]
    Material[] materials;

    // Update is called once per frame
    void Update()
    {
        // 白から黒へ一定で明滅 : PingPong関数を利用
        materials[0].color = Color.Lerp(startColor, endColor, Mathf.PingPong(Time.time / 2.0f, 1.0f));

        // 黒寄りの色の表示を強調 : Sin関数を利用
        materials[1].color = Color.Lerp(startColor, endColor, Mathf.Abs(Mathf.Sin((Time.time / 4.0f ) * Mathf.PI)));
    }
```

##### 実行結果 
![Color.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654066/41e6ccf9-cf7d-619d-73fe-3e3639534d4f.gif)


Sin関数のほうをグラフで見ると、以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654066/0d9cd663-3b2e-7eed-9c06-1729be1a421c.png)



このように、いくつか数式とグラフのパターンを把握していると、アニメーション等への転用ができます。



ここまで記載してきましたが、まだまだ書きたいことがあるため続きは後日公開できればと考えています。


指数関数もアニメーションにも利用でき、
微分であれば物理シミュレーションなどでよく利用され、
例としては、物を投げた時の最高地点や着地点の算出を求める際に利用できるので、
サンプルコード含めて紹介できればと考えています。