3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-08-31

macOSでOpenGLプログラミングの目次に戻る

はじめに

前回は、C++での文字列処理のクラスを追加し、ファイルを整理するためのグループ分けを行いました。

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

1. Timeクラスの追加

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

 

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

 

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

 

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

 

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

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では、次のように各関数の実装を書きます。

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()関数を呼び出すコードを書きます。これで実行開始時の時間が初期化されます。

MyGLView.mm(prepareOpenGLメソッド)
- (void)prepareOpenGL
{
    [super prepareOpenGL];

    Time::Start();

    /* 以下、省略 */
}

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

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ファイルを読み込むコードを書きます。

Game.hpp(先頭部分)
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl3.h>
#include "Time.hpp"
...

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

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ずつ変化するようになっています。

 

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

ここまでのプロジェクト:MyGLGame_step1-7.zip

4. まとめ

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

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


次の記事:macOSでOpenGLプログラミング(1-8. キーボード操作をサポートする)

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?