【Unity(C#)】HMD装着時にFPSを確認する

##VRエンジニア vs FPS
**VRのFPS調整って大変**ですよね。特にStandaloneデバイスしんどいですよね。

ライティング調節して、頑張って作ったエフェクト泣く泣く消して、
オブジェクトの数もゴリゴリ削って、Shader最適化して、ポストエフェクト切って、
「よっしゃこれでFPSいいかんじでしょ！」って確認したら50とかだったら泣いちゃいます。

「PCVRをスタンドアロンにコンバートしてよ！一回作って素材あるから楽勝でしょ！」
なんて言われた日には男泣きです。

##FPS確認
エディタ上のStatsで確認できるんですが、
Standaloneデバイスおよび、実行形式で書きだした場合の確認はHMD越しにしかFPSを確認することはできません。

なので、**スクリプト貼り付けたら勝手に画面にFPSが出現する**ってのを作りました。

何番煎じなんだよお前、パクリ乙って感じだと思いますが、
せっかく作ったんで書かせてください。

---
2019/08/14 追記
下記のコードだとカメラを余分に１つ増やしたことによる余計なレンダリング処理(?)か何かで
**FPSを調べるためのスクリプトでFPSが低下する**という漫談みたいな状態になっていました。

##コード(FPSが低下してしまうバージョン)

```C#:空のゲームオブジェクトにアタッチ
using UnityEngine;
using UnityEngine.UI;

public class FpsDisplay : MonoBehaviour
{
    int frameCount;
    float prevTime;
    float fps;

    Text fpsText;

    void Reset()
    {
        this.gameObject.name = "FPS";
    }

    void Start()
    {
        //カメラ自動生成
        GameObject camera_G = new GameObject("OnlyUIRenderingCamera");
        Camera faceCamera = camera_G.AddComponent<Camera>();
        faceCamera.clearFlags = CameraClearFlags.Depth;
        faceCamera.cullingMask = (1 << LayerMask.NameToLayer("UI"));

        camera_G.transform.parent = this.gameObject.transform;

        //キャンバス生成＆設定
        GameObject canvas_G = new GameObject("FaceCanvas");
        Canvas faceCanvas = canvas_G.AddComponent<Canvas>();
        canvas_G.AddComponent<CanvasRenderer>();
        faceCanvas.planeDistance = 1;
        faceCanvas.renderMode = RenderMode.ScreenSpaceCamera;
        faceCanvas.worldCamera = faceCamera;

        canvas_G.transform.parent = camera_G.transform;

        //テキスト生成＆設定
        GameObject text_G = new GameObject("FpsText");
        fpsText = text_G.AddComponent<Text>();
        fpsText.font = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font;
        fpsText.fontSize = 34;

        text_G.transform.parent = canvas_G.transform;

        //テキストのポジションを調整
        RectTransform textRect = text_G.GetComponent<RectTransform>();
        textRect.sizeDelta = new Vector2(1000, 1000);
        textRect.localPosition = new Vector3(400, -300, 0);
        textRect.localScale = new Vector3(1, 1, 1);

        //Layerを変更
        canvas_G.layer = LayerMask.NameToLayer("UI");
        text_G.layer = LayerMask.NameToLayer("UI");

    }

    void Update()
    {
        frameCount++;
        float timer = Time.realtimeSinceStartup - prevTime;

        if (timer >= 0.5f)
        {
            fps = frameCount / timer;
            fpsText.text = ((int)fps).ToString() + "FPS";

            frameCount = 0;
            prevTime = Time.realtimeSinceStartup;
        }
    }

}
```
実際にprofilerをチェックすると一目瞭然です。(ガクッとレンダリングの負荷が低下した瞬間が、FPSの表示をオフにした瞬間です)

![chain_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/61b0df49-29f9-85d4-a1eb-94863bd7e2d7.gif)


##コード(修正版)

```C#:キャンバスをワールドにしてカメラを一つに
using UnityEngine;
using UnityEngine.UI;

public  class FpsDisplay : MonoBehaviour
{
    int frameCount;
    float prevTime;
    float fps;

    Text fpsText;

    void Reset()
    {
        this.gameObject.name = "FPS";
    }

    void Start()
    {
        //キャンバス生成＆設定
        GameObject canvas_G = new GameObject("FaceCanvas");
        Canvas faceCanvas = canvas_G.AddComponent<Canvas>();
        canvas_G.AddComponent<CanvasRenderer>();


        //レンダリングをfaceCameraに
        faceCanvas.renderMode = RenderMode.WorldSpace;

        //このゲームオブジェクトの親、ポジションを設定
        this.gameObject.transform.parent = Camera.main.transform;
        this.gameObject.transform.localPosition = Vector3.zero;
        this.gameObject.transform.localRotation = Quaternion.identity;

        //キャンバスの親、ポジションを設定
        canvas_G.transform.parent = this.gameObject.transform;
        canvas_G.transform.localPosition = new Vector3(0,0,0.3f);
        canvas_G.transform.localScale = new Vector3(0.01f, 0.01f, 0);
        canvas_G.transform.localRotation = Quaternion.identity;

        //テキスト生成＆設定
        GameObject text_G = new GameObject("FpsText");
        fpsText = text_G.AddComponent<Text>();
        fpsText.font = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font;
        fpsText.fontSize = 34;
        fpsText.alignment = TextAnchor.MiddleCenter;

        text_G.transform.parent = canvas_G.transform;

        //テキストのポジションを調整
        RectTransform textRect = text_G.GetComponent<RectTransform>();
        textRect.sizeDelta = new Vector2(1000, 1000);
        textRect.anchoredPosition = new Vector3(0, 0, 0);
        textRect.localScale = new Vector3(0.1f,0.1f,1);
        textRect.localRotation = Quaternion.identity;
    }

    void Update()
    {
        frameCount++;
        float time = Time.realtimeSinceStartup - prevTime;

        if (time >= 0.5f)
        {
            fps = frameCount / time;
            fpsText.text = ((int)fps).ToString()+"FPS";

            frameCount = 0;
            prevTime = Time.realtimeSinceStartup;
        }
    }

}
```
`canvas_G.transform.localPosition = new Vector3(0,0,0.3f);`の箇所で
カメラの30cm前方にワールドキャンバスを設置しています。

最前面には表示されませんが、負荷は大幅に軽減されているはずです。


##使い方
空のゲームオブジェクトを生成してそれにアタッチするだけです。
オンオフはそのゲームオブジェクトのActiveを切り替えるだけです。

下記箇所でアタッチするだけでリネームされるようになってます。

```C#
    void Reset()
    {
        this.gameObject.name = "FPS";
    }
```

FPSの計算自体はよくある**1秒間記録ではなく0.5秒間記録して精度上げましょう**[^1]っていう計算方法のやつです。

~~また、[ここ](https://qiita.com/OKsaiyowa/items/413c9da869f23f6b44d1)にあるやり方で、実装しているので最前面に描画されます。~~


##ちょっと時間かかったところ

`fpsText.font = Resources.GetBuiltinResource(typeof(Font), "Arial.ttf") as Font;`
の部分です。

何度やっても**テキストをScriptから生成した場合はFontが設定されていない状態**[^2]になってしまいました。(私だけ?)
なのでフォントを引っ張ってきてます。

テキストが表示されないだけでエラーは出なかったので見つけるのに時間がかかりました。

**自分でインポートしたフォントを利用する場合**は、
インポートしたフォントをResources直下にぶち込んで
`Font font = Resources.Load<Font>("Resourcesより下階層のパス名);`
といった感じで取得できるそうです。[^3]

[^1]: [参考リンク](http://smartgames.hatenablog.com/entry/2017/05/15/233905)
[^2]: Unityのバージョンは2018.4.1f1
[^3]: [参考リンク](https://teratail.com/questions/119257)