C++
Xcode
OpenGL
macos
Objective-C++

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

More than 1 year has passed since last update.

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

はじめに

前回までの記事で、Xcodeのプロジェクトを作成してOpenGLの基本処理とウィンドウ周りの処理を実装する部分については、だいたい解説が完了したと思います。

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

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

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

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

 

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

 

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

 

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

 

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

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

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クラスの実装コードを書きます。

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」に変更しましょう。

 

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

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

MyGLView.mm(ヘッダファイルの読み込み部分)
#import "MyGLView.h"

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

#include "Game.hpp"

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

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

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

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()関数を呼び出すコードを追加しましょう。

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()に書けるようになりました。実行しても前回の内容と動作が変わることはありませんが、今後の開発がとてもやりやすくなります。

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

 

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


次の記事:macOSでOpenGLプログラミング(1-6. C++の文字列処理のサポート関数を追加する)