macOSでOpenGLプログラミング（1-5. ゲーム処理を書くためのC++クラスを作る）

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

# はじめに

[前回](http://qiita.com/sazameki/items/da9ffe26c5f269ac8e27)までの記事で、Xcodeのプロジェクトを作成してOpenGLの基本処理とウィンドウ周りの処理を実装する部分については、だいたい解説が完了したと思います。

現在のコードの中には、「Cocoaのアプリケーション処理のためのコード」「OpenGLの初期化を行うためのコード」「ウィンドウサイズ変更などの処理を行うためのコード」「OpenGLの描画を行うためのコード」などが、ごちゃっとひとまとめになってしまっています。しかしここからゲームの処理を書いていくためには、「OpenGLの描画を行うためのコード」だけしか必要ありません。

そこで今回は、ここから本格的にOpenGLのゲーム実装を行っていくために、「OpenGLの描画を行うためのコード」をC++のクラスにまとめて表現し、今後のゲーム実装をシンプルにするためのひと工夫を追加したいと思います。


# 1. GameクラスのためのC++ファイルを追加する

まず、「OpenGLの描画を行うためのコード」を表すGameクラスをプロジェクトに追加しましょう。「MyGLGame」フォルダを右クリックして、「New File…」を実行します。

　<img width="300" src="https://qiita-image-store.s3.amazonaws.com/0/166309/359a2837-5d42-1303-be60-5d055e7eb5e9.png">

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

　<img width="560" src="https://qiita-image-store.s3.amazonaws.com/0/166309/3233fea0-a55a-7973-fada-3d3354c4354b.png">

ファイル名に「Game」と入力し、ヘッダファイルを同時に作成するための「Also create a header file」のチェックボックスがチェックされていることを確認してから、「Next」ボタンを押します。次の画面で何も変更せずに「Create」ボタンを押すと、Gameクラスのためのヘッダファイルと実装ファイルがプロジェクトに追加されます。

　<img width="560" src="https://qiita-image-store.s3.amazonaws.com/0/166309/6809dd5f-1f60-39ca-627f-62aa424ef89a.png">

追加されたGame.hppとGame.cppです。

　<img width="180" src="https://qiita-image-store.s3.amazonaws.com/0/166309/9136ef83-ee3f-1a7e-cd38-b73510c3eeb8.png">


# 2. Gameクラスの実装コードを書く

Game.hppを編集して、次のようにGameクラスの宣言を書きます。MyGLViewクラスで利用していたテスト用のvalue変数を、このクラスのインスタンス変数に移動しています。

```cpp:Game.hpp（ヘッダファイル）
#ifndef Game_hpp
#define Game_hpp

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

class Game
{
private:
    float   value;

public:
    Game();
    ~Game();

public:
    void    Render();
};

#endif /* Game_hpp */
```

次にGame.cppを編集して、次のようにGameクラスの実装コードを書きます。

```cpp:Game.cpp（実装ファイル）
#include "Game.hpp"
#include <cmath>

Game::Game()
{
    glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    value = 0.0f;
}

Game::~Game()
{
    // ここでテクスチャなどの解放処理を行う
}

static float PingPong(float t)
{
    t -= floorf(t / 2) * 2;
    return 1.0f - fabsf(t - 1.0f);
}

void Game::Render()
{
    glClearColor(1.0f - PingPong(value), PingPong(value), 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    value += 0.01f;
}
```

コンストラクタ`Game::Game()`で最初の画面クリアを行う処理を書いていますが、今後初期化のためのコードを書く時にはここにコードを追加していきます。デストラクタ`Game::~Game()`で行う処理はまだありませんが、今後テクスチャなどを解放しなければならない時にはここにコードを書きます。

`Game::Render()`関数には、毎フレームごとに実行される処理を書きます。基本的には、「画面の描画」→「ゲーム処理のための変数の内容を変更」の順に処理を書いていきます。なお、PingPong()関数も、MyGLView.mに書いていたのを、ここに移動させました（ついでにstatic宣言して、Gameクラスの中だけで利用することを明示しています）。


# 3. MyGLViewクラスからGameクラスを利用する

Objective-CのクラスであるMyGLViewクラスから、C++のクラスであるGameクラスを利用するためには、**MyGLViewクラスの実装言語が「Objective-C++」だとXcodeに教えてやる**必要があります。これは**実装ファイルの拡張子を「.m」から「.mm」に変えるだけ**で実現できます。

Xcodeのプロジェクト・ナビゲータで「MyGLView.m」ファイルのファイル名を選択し、もう一度ファイル名をクリックして、拡張子を「.mm」に変更しましょう。

　<img width="160" src="https://qiita-image-store.s3.amazonaws.com/0/166309/5f6a0434-52e3-c27d-4254-d01da3547194.png">

> なお、C++のGameクラスとやり取りするのはMyGLViewクラスだけですので、拡張子を「.mm」に変更してObjective-C++を利用することを宣言するのは、MyGLViewクラスだけで構いません。他のObjective-Cのファイルに関しては、拡張子を「.m」のままにしておきましょう。

MyGLView.mmファイルの先頭に、GameクラスのヘッダファイルGame.hppを読み込むための#include "〜"文を追加します。

```objc:MyGLView.mm（ヘッダファイルの読み込み部分）
#import "MyGLView.h"

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

#include "Game.hpp"
```

MyGLViewクラスの変数宣言部に、Gameクラスのポインタを追加します。テスト用のvalue変数はGameクラスに移動しましたので、削除しておきます。

```objc:MyGLView.mm（変数宣言部分）
@implementation MyGLView {
    NSOpenGLContext     *glContext;
    CVDisplayLinkRef    displayLink;
    // value変数はGameクラスに移動したので不要
    //float               value;
    bool                hasDisplayLinkStopped;
    Game                *game;
}
```

prepareOpenGLメソッドでは、OpenGLのコンテキストを取得した直後にGameクラスのインスタンスを作成するためのnew文を追加します。Gameクラスのコンストラクタに、ゲーム実行開始時の画面クリアのコードを追加しましたので、new文の次の行でOpenGLのコンテキストのflushBufferメソッドを呼び出します。

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

    glContext = [self openGLContext];

    game = new Game();
    [glContext flushBuffer];

    // 垂直同期を使用するためには以下2行のコメントアウトを外す
    //GLint swapInt = 1;
    //[glContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];

    CGLContextObj cglContext = [glContext CGLContextObj];
    CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
    CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
    CVDisplayLinkSetOutputCallback(displayLink, &DisplayLinkCallback, (__bridge void*)(self));
    CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat);
    CVDisplayLinkStart(displayLink);
}
```

renderメソッドの中で行っていたOpenGLの命令を発行するコードは、すべてGameクラスのRender()関数に移動させました。makeCurrentContextメソッドとflushBufferメソッドの呼び出しの間に、Render()関数を呼び出すコードを追加しましょう。

```objc:MyGLView.mm（renderメソッドの実装）
// PingPong関数は不要になったので削除する
//float PingPong(float t)
//{
//    t -= floorf(t / 2) * 2;
//    return 1.0f - fabsf(t - 1.0f);
//}

- (void)render
{
    [glContext lock];
    [glContext makeCurrentContext];

    game->Render();

    [glContext flushBuffer];
    [glContext unlock];
}
```


# 3. まとめ

以上で、C++のGameクラスの内容を変更するだけで、OpenGLのゲーム処理を書くことができる準備が整いました。

ゲーム開始時の処理は`Game::Game()`に、ゲーム実行中の処理は`Game::Render()`に、ゲーム終了時の処理は`Game::~Game()`に書けるようになりました。実行しても前回の内容と動作が変わることはありませんが、今後の開発がとてもやりやすくなります。

動作は前回までと同じです。

　<img width="560" src="https://qiita-image-store.s3.amazonaws.com/0/166309/a8cf87ea-70b3-949b-593a-c4cf8d1ecbfc.png">

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

---
次の記事：[macOSでOpenGLプログラミング（1-6. C++の文字列処理のサポート関数を追加する）](http://qiita.com/sazameki/items/73bca6d720f43a8ac9f8)