c++ and SFML with VisualStudio Code on OSX環境の構築

  翻訳が退屈になってきたのでいよいよHello Worldに。何かプロジェクトを始める時、どれくらい事前学習にあててどれくらいで走り出すかいつも悩むけれども、これは常々な問題なのだろうなあ

  ざっくり和訳したSMFLゲームエンジン関連の人はアマチュアで数本ゲームを作ったことある程度のスキルのような気がする。参考にはなったが氏が作成したゲームエンジンをそのまま採用する気にはなれなかった。バグ出た時にめんどくさそう

  ただし有限オートマトンというか状態ステートマシンについては使った事が無いので勉強用に1冊本を買った。NPC  AIの振る舞いツリーもステートマシンで上手く表現できそうだったから

 そろそろテックな的な話題より成果物やプロジェクト自体を主軸に置いてブログを書いていこうと思う。

 というわけで、表題の通りOSXのVSCodeにC++ + SFML環境を構築した。以下メモ。

  • SFML and Xcode (Mac OS X) (SFML / Learn / 2.4 Tutorials)のInstalling SFMLを参考にSFMLライブラリを突っ込んていく
    • SFMLライブラリの利用が確定したわけではないので、別の場所に格納しようと思ったが、ライブラリのファイル数がそこそこ多く、コンパイル時にダルい思いをしたので、言われた通り初期設定されているパスに突っ込んだ
  • Visual Studio CodeのC++環境設定は、C++ programming with Visual Studio Code を参考に
    • シンボルハイライトなどは拡張機能突っ込むだけでOK
    • launch.json, tasks.json などといった設定ファイルを上手く扱わないとVSCode上からショートカットキーでビルドできない
      • SFMLもC++、デバッガも手探りの中これらを設定するのはかなり骨が折れたがとりあえずVSCodeからビルドとデバッグは出来るようになった
      • gdbでデバッグしようとするとOSXでは署名的なものがいるらしいので OS XでGDBを使う(ためにコード署名をする) - Qiita を参考にする
      • コードフォーマットの調整だけできなかった。shift + alt + Fだかを押してもコンテクストメニューが出てこずに設定の編集ができなかった(詳しく追うほどの思いは無かったので諦めた)

というわけで、開発開始。次はHello worldかな。

f:id:giraffyk1:20160915113659p:plain

【SMFL非公式和訳】チュートリアル: ベーシックゲームエンジン

この記事は僕がSFMLを学習する為に
Tutorial: [C++] Basic Game Engine
https://github.com/SFML/SFML/wiki/Tutorial%3A-Basic-Game-Engine

を無断で和訳したものです。訳者がSMFL、英語どちらも学習中の為意訳・誤訳ご了承のほど。

チュートリアル: ベーシックゲームエンジン

ベーシックゲームエンジンの制作

 素晴らしいゲームたちのコアはゲームエンジンだ。初心者が自分のゲームエンジンを初めて開発しようとした場合、かなりの確率で挫折することになる。キレのあるゲームエンジンを制作する際には、多くの検討が必要になるだろう。全てのグラフィックの管理、サウンド、ゲームオブジェクトの処理などは、多くの労力を必要とし、それらの開発はあなたがゲーム制作で実際にやりたい事と比べたらかなり見劣りするだろう。

 多くのゲームプログラミングに興味がある人はゲームのアイデア部分から取り掛かるが、実際はゲームのアイデアを実現するための具体的な手順を理解していない。付け加えていうならば、彼らのゲームのアイデアは自体もゴミだ。こういったもののほとんどは、世に出てすでにプレイされている多くのゲームと、ほとんど同じ代物である。

 だからといってゲーム制作を辞めるべきだとは私は思わない。何故なら製作途中で投げ出されたゲーム開発であっても、多くの経験と、制作していくにしたがって自分のゲームがリアルになっていく、その過程自体の楽しみを得る事ができる(良くも悪くも)。私の場合もそのゲームが制作され実現される過程自体を目的にゲーム制作をしている。

 こういった理由から、私の将来のアイデアに使いまわせるようベーシックゲームエンジンの制作をする事にした。これによりゲーム制作の楽しい部分、ゲームメカニズム制作をより簡単に楽しむことができる。このゲームエンジンを皆さんにシェアする前に、いくつかの難しいレッスン、長年に渡って私が学習してきた事をまずは名前空間に関してから共有させてほしい。

ネームスペース(名前空間)

 多くのゲームエンジンは他者から借りられた、盗まれた(そうでないことを願う!)または書かれた、基本的な要素から始まる。SFMLをサンプルとすれば、基本要素は、グラフィック、サウンド、ネットワーク、システム、画像サポートなどである。

 これらのSFMLクラスは、sf.という名前空間でラップして格納されている。だから全てのクラス、列挙型、定数、変数の前にsf::が置かれている。

 我々はゲームエンジンの名前空間をGQEとする(私のニックネームGatorQueからGatorQue Englineの頭文字を取った)。これは自分のプロジェクトではこれを好きに変えたら良い。ただしタイプの手間を考えて出来るだけシンプルにすること。

 このように.hppファイルにて名前空間の内側にクラスをラップする。

namespace MyStuff
{
  class MyClass {
    public:
      MyClass();
      virtual ~MyClass();
  };
} // namespace MyStuff

…で.cppはこのようにする。

namespace MyStuff
{
  MyClass::MyClass()
  {
  }

  MyClass::~MyClass()
  {
  }
} // namespace MyStuff

もう1つの名前空間の素晴らしい機能は、同じ名前が付けられた2つのクラスの干渉を防ぐ事である。例を挙げると、SFMLにはClockクラスがある。そこで君がsf::Clockクラスと全く別の自分用のClockクラスを作りたいとする。その時君のClockクラスを名前空間でラップしてしまえば、以下のようにどのClockクラスが呼び出されたか、コンパイラに指示を与える事ができる。

class MyClass {
  public:
    MyClass();
    virtual ~MyClass();

    // 変数
    sf::Clock mClock1;
    MyStuff::Clock mClock2;
};

 どのように名前空間タグをもちい、各Clockクラスの変数を作っているかに注目して欲しい。変数の前に毎回MyStuffと名前空間を書くのは少し嫌な気持ちにさせるかもしれない。そんな時は以下のように1行を君の.cppか.hppに追加すれば、コンパイラに各クラスの名前空間を伝える事ができる。

using namespace MyStuff;
// OR
using namespace sf;

 このやり方の問題点は、全てのクラスが同じ名前空間の中で名前空間タグ無しに作られることだ。もし君がローカルクラスで同じ名前を使ったら、コンパイラは混乱してしまう。私は以下のように使いたいクラスのみ指定して使うのをお勧めする。

using MyStuff::MyClass;
using sf::Clock;

 この方法であれば、このファイルに名前空間の衝突の可能性をなくしつつ、特定のクラスを名前空間タグ無しで使用できる。

 名前空間について理解したところで次のトピック、ゲーム制作のような大きなプロジェクトで沢山のファイルをどう取り扱う必要があるかを考えていこう。大したことはない。前方宣言とポインタを使うのだ。

前方宣言

 ゲームプログラミングにおいては、しばしば全ゲームオブジェクトが他のゲームオブジェクトを参照するために親クラスへアクセスできる必要がある。しばしば親クラスはこれらの全ゲームオブジェクトが作られた場所のコンテナとなるからだ。たとえば、全ゲームオブジェクトを持っているレベルクラスがあるとする。そしてこれらのゲームオブジェクトは、範囲変数によってそのレベルクラスの内側にとどめておく必要がある。そのうえ、他のレベルクラス内にあるゲームオブジェクトを移動できる必要も、現在の状態もどうにかして保持する必要がある。コードでこの問題を表現していこう。

#include "GameObject.hpp"
class Level {
  public:
    GameObject mObjects[100];
};

Level.hppファイルとGameObject.hppがどうやって結び付けられているかに注目してほしい。次に、GameObjectを親クラスLevelに結びつけようとするとどうなるか見て欲しい。

#include "Level.hpp"
class GameObject {
  public:
    Level& mParent;
};

 このサンプルコードをコンパイルしようとするとLevel.hpp、GameObject.hppどちらかが先に読み込まれることになる。どちらの場合も、include命令によって無限ループを引き起こすことになる。コンパイラは最終的に強制終了するか、もっと酷いことになり、当初求めていた結果は得ることができない。

 しかし心配は無用だ。簡単な解決策がこれにはある。.hppファイルでクラスの前方宣言で使用し、cppファイルではinclude命令だけを行う。

 以下は上記と同じ例だ。ただし代わりに前方参照を利用している。

// GameObjectクラスの前方宣言
class GameObject; // クラスがどういうものか定義していないのに注目 それはcppファイルの最後に登場する

class Level {
  public:
    GameObjects* mObjects; // GameObjectsがポインタで完全なオブジェクトでない事に注目
};

次にGameObject.hpp の方にはどういう変化があったか見ていこう。

// レベルクラスの前方宣言
class Level; // cppファイルで完了しているのでLevel.hppをincludeしていないのに注目

class GameObject {
  public
    Level& mParent;
}

 
 こうするとコンバイラがGameObjectやLevelクラスを見ようとしたとき、それらの定義をしようとしたときに、定義が書かれている部分を自分で探そうとする。同じく変数内のポインタやアドレスリファレンスについてもコンパイラはこれらのサイズを自動的に識別し、作成する(32bitか64bit。CPUアーキテクチャにより異なる)。

 前方参照はこのような場合に大変便利だ。ただしポインタやリファレンスオブジェクト、その他必要なポインターに充分な注意を払うひつようがある。上記の事柄をまとめて、HPPファイルで前方参照を使うべきかどうかの助けとなるシンプルなルールを以下に紹介しよう。

  • 呼び出そうとしているクラスは呼び出し元クラスののメソッドの引数としてだけ使っているか。呼び出しをクラスからポインタに変更できないか?
  • このクラスのコントラクション(サンプルの場合GameObject)で独立したクラス(サンプルの場合Levelクラス)のアドレスが分かるようになるか? その独立クラス(Levelクラス)を参照で使い、ポインタ利用ではないか
  • 2つのクラスはそれぞれのクラスのコードを利用するか? このクラスの使用は全てポインタか参照か?
  • これはクラスへのポインタ変数または参照アドレスか?

