macOSでOpenGLプログラミング（1-7. 経過時間とFPSの測定）

[macOSでOpenGLプログラミングの目次に戻る](http://qiita.com/sazameki/items/d060f20f621c658046d9)

# はじめに

[前回](https://qiita.com/sazameki/items/73bca6d720f43a8ac9f8)は、C++での文字列処理のクラスを追加し、ファイルを整理するためのグループ分けを行いました。

今回は、ゲーム開始時からの経過時間を測定し、経過時間に応じたゲーム実行のコードを書けるようにしましょう。また実行速度を表すFPSを計算する方法についても解説します。


# 1. Timeクラスの追加

時間を管理するTime構造体を定義するためのファイルを追加します。StringSupport.mmを右クリックして、出てくるメニューから「New File…」を選択します。

　<img width="320" src="https://qiita-image-store.s3.amazonaws.com/0/166309/5277cca0-3bb6-87ef-1232-02babc0e59d3.png">

テンプレート画面では、「macOS」の「C++ File」を選択して「Next」ボタンを押します。

　<img width="460" src="https://qiita-image-store.s3.amazonaws.com/0/166309/592ffde6-baac-6130-b797-02df0042a23b.png">

ファイル名に「Time」と入力し、「Also create a header file」がチェックされていることを確認して、「Next」ボタンを押します。次の画面で何も変更せずに「Create」ボタンを押すと、Time.hppとTime.cppの2つのファイルができます。

　<img width="420" src="https://qiita-image-store.s3.amazonaws.com/0/166309/0bd4fccf-f9a9-a95b-738b-218a57c30240.png">

Objective-C++を利用しますので、「Time.cpp」ファイルの拡張子を「.mm」に変更しておきます。

　<img width="220" src="https://qiita-image-store.s3.amazonaws.com/0/166309/4c15fab1-2199-2918-c0ec-b4c327f3d6f7.png">

Time.hppを次のように編集します。

```cpp:Time.hpp
#ifndef Time_hpp
#define Time_hpp

struct Time
{
    static unsigned frameCount; //!< フレーム数
    static float    time;       //!< ゲーム開始時からの経過時間（秒）
    static float    deltaTime;  //!< 直前のフレームからの経過時間（秒）
    static float    fps;        //!< FPS

    static void Start();
    static void Update();
};

#endif /* Time_hpp */
```

時間を管理するためのTime構造体をこのように定義し、4個のstatic変数を用意し、ゲーム開始時に初期化のために呼ばれる`Start()`関数と、毎フレーム呼ばれる`Update()`関数を用意します。

Time.mmでは、次のように各関数の実装を書きます。

```objc++:Time.mm
#include "Time.hpp"

#import <Foundation/Foundation.h>

static NSTimeInterval   startTime;
static NSTimeInterval   oldTime;
static NSTimeInterval   oldFPSTime;

unsigned Time::frameCount   = 0;
float Time::time            = 0.0f;
float Time::deltaTime       = 0.0f;
float Time::fps             = 0.0f;


void Time::Start()
{
    startTime = [NSDate timeIntervalSinceReferenceDate];
    oldTime = startTime;
    oldFPSTime = 0.0f;
}

void Time::Update()
{
    NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
    time = now - startTime;
    deltaTime = now - oldTime;
    oldTime = now;

    frameCount++;
    if (frameCount % 60 == 0) {
        fps = 60.0f / (time - oldFPSTime);
        oldFPSTime = time;
    }
}
```

現在時刻を秒単位で取得するために、NSDateクラスの`timeIntervalSinceReferenceDate`メソッドを呼び出します。このメソッドは、世界標準時の2001年1月1日 00:00:00からの経過時間を秒単位でリターンします。

こうして取得した現在時間を、`Start()`関数がゲーム開始時に呼ばれるタイミングで`startTime`変数に格納しておき、その後、毎フレーム`Update()`関数が呼ばれるタイミングで現在時刻から`startTime`を引くことで、ゲーム開始時から現在までの秒数が`time`変数に格納されます。

直前のフレームで取得した現在時刻を`oldTime`変数に格納し、それを現在時刻から引くことで、直前のフレームからの経過時間が`deltaTime`変数に格納されます。

`Update()`関数が呼ばれるたびに`frameCount`変数をインクリメントし、60フレームごとに、前回FPSを計算したときからの経過時間でフレーム数を割ることで、FPSを計算して、その値を`fps`変数に格納することができます。

なお、Time.mmの先頭で定義しているstaticな変数（Time.mmでのみ利用可能な変数）を、Time構造体のstatic変数として定義していないのは、これらの変数の型が`NSTimeInterval`というObjective-Cの型だからです。これをヘッダファイルに書いてしまうと、Time.hppを読み込んでTime構造体を使用するすべてのファイルの拡張子を「.mm」にしてObjective-C++でコンパイルしなければいけなくなります。しかしこれらの変数をTime.mmでのみ宣言しておけば、Time.hppは純粋なC++互換のファイルとなりますので、「.cpp」ファイルから読み込んでそのまま使用できます。


# 2. Time構造体の更新用関数を呼び出す

MyGLView.mmファイルを編集して、Time構造体の2つの更新用関数を呼び出すコードを書きます。

まずファイルの先頭でTime.hppをインクルードします。

```MyGLView.mm（ヘッダファイルのインクルード部）
#import "MyGLView.h"

#import <OpenGL/OpenGL.h>
#import <OpenGL/gl3.h>

#include "Game.hpp"
#include "Time.hpp"
```

ゲーム起動直後に呼び出される`prepareOpenGL`メソッドの先頭で、Time構造体の`Start()`関数を呼び出すコードを書きます。これで実行開始時の時間が初期化されます。

```objc++:MyGLView.mm（prepareOpenGLメソッド）
- (void)prepareOpenGL
{
    [super prepareOpenGL];

    Time::Start();

    /* 以下、省略 */
}
```

毎フレームごとに呼び出される`render`メソッドの最後に、経過時間を更新するための`Update()`関数を呼び出すコードを書きます。なお、計測したFPS値をウィンドウのタイトルに表示するための`setTitle:`メソッドの呼び出しのコードも、その直後に書いておきます。

```objc++:MyGLView.mm（renderメソッド）
- (void)render
{
    /* 先頭部分は省略 */

    // 経過時間の更新
    Time::Update();
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self.window setTitle:[NSString stringWithFormat:@"Game (%.2f fps)", Time::fps]];
    }];
}
```

ウィンドウのタイトルを設定するようなGUI操作のコードは、基本的にメインスレッド上で実行する必要があります。そこでNSOperationQueueクラスのメソッドを使って、`setTitle:`メソッドの呼び出しのコードを、メインスレッド上で実行されるブロック内に書いておきます。

> ちなみに、`printf()`関数や`NSLog()`関数を使った**標準出力への書き出しやデバッグログの書き出し**は、**意外と重い**処理ですので、計測したFPS情報をこれらの関数を使って表示してはいけません。それだけでFPSの数値がどんどん落ちてしまいます。これはゲームのデバッグにおいても同じ事が言えます。

以上でTime構造体の各static変数に、計測した時間が格納されるようになります。


# 3. 計測した時間を利用するコードを書く

こうして計測した前フレームからの経過時間を表す`deltaTime`変数を使用することで、Gameクラスの変数の値を変更する時に、秒単位で変更するコードが書けるようになります。

まず、Time構造体が利用できるように、 Game.hppの先頭にTime.hppファイルを読み込むコードを書きます。

```cpp:Game.hpp（先頭部分）
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl3.h>
#include "Time.hpp"
...
```

そして`value`変数の値を変更している`Game::Render()`関数の実装を、次のように、`Time::deltaTime`変数を使って値を変更するように書き換えます。このように書くことで、秒速0.25で値が変化するようになります。この書き方は、Unityでコードを書く場合と同様です。

```cpp:Game.cpp（Render()関数）
void Game::Render()
{
    glClearColor(1.0f - PingPong(value), PingPong(value), 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // 変更前
    //value += 0.01f;

    // 変更後
    value += 0.25f * Time::deltaTime;
}
```

これを実行すると、次のように、タイトルバーにFPS情報が表示され、色が秒速0.25で変化するようになったことが分かります。つまり、`value`変数の値が4秒間できっちり1.0ずつ変化するようになっています。

　<img width="420" src="https://qiita-image-store.s3.amazonaws.com/0/166309/329e6988-2222-22ea-e5c5-446ff69ccb29.png">

`Time::deltaTime`変数を使わずに書いていた時には、「『およそ』60分の1秒間に1回0.01ずつ値を変化させる」ということしか書き表せていなかったので、正確な時間に基づいた表現ができていませんでした。

ここまでのプロジェクト：[MyGLGame_step1-7.zip](http://sazameki.jp/temp/MyGLGame_step1-7.zip)


# 4. まとめ

今回は、Time構造体を追加し、経過時間とFPSを計測できるようにしました。また前フレームからの経過時間を表す`deltaTime`変数を使って、正確な時間に基づいたアニメーションが記述できるようになりました。

次回は、キーボードの操作をサポートして、キーボード入力に基づいて値を変更できるようにしていきましょう。

---
次の記事：[macOSでOpenGLプログラミング（1-8. キーボード操作をサポートする）](https://qiita.com/sazameki/items/b047df7ee21d96b70548)