(※訳注 c++に不慣れな為訳に自信なし)

 これらの質問がイエスなら、前方参照を利用することを検討すべきだろう。全てのコードをCPPクラスに入れ、HPPファイルは宣言だけにしたのもこれが理由だ。

 これで前方参照についての話題は終わった。次は私が作ったベーシックゲームエンジンと基礎的な機能を議論していくことにしよう。

Main関数

 私の場合、main.cppファイルをシンプルで素直な状態を維持しようとしている。これは1つのゲームアプリケーションクラスとそれをインスタンス化した私ので作成されたMain関数だ。(GQE Projectに全ソースあり)

/**
 * ここが新しいプロジェクトのスタート地点。このファイルの処理は大したことがないが重要である
 * ここにメインのゲームループとアプリケーションを作成する
 *
 * @file main.cpp
 * @author Ryan Lindeman
 * @date 20100707 - Initial Release
 * @date 20110611 - Added new logging capabilities using macros and c++ classes.
 */

#include <assert.h>
#include <stddef.h>
#include <GQE/Core.hpp>
#include <MyApplication.hpp>

int main(int argc, char* argv[])
{
  // anExitCodeを具体値で初期化
  int anExitCode = GQE::StatusNoError;

  // アプリケーション生成前にロガーを作成
  GQE::FileLogger anLogger("output.txt", true);

  // アクションアプリケーションを生成
  GQE::IApp* anApp = new(std::nothrow) GQE::MyApplication();
  assert(NULL != anApp && "main() Can't create Application");

  // コマンドライン引数の処理
  anApp->ProcessArguments(argc, argv);

  // アクションアプリケーション稼働開始
  // アクションアプリケーションの初期化
  // アプリケーションが終了するまで持続されるゲームループの中へ
  // アプリケーションのリセット
  // Exitはここに戻ってくる
  anExitCode = anApp->Run();

  // deleteでアクションアプリケーション自身をリセット
  delete anApp;

  // オブジェクトへのポインタを保持させない
  anApp = NULL;

  // exitコードを返す
  return anExitCode;
}

 上記のメイン関数は基本的な形にとどめておいている。トラブルを避けるためにあなたのプロジェクトにこのファイルをそのままコピーすると良い。その上でゲーム毎の違いをアプリケーションファイルに書くと良いだろう。

 続いてAppクラスの下でどんな部品が動いているか見ていこう。

ゲームアプリケーション

 どんなゲームでもベーシックゲームエンジンが動作するために、最も基本的なゲームアプリケーションアルゴリズムをゲームアプリケーションクラスであるAppに入れる必要がある。このために、他のオープンソースゲームエンジンやゲームエンジンチュートリアル、それに加えて、それらで動作する私が書いた最も基本的なゲームアルゴリズムを用意した。このゲームアプリケーションアルゴリズムは次のようにApp.cppファイルのアウトラインである(GQE Projectに全ソースあり)

  int App::Run(void)
  {
    SLOG(App_Run,SeverityInfo) << std::endl;

    // 最初に稼働フラグをtrueに
    mRunning = true;

    // AppポインターをStatManagerに登録
    mStatManager.RegisterApp(this);

    // AppポインタをStateManagerに登録
    mStateManager.RegisterApp(this);

    // 最初にGQEコアライブラリのIAssetHandler派生クラスを登録
    mAssetManager.RegisterHandler(new(std::nothrow) ConfigHandler());
    mAssetManager.RegisterHandler(new(std::nothrow) FontHandler());
    mAssetManager.RegisterHandler(new(std::nothrow) ImageHandler());
    mAssetManager.RegisterHandler(new(std::nothrow) MusicHandler());
    mAssetManager.RegisterHandler(new(std::nothrow) SoundHandler());

    // 派生クラスにカスタムIAssetHandlerの登録時間を渡す(※訳注 原文Give derived class a time to register custom IAssetHandler classes いつ登録するかの情報?)
    InitAssetHandlers();

    // 全体的な設定情報であるsettings.cfgファイルをConfigAsetにロードさせる
    // IDは下の "resources/settings.cfg"で登録
    InitSettingsConfig();

    // レンダラーウインドウをディスプレイグラフィック上にオープンする事を試みる
    InitRenderer();

    // IScreenFactoryクラスに派生アプリケーションクラスを登録させる
    // IScreen派生クラスの提供 (前のIState派生クラスと同じもの)をリクエスト
    InitScreenFactory();

    // StatManagerを初期化させる
    mStatManager.DoInit();

    // 稼働フラグがまだtrueならゲームループ
    GameLoop();

    // アプリケーションリセット
    HandleCleanup();

    // 内部リセットの実行
    Cleanup();

    // 終了前に稼働フラグをfalseに変更しておく
    mRunning = false;

    if(mExitCode < 0)
      SLOGR(App_Run,SeverityError) << "exitCode=" << mExitCode << std::endl;
    else
      SLOGR(App_Run,SeverityInfo) << "exitCode=" << mExitCode << std::endl;

     // 終了用の終了コードを具体的に返す。終了の意味で0の意味は使わない
    return mExitCode;
  }

 このように、アルゴリズムは以下の基本的なステップで構成されている。

  • 各マネージャクラスが自分の親クラスであるゲームアプリケーションクラスへのポインタ/参照を持っているか確認(マネージャクラスの詳細については後述)
  • AssetManagerクラスに各AssetHandlerクラスを登録(AssetHandlerクラスの詳細については後述)
  • ゲーム設定ファイルからゲーム設定をロードし、ウィンドウをRenderするのに必要な設定を取り出す。
  • SFMLレンダリングウィンドウ/ターゲットを初期化
  • ゲームの初期情報を含む具体的な情報を初期化(Gameの状態Game statesについてはのちほど)
  • ゲームループ本体の稼働開始
  • ゲーム終了前にゲームアプリケーションが必要としていたものをリセット
  • ゲームアプリケーションの終了

 これらのステップは仕上がりがどうであれどんなタイプのゲームにも通用する。変更の必要があるかも知れない部分はInitAssetHandlers, InitScreenFactory, HandleCleanupだろう。それ以外の部分は毎ゲーム同じにすべきだ(少なくとも目指すべきだ)。

 ゲームアプリケーションクラスの2つ目の目的は、ゲーム全体に大して基本的なクラスの格納場所(コンテナ)を提供することだ。 sf::RendererWindowクラス、ゲームオブジェクト、ゲームステータス、ゲームマネージャークラスその他のことだ。ゲームマネージャークラスとゲームステータスクラスの詳細に入る前に、ゲームループ内のApp::GameLoopメソッドについて補足しておきたい。

ゲームループ

 多くのゲームエンジンチュートリアルは以下のゲームループアルゴリズムに沿っている。

  • 入力デバイス(キーボード、マウス、ジョイスティック他)の処理
  • ゲームロジックの処理(動くゲームオブジェクトの位置、スピードなどの更新、衝突判定やAI機能の処理)
  • 画面のリセットとゲームオブジェクトの描画
  • ゲーム終了シグナルがセットされるまで処理を繰り返す(普通は入力デバイスによりセットされる)

 しかしこのゲームループアルゴリズムには1つだけ問題点がある。ゲームロジックの処理とコンピューターがグラフィックを画面に表示できるスピードが密接に関係している点だ。もし君が遅いグラフィックカードを搭載したPCでゲームを動作させた場合、速いグラフィックカードを搭載したPCで動作させる場合とくらべて遅くなる。これを防止するために、私はインターネットで実力を磨き、問題を解決した( entropyinteractive.comを見て)。

 以下は上記の問題に対応したゲームループのコードになる(GQE Projectに全ソースあり)。

  void App::GameLoop(void)
  {
    SLOG(App_Loop, SeverityInfo) << std::endl;

    //Updateループの頻度を制限されたものに調整するためにClockを使用
    sf::Clock anUpdateClock;

#if (SFML_VERSION_MAJOR < 2)
    // Updateクロックのリスタート/リセット
    anUpdateClock.Reset();

    // 次回いつ(何秒後)更新が必要か?
    float anUpdateNext = anUpdateClock.GetElapsedTime();
#else
    // 最終フレームからの経過時間の計算のためにCockを使用
    sf::Clock anFrameClock;

    // Updateクロックのリスタート/リセット
    anUpdateClock.restart();

    // 次回いつ(何ミリ秒後)更新が必要か?
    sf::Int32 anUpdateNext = anUpdateClock.getElapsedTime().asMilliseconds();
#endif

    // 1つ以上状態がアクティブであることを確認
    if(mStateManager.IsEmpty())
    {
      // アクティブな状態のものが1つもなければエラーで終了
      Quit(StatusAppInitFailed);
    }

    // IsRunnningがtrueを戻す限りループ
#if (SFML_VERSION_MAJOR < 2)
    while(IsRunning() && mWindow.IsOpened() && !mStateManager.IsEmpty())
#else
    while(IsRunning() && mWindow.isOpen() && !mStateManager.IsEmpty())
#endif
    {
      // 現在のアクティブな状態を取得
      IState& anState = mStateManager.GetActiveState();

      // UpdateFixedループが何回連続で呼ばれたかのカウント
      Uint32 anUpdates = 0;

      // 入力されたものがあれば処理
      ProcessInput(anState);

      // 現在の更新時刻を記録
#if (SFML_VERSION_MAJOR < 2)
      float anUpdateTime = anUpdateClock.GetElapsedTime();
#else
      sf::Int32 anUpdateTime = anUpdateClock.getElapsedTime().asMilliseconds();
#endif

      // ゲームループのUpdateFixed割り当てを処理
      while((anUpdateTime - anUpdateNext) >= mUpdateRate && anUpdates++ < mMaxUpdates)
      {
        //現在のアクティブ状態の処理を次回に
        anState.UpdateFixed();

        // StatManagerの更新処理を実行
        mStatManager.UpdateFixed();

        // 次回のUpdateFixed実行タイミングをいつにすべきか計算
        anUpdateNext += mUpdateRate;
      } // while((anUpdateTime - anUpdateNext) >= mUpdateRate && anUpdates <= mMaxUpdates)

      // 現在のアクティブ状態の変数更新処理
#if (SFML_VERSION_MAJOR < 2)
      anState.UpdateVariable(mWindow.GetFrameTime());
#else
      // SFML2.0用に秒数を浮動小数点数に変換
      anState.UpdateVariable(anFrameClock.restart().asSeconds());
#endif

      // アクティブ状態のものを描画させる
      anState.Draw();

      // StatManagerの描画を処理
      mStatManager.Draw();

#if (SFML_VERSION_MAJOR < 2)
      // ウィンドウを画面に描画
      mWindow.Display();
#else
      // ウィンドウを画面に描画
      mWindow.display();
#endif

      // 必要であればここで直近で削除された状態のリセット処理 
      mStateManager.HandleCleanup(); 
    } // while(IsRunning() && !mStates.empty())
  }

 様々なコンピュータ上で同じ速度でゲームロジックを処理するためのポイントは、グラフィックカードのスピードから独立した速度を管理の元、ゲームロジックを実行することだ。このため、独自のゲームロジック速度(レート)で作成した(今回のベーシックゲームエンジンでは20hzつまり毎秒20回ゲームループしか実行されないように制限した)。それに加え、ゲームロジックが上限(最大mMaxUpdates)に到達するまで、更新が必要な分画面に描画されるようにした。

 最近のコンピューター(私のデスクトップやノートPCみたいな)であれば、ゲームロジックは20hzで、画面のFPS(フレーム毎秒)は60hzで動作する。もしこれを古いPCで動作させると、ゲームロジックは20hzで画面のFPSは15-30hzくらいになる。この方法で、どんな環境でもゲームロジックを同じにし、良いグラフィックカードが搭載されている場合にのみなめらかなアニメーションをさせるという事が出来る。

 この驚きの変化は上記のゲームループ内のmUpdateRateとmMaxUpdate変数によって行われている。これはAppコンストラクターにより設定されている。

 mUpdateRate(1.0f / 20) // 20 Hzでゲームロジックが動作する. 15, 30, などどんな値でも君の自由に変更できる

上記を踏まえ、アルゴリズムは以下のようになる。

  • ゲーム状態(State)への参照アドレスの取得(次の項目で議論する)
  • ゲーム状態(State)を通して入力デバイス(キーボード、マウス、ジョイスティック他)の処理
  • ゲーム状態(State)を通して ゲームロジックの処理(上記のUpdateFixed)
  • ゲームロジックレート(更新頻度)に到達するまで上記2ステップを繰り返す
  • ゲームレート処理を実行(上記UpdateVariable)
  • ゲーム状態(State)に画面へのオブジェクト描画を許可
  • ゲーム終了フラグがセットされるまで上記処理を繰り返す(上記IsRunningまたは !mState.empty())
  • ここで疑問が浮上。ゲーム状態(State)ってなに? それはなにをしてるの?

ゲーム状態(State)

 最近のゲームの多く、特にカジュアルゲームはウェブ上でいろんな種類のものを目にする。それらのゲームはよくあるパターンに基づいて動作している。たとえばこんなゲームフローだ。

  • 制作者である会社やチームのためにスプラッシュスクリーンを表示(※訳注 起動時の会社ロゴの表示の事)
  • ネットやハードディスクからゲームの画像が読み込まれるまで、お待ち下さい画面を表示
  • 挨拶をしつつ名前/ニックネームを尋ねる(初回プレイ用)
  • メインメニューの表示とゲームモードの選択入力(シングルプレイヤー、対戦、タイムアタックなど)
  • 現在のゲームモードをゲームオーバーになるかプレイヤーが終了するまで継続
  • 最後に2-3ステップをゲームアプリケーション終了まで繰り返す

 この流れに君が同意してくれるなら、多くのゲームは次に沿ったゲームフローを処理する事になる。スプラッシュ→ロード→メニュー→ゲーム→ロード→レベル→ゲーム→ロードレベル→ゲーム→メニュー→終了。今の状態を終了したとき、即座に前の状態に戻れる事をゲーマーの君なら期待するだろう。これはゲームオーバー状態に達したら、メインメニューに戻らなければならない事と同義である。このゲームフローの中の各ポイントでゲーム状態(State)を呼べる必要がある。ゲーム状態(State)とは要するに、ゲームフロー全体の各ポイントをかっこ良く言っているだけだ。

 もしあなたが大学でコンピューターサイエンスの専門的な分野を学んでいたら、もしくはいくつかのゲームチュートリアルを読んだことがあるならご存知だろうが、私が話しているこれは有限オートマトンまたはFSM(Finite State Machines 有限ステートマシン)の事だ(初めて聞く単語なら是非これについてのチュートリアルをどこかでご一読いただきたい。とても役に立つ)。有限オートマトンはあるゲームの状態から違うゲームの状態へのフローを表現する簡潔で感覚的な方法だ。一般的には、円と円を線で結んだ形ようなイメージになる。上または下に付く線は、1つの円(ゲーム状態)から別の円(ゲーム状態)に変化するルールとセットで書かれる。たとえば、最初の円に「スプラッシュ」と名づけて、ゲーム開始時にスプラッシュ画面をプレイヤーに表示する。次の円は「ローディング」と名付けローディングフェーズを表現する。上の方で書いた、お待ち下さい画面の事だ。この2つの円を繋ぐラインに付く条件は「3秒待つ」といった感じだ。スプラッシュスクリーンを3秒表示したらローディングフェーズに移るという意味だ。

 3つ目の円は「メインメニュー」と名付ける。そしてローディングと名付けられた円と繋ぎ、この線には「サウンドと画像全てを読み込み終わるまで待つ」条件付けすることになるだろう。次。今のところかなり直線的であるが、メインメニューからは多くの選択肢があり、メインメニューの円からは多くの線とそれに繋がる円が出来ることになる。追加される円は「オプション画面」「シングルキャンペーン」「マルチプレイキャンペーン」「デスマッチ」といった感じになるだろう。

 当初望んていたように、このベーシックゲームエンジンはこういったフローをなにか簡単な方法で管理・実現したい。ゲーム状態(State)毎にあつらえたゲームループをそれぞれに用意する事は可能だ。しかし、コードをコピーせずに抽象化できるところはしたい。というわけで、抽象化クラスをIStateを以下のように作成した。(GQE Projectに全ソースあり)

   /**
     * DoInitはStateを初期化する機能がある。 HandleCleanupはmCleanupがTrueの時に呼ばれ
     * Derivedクラス群は常に呼ばれる
     * IState::DoInit() の管理データをまずリセット
     */
    virtual void DoInit(void)
    {
      ...
    }

    /**
     * ReInit は StateManager::ResetActiveState()  が呼ばれた時、このステータスをリセット機能
     * この方法でゲームデータのリセットや再読み込み無しでゲーム状態(State)を再起動できる
     */
    virtual void ReInit(void) = 0;

    /**
     * DeInit は この状態がリセットされたかどうかの目印を付ける機能
     */
    void DeInit(void)
    {
      ...
    }

    /**
     * Pause は 他のゲーム状態(State)にフォーカスが移った際や自分がフォーカスを失った際に、
     * このゲーム状態(State)をポーズする機能
     */
    virtual void Pause(void)
    {
      ...
    }

    /**
     * Resume は前のゲーム状態(State)が削除され、フォーカスが戻った時に再開する機能
     */
    virtual void Resume(void)
    {
      ...
    }

    /**
     * HandleEvents はこのゲーム状態(State)がアクティブになっている時に、デバイス入力を処理する機能
     * @param[in] theEvent はAppのループメソッドから来る
     */
    virtual void HandleEvents(sf::Event theEvent) = 0;

    /**
     * UpdateFixed は このゲーム状態(State)がアクティブ状態になっている時、全てのゲーム状態(Stat)fixed updateする機能
     */
    virtual void UpdateFixed(void) = 0;

    /**
     * UpdateVariable はこのゲーム状態(State)がアクティブ状態になっている時、全てのゲーム状態(Stat)variable updateする機能
     * (※訳注 fixed/variableの違いについてはコードを見ていないため不明)
     */
    virtual void UpdateVariable(void) = 0;

    /**
     * Draw はこのゲーム状態(State)がアクティブ状態になっている時、このゲーム状態が描画が必要な時に処理する機能
     */
    virtual void Draw(void) = 0;

   /**
     * HandleCleanup is はこのゲーム状態(State)が削除される前に要求されたリセットを処理する機能
     */
    virtual void HandleCleanup(void)
    {
      ...
    }

 このやり方でアプリケーションクラスのAppはその中身やゲームフローが実際どうなっているのかは知る必要がなくなり、ただ現在のゲーム状態(State)を通して呼び出される上記のIStateクラスのメソッドを実行してやればよい。これが美しいポリモーフィズムアクションというものだ(オブジェクト思考で使う格好いい単語)。別な言い方をすれば物事の共通化ということだ。これで車輪の再発明をせず、新しいゲーム開発に使う大幅な時間の節約ができる。

 現在のゲーム状態(State)を提供するクラスはManagerクラスと呼ばれている。これが次の話題となる。Managerクラスとはなんぞや?

Managerクラス

 Managerクラスはオブジェクト思考テクニックを使った、独自の機能を提供するクラスだ。Managerクラスは全てのゲーム状態(State)やゲームブジェクト
を処理するのに慣れている。それはゲームアプリケーションクラスIAppのパブリックなメンバ変数として扱われる。例えば、現在のどゲーム状態(State)が実行されているか、次のゲーム状態(State)はどう定義されているかなどを管理しいているStateManagerがある。また、画像、音、音楽などのゲームデータがロード済みかどうかを管理しているAssetManagerクラスがある。AssetManagerクラスの中でも気の利いた機能の1として挙げられるのが、複数のゲーム状態(State)で画像、音楽、サウンドなどを共有できる機能だ。

それぞれのゲーム状態(State)は、AssetManagerクラスに欲しいゲームデータを要求する。AssetManagerはいくつゲームデータが参照しているか格納しており、ゲーム状態(State)が画像、音、サウンドなどが不要になったと伝えると、AssetManagerデータ参照はその参照数を1つ減らす。どこからも参照されなくなると、メモリ上から削除する。同じくAssetManagerはゲームデータの遅延ローディングに対応する。これはローディング状態などの特殊なStateの開発に大いに役立つ。ゲームデータの読み込みが完了したら、ゲーム状態(State)はStatManagerクラスに自分自身の削除を要求し、前のゲーム状態(State)に戻す。

 そのうち追加のManagerクラスをこのベーシックゲームエンジンに追加するつもりだ。現在GUIをベーシックゲームエンジンに提供するWidgetManagerを開発中だ。

コンフィグレーションファイル(設定ファイル)

 ベーシックエームエンジンの重要な側面の1つは、柔軟な設定情報をファイルからロードする機能だろう。ConfigReaderクラスは.INIスタイルのファイル読み込みに対応し、ゲーム状態(State)はファイルからどんなタイプの情報もロードする事が出来る。IApp.cppクラスにあるinitSettingsConfigメソッドは、以下のようにConfigReaderの使い方のシンプルなサンプルとなっている(GQE Projectに全ソースあり)。

  void App::InitSettings(void)
  {
    SLOG(App_InitSettingsConfig, SeverityInfo) << std::endl;
    ConfigAsset anSettingsConfig(IApp::APP_SETTINGS);
  }

コメント

素晴らしい!

1つだけ、動作しなかったときのために修正コードを記載する。

result = new ImageAsset(theFilename, theStyle);
assert(NULL != result && "AssetManager::AddImage() unable to allocate memory");

もしnewでメモリを確保できなかったら例外bad_alloc例外を投げる。戻り値にNULLが欲しいなら'(nothrow)' を追加する必要がある

result = new(std::nothrow) ImageAsset(theFilename, theStyle);
assert(NULL != result && "AssetManager::AddImage() unable to allocate memory");

(See http://www.cplusplus.com/reference/std/new/nothrow/)

//Peter Welzien//

賛辞の言葉とコメントをありがとう。あなたの提案はとても参考になりました。変更も適用しています。

//Ryan Lindeman//

Ryan: 投稿ありがとう! 僕はこの手の投稿をここ何年かいくつも読んで(そしていくつか自分でも失敗しつつ実際試してみた)いるけど、君の投稿はその中でもとても適性で明快だったよ。

Peter: サンプルに同じコードがペーストされている部分があったよ。大したことじゃなかったので、自分の頭の中で処理しちゃったけど。

//phobius//

このベーシックゲームエンジンは開発中でチュートリアルも更新の必要があると思います。みなさん是非このゲームエンジンを試してください。

//Ryan Lindeman//

ゲームロジックとレンダラーに関して質問です。これらを分離してスレッド処理するというのはどうでしょう? そうすればグラフィックカードを待たなくて良いし、逆もまたしかり。何か問題があるかな。私の理解不足?


// RebelMoogle //

良い投稿だ。でも前方宣言のところで質問。これはただプリプロセッサ命令の#pragmaで1度、または#ifndef #defineとすればすればヘッダループの問題は無くなるのでは?

//Kevin Kung//
Contact GitHub API Training Shop Blog About

【SMFL非公式和訳】チュートリアル: ベーシックゲームデザイン

この記事は僕がSFMLを学習する為に
Tutorial: Basic Game Design
Tutorial: Basic Game Design · SFML/SFML Wiki · GitHub

を無断で和訳したものです。訳者がSMFL、英語どちらも学習中の為意訳・誤訳ご了承のほど。

 自明のとおり、君には新しいゲームのアイデアがあり、それを実際に生み出したいと思っている。しかし、どこから手をつけたら良いかわからない。

 このチュートリアルでは君のゲームのアイデアをベーシックゲームデザインを通して生み出すことを手助けするために書かれている。もしこのチュートリアルを読んでいる間に大きな間違いを発見したり、役に立ちそうにないと感じたら、君はこのチュートリアルにまったく沿う必要はない。このチュートリアルは数年に渡る私の個人的な趣味として開発したやっつけゲームの経験とコードレビュー、それからネットで見つけたチュートリアルをベースに書かれている。もしあなたがゲーム会社に勤めるプロのゲーム開発者なら是非みんなにあなたの見識を共有してほしい。

 それはともかく、早速ゲームのアイデアから議論していくことにしよう。

Game Idea(ゲームのアイデア)

 まだ未熟な若者だった頃、私は友人や家族、それから学校の先生と、私の最新のゲームアイデアを議論することに夢中だった。もし君があの頃の私と同じなら、君にはまだまだ深化の余地がある。私はゲーム制作のことを熟慮する(年を取るとも言う)に従って、ゲームアイデアというのは、ゲーム制作において全体の1%(か君のアイデアが最高に良くても10%)程度だという事がわかってきた。残りの99%はアイデアを理論化したり実装したりする部分だ。だからベーシックゲームエンジンチュートリアルの制作(とGQEプロジェクト)を、みなさんと、それから私のゲームを形にするために取り組んだわけだ。

 それにくわえ、素晴らしいゲームアイデアというものは、シンプルなコンセプトや理論に基いていることも学んだ。初期のFPS(ファースト・パーソン・シューティング)作品でいえば、その基本コンセプトは「迷路」だ。プレイヤーは先の分からない道をゴールに到達するまで進んでいく。次の面も(さらに多くの敵や人を殺しながら)ゴールを探して道を進む。

 次第にFPSは進化し、中でもマルチプレイヤー型FPSは洗練されオープンな環境になった。

 というわけで、ゲームデザイナーであるあなたの目標は、あなたの能力で実際に制作可能なゲームのアイデアを探すことだ。もし実現するのに複雑すぎるアイデアを選んでしまったら、私の過去の経験と同じく、まずゲームを完成させることができないだろう。

 しかし、絶望する前に伝えておこう。君が実現できる素晴らしいゲームというものは世の中に山ほどある。

 では、それらのゲームの検討を、まずはゲームのジャンルから議論していこう。

Game Genre (ゲームのジャンル)

 人間というものは、カテゴライズしたがるものだ。ゲームデザイナーとて例外ではない。この十数年、ゲーム制作会社は、各ゲームカテゴリになんだか格好良く聞こえる名前を付けて、「ゲームジャンル」リストを生み出してきた。

 最近私は、そういったゲームジャンルリストを使って、自分のゲームアイデアたちを他のゲームとの「違い」や「類似点」やらで分類して、さらに同じゲームジャンルのゲームと私のアイデアも比較して、制作に値するオリジナリティや面白さを兼ね備えた良いアイデアか検討した。結果、とても多くのライバル「アプリ」が見つかった。「似たような」ゲームが存在しない新しいゲームのアイデアを生み出すのは年々難しくなっている。だから、君はそのゲームのアイデアを形にする前に充分に調査し、煮詰める必要がある。

 ゲームアイデアをより深化させるために、ゲームデザインに関するドキュメントを作成することを私はおすすめしたい。ドキュメントがあれば、ゲーム制作にあたって君とその仲間のガイドとなる。さらにこのドキュメントは、アイデアをより深化させ、ゲームのメカニズム実装方法を手早く導き出す手助けにもなる。君が真剣にゲーム開発に取り組んでいるなら、ゲーム制作を始める前にドキュメント制作にも真剣に取り組み、時間を割く必要がある。一般的なゲーム会社ではどんなゲームのアイデアでもデザイナーから、お楽しみ(代償とも言う)の前に、この手のドキュメントを最低限要求する。

 ゲームデザインに関するドキュメントが一旦完成したら、次はゲームのプロトタイプを制作する。ゲーム開発のプロトタイプフェーズにおいては、最小限の核やゲームデザイン上のメカニズムを搭載したものを制作する。ゲームデザインプロトタイプの最初のステップは、ゲームエンジンスタイルの選択だ。

Game Engine Style (ゲームエンジンスタイル)

 いくつものゲームを制作するに従って、それらがいかに似たような実装になるか君は気づくだろう。例えゲームジャンルが異なった場合でも、多くの部分は同じゲームエンジンスタイルで作成することが出来る。たとえば、マルバツゲーム(三目並べ)とアステロイドゲーム(ピンポンゲーム)は同じゲームエンジンで作成できる。たとえアステロイドゲームがアクションゲームに分類され、マルバツゲームが(たぶん)パズルゲームに分類されるとしても。

 上記から言える事として、君はゲームデザイナーとして、ゲームエンジンスタイルの違いを理解し、ゲームデザインを実装するにあたって、これらが君にどんな機能を提供するか理解することだ。

 私の数年のゲームプログラミング経験と文書で得た知識による、ゲームエンジンスタイルの違いを以下のリストで表した。

 私はこれらの経験と、得られた知識に本当に感謝している。だからこのページへの追記や、君のちょっとした思いつきをここに追加することをためらわないで欲しい。

 それぞれのゲームエンジンスタイルはそれぞれ異なる層の複雑性に関わっており、それは君がゲームデザインと目論見をどのようにまとめるのが賢明か、どのゲームエンジンが君のゲームデザインに良くフィットするかに関わってくる。2Dゲームに3Dゲーム用のエンジンスタイルを使おうとすると、大変な目に遭う。もしそれが出来たとしても、不必要で複雑なものを山程用意してやらなければならない。

 それぞれのエンジンの実装スタイルについて、以下のリストにもとづき比較できるように心がけた。

  • オブジェクトマネージメント -- ゲーム内の各要素のマネージメント(管理)
  • コンテンツマネージメント -- 画像や音、音楽などのゲーム内資源のマネージメント
  • ゲームロジック -- ゲームメカニズムやルールの処理
  • ゲームレンダリング -- ゲーム内要素の画像や音の出力処理

Sprite Engine(スプライト エンジン)

 君がゲームデザインとプログラミングのビギナーなら、ここから始めるべきかもしれない。スプライトエンジンを使った最も基本的なコンピューターゲームといえば、ポン(Pong)だ。(※ピンポンゲームの事)

 ポンは偉大な初心者向けゲームだ。しかし私はマルバツゲームを最初のゲーム開発におすすめしたい。マルバツゲームは簡単な入力処理とゲーム状態ロジックのみで制作できる。一方ポンは、それらに加えて動くグラフィックが必要になる。

 スプライトエンジンを使ったプロ開発のゲームは多数存在する。モータルコンバット、スペースインベーダー、Scorched Earthなど(※昔あった砲撃ゲーム) 。さて、スプライトエンジンの実装を定義していこう。

オブジェクトマネージメント

 スプライトエンジンにとってオブジェクト群は、シンプルな配列で一般的に全て表せれる。もしくは全てを個別の変数に格納する。

 一般的に変数はゲームオブジェクトを表す事ができるシンプルな仕組みだ。前述のマルバツゲームの場合、10オブジェクトで表現できる。×が5つ、○が4つ。それからゲーム盤が1つ。ぽんであれば3〜4のオブジェクトになる。棒が2つとボールが1つ。それから仕切りが必要な場合もある。

コンテンツマネージメント

 スプライトエンジンの場合、コンテンツもオブジェクトと同じくシンプルだ。プログラマーはゲームオブジェクトを表示させる際、そのコードのすぐ側でオブジェクトの画像ファイル1つずつを毎度直接コードで指定する。プログラマーがホントに頭が良かったらそれらは上手く結び付けられ、必要なものが必要なだけ画面に出力される。

 そういったわけで、一般的にこのゲームエンジンスタイルでは、ゲームコンテンツの管理が出来るだけ少なくなるよう考慮される。

ゲームロジック

 スプライトエンジンの場合、ゲームロジックは一般的にIF/SWITCHなどの条件分岐文をハードコーディング(直書き)をすることによって構成される。これらの条件分岐は、動くゲームオブジェクトに対する衝突判定や画面の境界線を判断するためのものにすぎない。ポンの場合、いくつかのシンプルなロジックで動くボールを壁や棒で跳ね返せる。マルバツゲームの場合、そのマスがすでに取られているか、3マス並んでいるかを監視するのに用いられる。

ゲームレンダリング

 スプライトエンジンの場合、グラフィックの表示は、ゲームエンジンを通してループ(繰り返し)毎に、ゲームオブジェクトを画面に描画することで構成されている。多くの場合、スプライトエンジンはゲームオブジェクトの複雑な動きを許容しないので、画面外のオブジェクトに何が起っているかなど面倒な事を考えなくてよい。しかし、あなたがそんな動きを許した場合、ゲームエンジンをループするたびに全てのオブジェクトがそのループを通ることになるので、それなりの負荷が発生することになる。

Tile Engine (タイルエンジン)

 スプライトエンジンゲームを作れる程度に技能が上達したら、タイルエンジンの開発を次に検討すべきだ。

 タイルエンジンの流通レベルのゲームはたくさんある。素晴らしい例で言えば、チェッカー、チェス、マリオブラザーズ、ゼルダの伝説、ウルティマ1-4などなど。

 ではタイルエンジンとは何かを定義していこう。

オブジェクトマネージメント

 タイルエンジンではゲームオブジェクトを、背景(バックグラウンド)、前景(フォアグラウンド)と2つに分類して取り扱う。前景は一般的にシンプルな配列でゲームオブジェクトや値を管理する。背景用のゲームオブジェクトは、ゲーム世界をタイル状に分割し、マップと呼ばれる方法や多次元配列で管理する。このような複雑なデータは充分上手く管理する必要があるが、他のゲームエンジンスタイルのそれと比べれば理解も実装も用意だろう。

コンテンツマネージメント

 コンテンツマネージメントも、タイルエンジンにおいてはやや複雑となる。一般的に、背景タイルなどの画像データを1枚の巨大な画像ファイルに全て格納し、グラフィックカードでその画像ファイルをロードする。その後ゲームエンジンのループ内で適宜画像を切り出し、背景を描写する。

 さらに、背景ファイルに余裕がなく、グラフィックカードが可能であれば、2枚目の巨大な画像ファイルを前景のために用意し、全ての前景画像データを格納する。

 このように巨大な画像ファイルの取り扱いさえ上手く行けば、スプライトエンジンと比較してもさほど複雑にはならないだろう。

ゲームロジック

 ゲームロジックの処理もタイルエンジンにとっては複雑さを増す要素の1つだ、なぜなら、タイルエンジンのゲーム世界は大きくなり、ゲームロジックはゲームエンジンループ内で時間をさらに要求する。これはタイルエンジンスタイルのゲームではより多くのゲームオブジェクトがゲーム内で飛び回るからだ。これはあなたのゲームデザイン実装をより困難にする。

 そのため、ゲーム開発者たちは、スクリプトエンジンを実装することでゲームメカニズム実装をより簡単にしようとする事が少なくない。ただ、君がいくつかの簡単なタイルエンジンスタイルのゲームを完成させるまでは、私はスクリプトエンジンの組み込みをおすすめしない。スクリプトエンジンによって得られるメリットと、スクリプトエンジン自体の実装の負担を天秤にかけれるようになってから、初めて検討すべきだろう。

ゲームレンダリング

 もし上手く実装できなければ、ゲームレンダリングは、タイルエンジンにとって大きなボトルネック(負荷)に成りうる。SFMLフォーラムで、これを上手く実装するための手助けとなるスレッドをいくつか見つけられるだろう。

 ストレートなやり方としては、マップ情報を元に全ての背景タイルを個別のスプライトでゲームループ毎に描き出すことだ。このやり方だと768のスプライト(1024x768ピクセルの画面を32x32ピクセルで埋める)を毎ループ描き出す事になる。しかもまだ前景は含まれていない。もしあなたの背景がアニメーションしているなら、どうしてもこの高コストな方法は避けられない。

 他の手段としては、まず全ての背景タイルを1枚の画像にまとめてから、それを一度に描画し、その後前衛を描画する。この方法であれば、ただ巨大な画像で描かれたゲーム世界の前衛を、ゲームオブジェクトか動きまわるだけだ。こうすれば、背景の再描画は768のスプライトから、前衛のゲームオブジェクトが移動した部分、1移動につき2タイル分のスプライトで済む。さらに画面移動分の描画についても、元の背景画像データをコピーして利用できる。

 ただ、どちらにせよスプライトエンジンよりは複雑さを増すだろう。

Storybook Engine (ストーリーブックエンジン)

(※アドベンチャーゲーム用エンジン)

 ストーリーブックエンジンと聞いて君は明確にイメージできないかもしれない。ストーリーブックエンジンを使った商業ゲームはいくつもあります、Kings Questシリーズ, Police Questシリーズ, Space Questシリーズ, Mystシリーズなど。これらの名称を聞いて「ああ、ああいうのね」とピンときたかもしれない。私はこのタイプのゲームの熱狂的なファンだと白状しよう。ただ、私自身がこの手のゲーム開発について熟知しているというわけではない。

オブジェクトマネージメント

 ストーリーブックでもオブジェクトは2種類として取り扱う。前景(フォアグラウンド)と背景(バックグラウンド)。 

 前景は一般的に、プレイヤーの操作に基いて反応するオジェクトたちをシンプルな配列に格納して取り扱う。それ以外のプレイヤーが注意するに値しないオブジェクトたちを背景として取り扱う。一般的にストーリーブックエンジンのオブジェクトマネージメントは、タイルエンジンのそれほどは複雑にはならないだろう。

コンテンツマネージメント

 一方コンテンツマネージメントは、タイルエンジンのそれより多くの課題をはらんでいる。一般的にストーリーエンジンゲームでは大きな画像群を表示する必要がある。タイルエンジンであれば、小さな画像をまとめあげて、繰り返し表示するだけだが、ストーリーエンジンの場合、全画面に背景を表示し、さらに前衛に別の大きな画像を表示する必要がある。ただ、それらは多くの場合静的で、プレイヤーが何か操作するまで停止している、もしくはアニメーションを繰り返している。

 こういった環境は、しばしばゲームが動作するプラットフォームや、ユーザーのゲーム環境に大きく制限を加える事になる。しかしやりようが無いわけではない。

ゲームロジック

 ストーリーブックエンジンの場合、ゲームロジックの処理はかなり複雑にも、とてもシンプルにも成りうる。多くのストーリーブックエンジンはスクリプト言語エンジンを実装している。それはスクリプトとコンテンツの書き換えだけでゲームに変更を加える事を可能にする。最近のストーリーブックエンジンゲームは、カーソル選択と決定程度の入力しか必要としない事が理由だ。例えば、カーソル移動を追跡してどのオブジェクトをプレイヤーが掴んでいて、それが正解どうか、といった事を処理しない。また、改変、続編の制作も容易となる。これは、手間を考えてもスクリプトエンジンをゲームロジックに組み込む背景となる。

ゲームレンダリング

 もし上手く実装できなければ、ゲームレンダリングは、ストーリーブックエンジンにとって大きなボトルネックに成りうる。しかし、ゲームロジックはとても簡単。それほどゲームレンダリングを調整する必要はないだろう。多少スプライトエンジンのそれより複雑なくらいで、タイルエンジンほど複雑にはならないだろう。

ファーストパーソンシューターエンジン(FPSエンジン)

 今ゲーム制作をする人の多くが、FPSゲームエンジンを使いこなそうと努力している。今日のゲーム業界でFPSゲームが益々勢力を拡大しているのが理由だ。ただ、複雑さの観点で言えば、FPSエンジンはとても複雑だ。マルチプレイヤー対応の場合は輪をかけて難しくなる。あなたが真剣にFPSゲームを制作しようと考えているなら、ネットで探せるオープンソースのFPSエンジンを採用してゲーム制作をする事を真剣におすすめする。この業界では、IDソフトウェアがFPSエンジンのソースコード提供に積極的だ。

オブジェクトマネージメント

 FPSにとってオブジェクトマネージメントは大きな仕事だ。今まで紹介してきたゲームエンジンよりもさらに多くのゲームオブジェクト管理が必要となる。そのほとんどはゲームループで画面を出力するための画像出力命令として管理されている。FPSゲーム制作は10-15年前の黎明期よりかなり複雑になっている。

コンテンツマネージメント

 FPSエンジンのコンテンツは多分、タイルエンジンやストーリーブックエンジンのそれより多少複雑な程度だろう。FPSエンジンのコンテンツは複雑にもシンプルにもすることが出来る。多くの人が考えるように素晴らしいゲームはコンテンツのリッチさよりプレイそのものやレベルデザインにある。ただ、そのどちらも実現できればより多くのファンを獲得できるだろう。

ゲームロジック

 ゲームロジックは今まで紹介してきたどんなゲームエンジンと比較しても複雑だ。その理由は二次元ではなく三次元の世界を取り扱わなければならないからだ。

 いくつかの初期FPSエンジンは二次元を取り扱うため専用にデザインされた。まだその頃のハードウェアは三次元の複雑な問題を解決できるようになっていなかったからだ。今のFPSエンジンでこれは一般的な問題では無くなった。このゲームエンジンスタイルで最も複雑な側面は、弾丸やらロケットとオブジェクトの衝突判定を常に行わなければならい点だ。それ以外にもオブジェクトが三次元の正しい場所に正しく描画させるためにも多大な労力が必要となる。

ゲームレンダリング

 FPSゲームエンジンにおいてレンダリングは他のゲームエンジンと比較してもっとも複雑になる。毎フレームの描画負荷を低減するために、どのオブジェクトを描画するか、スキップするかなどの取り組みに多大な労力が必要となる。

 こういったオブジェクトの整理はゲームが始まる前(背景や壁などの、弾丸自体、階段などの静止オブジェクト)に完了するか、ゲーム動作中(血しぶき、体の一部、窓など前景オブジェクト)に行わる。マルチプレイヤー対応を行えばさらに複雑となる。

シミュレーションエンジン

 シミュレーションエンジンとFPSエンジンの違いはそれほど大きくないだろう。ただ、シミュレーションエンジンは、FPSエンジンと比較しても厳密な物理処理を要求する事がある。シミュレーションエンジンが多分組み込まれている例でいえば、タイガー・ウッズゴルフ、フライトシミュレーター、フットボールシミュレーターなどが挙げられる。

オブジェクトマネージメント

 シミュレーションエンジンの場合、ゲームオブジェクトマネージメントはとても大きな仕事となる。これほど多くのオブジェクトを扱うゲームエンジンスタイルは他にはない。それらのオブジェクトは物理的な状態を情報として伴っている。ゲームオブジェクトの正確なモデリングナシでは、現実世界の物理現象を表現することはできない。というわけで、最近のFPSゲームエンジンがシミュレーションエンジンと似通ってくるのは驚くに値しない。ただ、ゲームを面白くするために物理現象をゲームらしく再現したほうが良い場合もある。

コンテンツマネージメント

 シミュレーションエンジンにとって、コンテンツはFPSエンジンと同じように扱われるだろう。唯一の大きな違いとしては、シミュレーションエンジンのほうがより厳密に光源を管理している点だ。

ゲームロジック

 シミュレーションエンジンにとってゲームロジックの処理はFPSにくらべてより厳密に取り扱わなければならない。それはリアルな世界の計算を実数を使って処理をするためだ。これにより、コンパイルエラーを始めとした様々な問題が簡単に発生するようになる。

 FPSエンジンでは2つの自由度しか移動が許可されていないが、シミュレーターエンジンではしばしば6自由度の移動が許可される。これは衝突判定やその他の事柄から発生する処理パフォーマンスに大きく関連してくる。さらにいえば、これらが思惑通りに配置され正しく表示されるかに多大な注意を払わなければならない。

ゲームレンダリング

 ゲームレンダリングはシミュレーションエンジンにとっておそらくFPSエンジンよりさらに複雑になる。毎フレームの描画負荷を低減するために、どのオブジェクトを描画するか、スキップするかなどの取り組みに多大な労力が必要となる。

 こういったオブジェクトの整理はゲームが始まる前(背景オブジェクト)に完了するか、ゲーム動作中(前景オブジェクト)に行わる。マルチプレイヤー対応を行えばさらに複雑となる。

マルチユーザーダンジョンエンジン(MUDエンジン)

 マルチユーザーダンジョンエンジン、またはMUDエンジンは、マッシブ(大規模)マルチプレイヤーロールプレイングゲームエンジンの前身とみなされる事がしばしばある。これは、多くのMUDゲームエンジンが、前インターネット時代のテキストエンジンゲームから進化してきたためだ。このゲームエンジンスタイルは、グラフィックやその他の要素より、実際のプレイヤーのゲーム経験が反映され、練磨されてきた部分が大きい。

オブジェクトマネージメント

 MUDエンジンにとってゲームオブジェクトマネジメントは、他に紹介してきたどんなゲームエンジンより複雑になりうる。それは、このエンジンが本質的に多数のプレイヤーが一度にゲームに参加することに対応しているためだ。これによりオブジェクトマネージメントがとても重要なものとなる。君もこの手のゲームをプレイしている時に、ほぼ同じタイミングで他プレイヤーと同じタイミングでアイテムを拾おうとしたことがあると思う。これを疎かにすると参加プレイヤーが1つのアイテムから人数分のアイテムを複製できる事になる。

コンテンツマネージメント

 MUDエンジンにとってコンテンツマネージメントは、他に紹介してきたどんなゲームエンジンよりも複雑になりうる。なぜならプレイヤー達は探険、経験、拾得などゲームの内容を1人でプレイする1時間よりさらに早く消費する事が可能だからだ。

 自分のゲームをホットな状態に維持するために新しいコンテンツを追加して行くことは、コンテンツマネージメントにより複雑性をもたらすだろう。

ゲームロジック

 MUDエンジンにとってゲームロジックの処理は、マルチプレイヤーのアカウントを管理し、だれが同時にシステムにアクセスしているのかを管理するのが大変なだけである。一般的にゲームメカニズムやルールはそれほど複雑にはならないだろう。しかしながらプレイヤーイベントやゲームの状態を永続的に管理する必要があり、これはかなり難しい。これを解決するために多くのMUDエンジンでは、データベースを使ってゲームオブジェクトとユーザーデータを管理する事を試みている。これが単に1つのコンピューター上で、1人のプレイヤー情報を管理する場合よりゲーム開発の難易度をあげることになる。このゲームシステムの管理がゲーム会社に大きな負担を強いらせているのだ。

ゲームレンダリング

 MUDエンジンにとってグラフィックスレンダリングとはタイルエンジンと同じかもしれない。これはMUDエンジンの肝がゲームロジックとゲームオブジェクトにあり、ゲームの描画部分は大した売りでは無いからだ。多くのゲームプレイヤーは、探険中に何がどう見えるのかではなく、新たな体験、探険自体を欲しているのである。しかしながら最近のMMORPGゲームはいくぶん考え方を変えつつあるようだ。

マッシブ(大規模)マルチプレイヤーオンラインロールプレイングゲーム (MMORPG) エンジン

 前述のとおり、多くのMMORPGエンジンは古きMUDエンジンが原点となっている。それらの最も大きな違いは、同時に多くのプレイヤーがゲームに参加する点だ。例を挙げると、Everquest、World of Warcraft、Star Wars Online、Ultima Onlineなどが挙げられる。

オブジェクトマネージメント

 MMORPGエンジンにとってオブジェクトマネージメントはMUDエンジンより複雑で、さらに言えば今までに紹介してきたエンジンスタイルのどれよりも複雑だ。これは接続した複数のプレイヤーを同じ場所にいる状態を管理する必要があるからだ。

 そのため、ゲームエンジンをできるだけシンプルに留めるためにプレイヤーは仮想的な複数の世界に分割される。別の言い方をすると、複数の仮想ワールドXYZがありつつ、それぞれのプレイヤーがお互いを視認できるよう1つのワールドXYZに配置される。プレイヤーがワールドXYZ Aに割り当てられ、もう一方のプレイヤーがワールドXYZ Bに配置されるとする。そうすれば2人は同じワールドに存在しているように見える。ただしプレイ中はお互いを視認できない。(もしこの認識が間違っていたら是非誤りを指摘してほしい)

コンテンツマネージメント

 MMORPGエンジンにとってコンテンツマネージメントはMUDエンジンのそれよりさらに複雑になる。半年から2年かけて開発したような新しいコンテンツをプレイヤーはほんの1日で消費してしまう。全コンテンツの生産管理と新しいコンテンツの同時リリースは大変なチャレンジとなる。全プレイヤーを満足させるコンテンツの質・量の投入はかなり難しいため、新しいコンテンツ開発の間、何かべつの方法でユーザーにゲーム参加の目的を用意してやる必要がある。

ゲームロジック

 MMORPGエンジンにとってゲームロジックの処理は、マルチプレイヤーのアカウントを管理し、だれが同時にシステムにアクセスしているのかを管理するのが大変なだけである。一般的にゲームメカニズムやルールはそれほど複雑にはならないだろう。しかしながらプレイヤーイベントやゲームの状態を永続的に管理する必要があり、これはかなり難しい。

 さらにいえば、FPSエンジンのような見た目を表現するためにゲームオブジェクトを処理しなければならない。これはMUDエンジンゲーム以上にゲームロジックを複雑にする。

ゲームレンダリング

 MMORPGエンジンにとってゲームレンダリングはFPSエンジンのそれとおなじになるかもしれない。最近ではMUDエンジンゲームより見た目で勝負する必要があるからだ。特にゲーム内のコンテンツを全て探索しきってしまったプレイヤーに対して、見た目は大事だ。


 結論として、現在いくつかの違ったタイプのゲームエンジンスタイルが世の中で使われている事を伝えたい。大事な事は、君のゲームデザインに適したゲームエンジンスタイルを採用することだ。また、誰も試したことのないような新しいチャレンジに臆する必要が無いこともあわせて伝えたい。君がどのゲームエンジンが自分のプロジェクトに適しているか決めたら、次は君のゲームではゲームオブジェクトをどのように管理すべきかを検討するべきだ。ではそれについて次から議論していこう。

ゲームオブジェクトマネージメント

近日公開...

ゲームコンテンツマネージメント

近日公開...

ゲームの配布

近日公開...

コメント欄

 ベーシックゲームデザインからゲームを生み出す君のやり方を是非気軽に上記トピックに追加してほしい。君はどうやってゲーム完成までのモチベーションを維持しただろうか?

※訳者注 最終更新から1年以上経過しても残りのコンテンツが追加されないため、更新はないと思われます

【SMFL非公式和訳】チュートリアル: ダイナミックキーバインディングマネージャー

この記事は僕がSFMLを学習する為に
Tutorial: Manage dynamic key binding
https://github.com/SFML/SFML/wiki/Tutorial%3A-Manage-dynamic-key-binding
を無断で和訳したものです。訳者がSMFL、英語どちらも学習中の為意訳・誤訳ご了承のほど。


キーバインドとは、ゲーム中のキー操作とアクションを関連付けしたものです。これによって移動キーでキャラクターを動かしたり、エンターキーで攻撃させたりできます。FPSを作りたければマウスの左クリックで銃弾を発射させるという具合です。もう少し具体的に言えば私が言う「アクション」とは、イベントを関連付ける事です。

え? 左ききプレイヤーにはどう対処すればいいかですって? きっと彼らは左クリックの代わりに右クリックで攻撃したいに違いない! ご安心を。キーバインドは簡単にそれを実現します。そのアクションを新しいイベントに関連付けるだけです。

私のこのやり方はベストではありませんが、私にとっては充分機能しています。

まず最初に、SFMLイベントタイプ、 マウス、キーボード、ジョイスティックをそれぞれ検知する必要があります。違う入力デバイスは別々に扱ってやらなくてはなりません。

enum InputType
{
    KeyboardInput,
    MouseInput,
    JoystickInput
};

その次に、バインディングを定義します。

struct MyKeys
{
    InputType myInputType;
    sf::Event::EventType myEventType;
    sf::Keyboard::Key myKeyCode;
    sf::Mouse::Button myMouseButton;
};

ほら、とても簡単な仕組みでアクションを特定のイベントに関連付けることができました。
あなたのメインプログラムに以下のようにstd::mapでバインディングを格納してください。xmlやその他の方法でも実現できます。

    std::map<std::string,MyKeys> Keys;
    MyKeys key;

    // マウス左ボタンに「撃つ」アクションをバインドする
    key.myInputType = MouseInput;
    key.myEventType = sf::Event::MouseButtonPressed;
    key.myMouseButton = sf::Mouse::Left;
    Keys["Shoot"] = key;

    // エンターキーに「ジャンプ」アクションをバインドする
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::Return;
    Keys["Jump"] = key;

    // 左CTRLキーに「使う」アクションをバインドする
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::LControl;
    Keys["Use"] = key;

std::mapを使うと簡単にアクションを名前で管理することができます。

そして、イベントマネージャーをこんなダイナミックなものに改善する事ができました。

    // イベント管理
    while (App.pollEvent(Event))
    {
        bla bla bla...
        // My events
        // 撃つイベント
        if (TestEvent(Keys["Shoot"], Event))
        {
            Shoot();
        }
        if (TestEvent(Keys["Jump"], Event))
        {
            Jump();
        }
        if (TestEvent(Keys["Use"], Event))
        {
            Use ();
        }
    }

結果、アクションを分割して名前で管理する事ができるようになりました。これで関数やコードやらを直感的に扱えます。
TestEvent関数:

bool TestEvent(MyKeys k, sf::Event e)
{
    // マウスイベント
    if (k.myInputType == MouseInput &&
        k.myEventType == e.type &&
        k.myMouseButton == e.mouseButton.button)
    {
        return (true);
    }
    // キーボードイベント
    if (k.myInputType == KeyboardInput &&
        k.myEventType == e.type &&
        k.myKeyCode == e.key.code)
    {
        return (true);
    }
    return (false);
}

アクションが実行出来る時、この関数はtrueを返します。何が実際に出来るかは書く必要はありません。実行できることだけを知っていれば良いのです。

テスト用の完全なサンプルコードはこちら:

#include <iostream>
#include <fstream>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>

enum InputType
{
    KeyboardInput,
    MouseInput,
    JoystickInput
};

struct MyKeys
{
    InputType myInputType;
    sf::Event::EventType myEventType;
    sf::Keyboard::Key myKeyCode;
    sf::Mouse::Button myMouseButton;
};

bool TestEvent(MyKeys k, sf::Event e);
void Shoot(void);
void Jump(void);

int main(int argc, char** argv)
{
    // Variables for main
    sf::RenderWindow App;
    bool Running = true;
    sf::Event Event;

    // Variables for demo
    std::map<std::string,MyKeys> Keys;
    MyKeys key;

    // Let's bind the left mouse button to the "Shoot" action
    key.myInputType = MouseInput;
    key.myEventType = sf::Event::MouseButtonPressed;
    key.myMouseButton = sf::Mouse::Left;
    Keys["Shoot"] = key;

    // Let's bind the Return key to the "Jump" action
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::Return;
    Keys["Jump"] = key;

    // Let's bind the Left Control key to the "Use" action
    key.myInputType = KeyboardInput;
    key.myEventType = sf::Event::KeyPressed;
    key.myKeyCode = sf::Keyboard::LControl;
    Keys["Use"] = key;

    // Window creation
    App.create(sf::VideoMode(640, 480, 16), "config test");

    // Main loop
    while (Running)
    {
        // Manage Events
        while (App.pollEvent(Event))
        {
            // Using Event normally

            // Window closed
            if (Event.type == sf::Event::Closed)
            {
                Running = false;
            }

            // Key pressed
            if (Event.type == sf::Event::KeyPressed)
            {
                switch (Event.key.code)
                {
                    case sf::Keyboard::Escape :
                        Running = false;
                        break;
                    case sf::Keyboard::A :
                        std::cout << "Key A !" << std::endl;
                        break;
                    default :
                        break;
                }
            }

            // Using Event for binding
            // Shoot
            if (TestEvent(Keys["Shoot"], Event))
            {
                // You can use a function
                Shoot();
            }
            if (TestEvent(Keys["Jump"], Event))
            {
                Jump();
            }
            if (TestEvent(Keys["Use"], Event))
            {
                // or only code
                std::cout << "Use !" << std::endl;
            }
        }

        // Display the result
        App.display();
    }

    // End of application
    return EXIT_SUCCESS;
}

bool TestEvent(MyKeys k, sf::Event e)
{
    // Mouse event
    if (k.myInputType == MouseInput &&
        k.myEventType == e.type &&
        k.myMouseButton == e.mouseButton.button)
    {
        return (true);
    }
    // Keyboard event
    if (k.myInputType == KeyboardInput &&
        k.myEventType == e.type &&
        k.myKeyCode == e.key.code)
    {
        return (true);
    }
    return (false);
}

void Shoot(void)
{
    std::cout << "Shoot !" << std::endl;
}

void Jump(void)
{
    std::cout << "Jump !" << std::endl;
}

C++の勉強その5(ゲームエンジン)

わからないものはわからないままに、C++ゲームエンジン&実装の勉強へと進む。まずはゲームエンジン最右翼のcocos2d-xから。


Cocos2d-x: オープンソースゲーム開発プラットフォーム

http://jp.cocos.com/

表紙だけ日本語でドキュメントは英語の模様。
http://cocos2d-x.org/docs/programmers-guide/about/

読み進めていると…audioファイル対応がwindowsはwavのみ。macにいたっては対応ファイルタイプについて何も書いていない。audio関連が不足していると考える人々はCricket Audioなど外部のものを使うのが常套手段になっている。

そんな背景から、他のエンジンは無いものかと調べた。するとSFMLというのが海外では受けが良いらしい。

SFML

http://www.sfml-dev.org/index.php


SFMLは 正確にはゲームエンジンではなくマルチプラットフォーム対応のマルチメディアライブラリだと思うけど、端的に言ってゲーム用と言ってしまって良いと思われる。こちらにはWAV, OGG/Vorbis and FLAC対応と書かれている。しかもメモリロードやバイナリストリームロードなどイロイロ機能がある。

一方、Cocos2d-xはさらに物理エンジンとか3D対応とかVR対応なんてのもついてる。いらないものはいいんだけど、例えば衝突判定が入ってたり、UI簡単に作れたり、外部ツールが整ってたり。機能の数だけで言えば圧倒的にCocos2d-xに軍配があがる。

5分くらい考えて、SFMLを採用した。勉強になるし、ブラックボックス化した部分に躓いてプロジェクトが停滞するのはちょっと避けたかった。衝突判定はさらに別のライブラリ(Box2Dなんか)でやろう。

ただ、cocos2d-xのチュートリアルは後ほど全部目を通す事にする。全体設計や実装コードのアイデアになるものが多い。

サイトA:プログラミング SFMLチュートリアル 非公式日本語翻訳版

http://www.site-a.info/programming/sfml/SFML_unofficialTranslation.html

ありがたいことにSFMLチュートリアルとFAQを和訳してくださっているサイトを発見したので、そちらで読み進める。しかも最新版対応! かわいい日本語訳なのでとっても読みやすい。

ここでは、全体の設計をイメージできる程度に機能の確認をしつつ流し読み。

  • スレッド化の感所について
    • ゲーム開発経験が無いのでスレッドを使うポイントが良くわからない。非同期処理は別のもので出来るよね?→必要に迫られたら使うものらしい
    • ちょっと良くわからないので、マルチメディアのロード処理なんかで重くなってから使う気持ちでいる
  • ファイルデータ入力ストリームについて
    • リリース段階ではzip圧縮して.dat拡張子にするのが一般的らしいがSFMLにはzipを扱う機能は無さそうなので、外から引っ張ってこないといけない? 自分でバイナリデータの結合仕様とかは書きたくない
    • とりあえずあるもので実装して後で書き直しやすいようにしておく
  • 2dオブジェクトの衝突判定に使えるバウンディングボックスはオブジェクトのサイズと等しいらしい。オブジェクトのサイズ=読み込み時のスプライトサイズ(多分)このままじゃちょっと衝突判定には使えないかな? 動かない壁とかには使えるかな?
チュートリアルを呼んだ感想
  • SFMLはスプライトのアニメーションも自分で書くくらいにシンプルなものらしい

http://www.gamefromscratch.com/post/2015/10/26/SFML-CPP-Tutorial-Spritesheets-and-Animation.aspx

  • 実際のゲーム開発に役立しそうなソースコード群。こちらにスプライトアニメーションクラスなどのソースがある(有志による)

https://github.com/SFML/SFML/wiki/Sources

  • メモリ管理を自前でやらない方がいいのはなぜ?(FAQより)
    • C++だけどメモリ管理とか今更手動でするなや! といった論調
    • もうSTLで自動管理してもらおうと思う。。
    • フレームワークというよりはやはりライブラリ群という印象

Tutorial: Basic Game Engine

https://github.com/SFML/SFML/wiki/Tutorial%3A-Basic-Game-Engine

  • main()には起動(とexit)に関わる事のみ入れて、run()以下にゲームの初期化など。その下のGameLoop()内に実際のゲームを置く。これってエンジンの実装なのか、エンジンを使ったゲームの実装なのか…?
  • 英語だし、初学者がざっと流し読みしただけでは理解できる内容ではなかった。リアルなTIPSが多そうなので、自分で作る際にじっくり読み込む

Cocos2d-x Programmers Guide

http://cocos2d-x.org/docs/programmers-guide/about/

  • ゲーム機能毎にシーンというオブジェクトを用意して、各シーンにされにその機能のオブジェクトを管理させる、という構造らしい。わかりやすい
  • 全ての変数でauto型(c+11の型推定)を使っている
  • ツリー構造によるz値(奥行き)の管理、アクション関連のキュー取り扱いなどとても参考になった。使いたいけど…我慢

次は、SFMLで作ったBasic Game Engineとやらのドキュメントを読み込んで、僕のゲームの全体設計の手がかりにする。それが終わったらそろそろ手を動かそう。文章を読むのに飽きてきた。

C++の勉強その4

平山先生の本は終わり。数学的なもの、メモリ周りは唸る部分も多かったが、やっぱりコードの保守性が考え方がだいぶ違うなあ。演算子書き換えまくったり。演算子のオーバーロードなんてフレームワーク書くとかじゃないとやりたくない。ゲームプログラマ特有のものかしらん。ブラックボックスな標準ライブラリ使う位ならO(n)かO(log n)じゃい! みたいのもちょっと気になった。

僕のゲームは2Dなので衝突とNPC AIだけ気にすれば多分大丈夫だろう。

さてさて、次はC++系Web資料にざっと目をとおしていく。終わったらcocos2d-x資料に目を通してhello worldの予定。

C++編【標準ライブラリ】

http://ppp-lab.sakura.ne.jp/cpp/library/index.html

  • 標準ライブラリの勉強。STLも標準ライブラリの一部らしい
  • 前のが通らなかったのは規格が違うかららしい

というかXcodeから入れたgccコンパイラのバージョンいくつなの?

~ $ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.3.0 (clang-703.0.31)
Target: x86_64-apple-darwin15.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

あれ。Xcodeから入ったのはgccじゃなくてclang?
ちょっと良くわからないのでc++11のコードを書いて通してみる。

//test.cpp
#include <iostream>
using namespace std;
class C {
        int i;
        public:
        C (int ia) : i(ia) {}
        C () : C (123) {}
 };
int main(int argc, char const* argv[])
{
        C c;
        return 0;
}
~$ g++ test.cpp
test.cpp:7:16: error: delegating constructors are permitted only in C++11
        C () : C (123) {}
               ^
1 error generated.

Wikipedia: C++11 を参考に
http://kaworu.jpn.org/cpp/C++11

~$ clang++ test.cpp -std=c++11
~$

通った。


Xcode 4 でデフォルトになった LLVM って何?
http://blog.fenrir-inc.com/jp/2011/07/llvm.html

違うものなのか。。mac/winでコンパイル予定だけど何か不具合があるといやだなあ。まあその時はgccを単独で入れよう。とりあえずc++11でかければいいや。というわけで、ドキュメントに戻る。STLに関しては合わせて以下も読む。

[C++] STLの型の使い分け
http://qiita.com/h_hiro_/items/a83a8fd2391d4a3f0e1c

  • mapはバイナリツリーでunorded_mapはチェイン法で実装されている。昔チェインハッシュ自分で書いたなぁ…(2度と書きたくない)
Let's Boost

http://www.kmonos.net/alang/boost/

正規表現、日付、シリアライズ(&デシリアライズ)、スレッド、乱数その他。

C++ビギナーに捧ぐ EffectiveC++入門

http://www002.upp.so-net.ne.jp/ys_oota/effec/

Effective-C++を買って読むと全然進みそうにないのでコチラをナナメ読み

  • クラスインターフェースはできる完全かつ最小限になるまで、よく設計を吟味する
    • んー。インターフェイスを機能で分けたほうが実装側の保守性があがるし…トレードオフじゃないかな
  • 関数内で new したオブジェクトのリファレンスを返してはいけない
    • main以外で作ったものは全部全部deleteしなきゃならないみたいな話になるんじゃないのか。気持ちは分かるけど
Google C++スタイルガイド 日本語訳

http://www.textdrop.net/google-styleguide-ja/cppguide.xml

だいたいこれに沿って書こうと思う。

  • 関数内で使う変数は、できるだけスコープを限定してください。そして宣言時には変数を初期化してください
  • 一般的にコンストラクタでは、メンバ変数の初期値の設定だけをするべきです。複雑な初期化をする場合には、明示的に Init() メソッドを使うべきです
  • 引数が1つのコンストラクタにはC++の explicit キーワードを付けてください
  • struct はデータを運ぶ受動的なオブジェクトだけに使ってください。それ以外にはすべて class を使ってください
  • たいていの場合、継承よりもコンポジションを使った方が適切です。継承を使うときには、public 継承にしてください
    • これは判断がイロイロ難しい
  • 特殊な状況を除き、演算子をオーバーロードしてはいけません
  • ポインタが本当に必要になったときには scoped_ptr を使いましょう。オブジェクトのオーナーシップを本当に共有する必要があるとき(たとえばSTLコンテナの内部)には、非constの std::tr1::shared_ptr を使うべきです。auto_ptr を使ってはいけません
    • どうなんだろう。生ポインタじゃダメかな…。書きながら検討
  • ファイル名はすべて小文字にするべきです。アンダースコア(_)やダッシュ(-)を含んでも構いません
  • 型名は大文字で始めて、単語の頭文字を大文字にして、アンダースコアを使わないようにしてください。MyExcitingClass、MyExcitingEnum など
    • enum は全部大文字で
  • 変数名はすべて小文字にして、単語と単語の間にはアンダースコアを入れてください。クラスメンバ変数はアンダースコアで終わるようにします。たとえば my_exciting_local_variable、my_exciting_member_variable_ など
  • 定数名k で始まり、大文字小文字で続けてください。たとえば kDaysInAWeek など
  • 通常の関数は大文字小文字の組み合わせにしてください。アクセサやミューテータは変数名と一致させてください。 MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable()
    • キャメルケースのほうでいいかな
  • 名前空間名はすべて小文字で、プロジェクト名とできればディレクトリ構造に基づく名前にしてください。たとえば google_awesome_project など


とりあえずcpp関連の情報はここまで。

C++の勉強その3

 ゲームプログラマになる前に覚えておきたい技術に戻って読書再開〜読了。3D部分などはすっとばした。ここからの資料はざっと流し読みにして、実際の開発の際に再読することにする。

【今日の発見】

  • newしないクラス呼び出しがスタックの呼び出しでnewをつけるとヒープでメモリを取るらしい
    • クラス関数を直接叩く時に使うと勘違いしていた
    • 逆説的にnewはかなり重いってことみたい
  • 配列に格納されるものは型を揃えないといけないらしい
    • 仕組みを考えれば当然なんだけど、他言語ではあまり見かけないためびっくり。多種のモンスターインスタンスを配列に突っ込む際、全部同じ型になるようにするか、別の方法を模索するしかないよう
  • unsigned int型は4バイトで32ビットが基本だけど環境依存。int32_tなどでビット数を固定できるらしい
  • 基底クラスのポインタは継承子クラスで使えるらしい(なんか旧に緩い仕様な気が…)

 勘違いしている事や理解していない事がいっぱいで先行き不安。