SlideShare a Scribd company logo
どこでも動くゲームを作るための
   ベタープラクティス



       西山信行
      5mingame2
C++初心者で~
ゲームを作るのが大好き☆プログラマ

続きはWebで (「でらうま」で検索)
「こないだサターンでゲーム作ったんだ
けどさ…」「オレ持ってんのプレステな
んだよね」
ハイドライド(1984)        テグザー(1985)
PC-8801             PC-8001mkIISR
X1/turbo            PC-8801
FM7/77/77AV         X1
MZ-2000/2200/2500   FM-7/77/77AV
MSX MSX2            ファミリーコンピュータ
PC-6001MkII         MZ-2500
PC-9801U/F          PC-9801
ファミリーコンピュータ         MSX
                    PC/AT(PC-DOS)
                    AppleIIGS
                    Macintosh
Unity! WebGL! etc,etc…
Unity! WebGL! etc,etc…
今はやりの技術を使えばボクにも実現可能!
どうやら結論が出たようですね…
違います!
違います!
そういう正攻法じゃ満足できないんです…

今日集まってくれたみなさんも、もうちょっと
マゾいと信じています
OpenGL + OpenAL + C++
OpenGL + OpenAL + C++
• OpenGLは今やもっとも幅広く使える描画実装
    Windows/OSX/iOS/Linux/Android/ゲーム専用機…
•   OpenALもそれに近い
    Windows/OSX/iOS/Linux/Android…
• C++はCPUに近い言語なので実行速度的に有利
  (と思う)
• C、C++にもオープンソースの優秀なライブラ
  リが多数存在する
OpenGL + OpenAL + C++
• そして…OpenGLはMESAというソースの公開さ
  れた実装が存在する
予告内容
• 用意するもの~
• 実装のコツ~
• ハマった点

時間があれば…
• デバッグ手法
• 最適化
じゃあ始めましょうか!
• Windows
  VisualStudio2010
• OSX/iOS
  Xcode最新版
• Linux
  ターミナル

どの環境でもC++の標準ライブラリが使えます
じゃあ始めましょうか!
あと、こんだけ準備して。
•   glut
•   OpenAL
•   zlib
•   libpng
•   Freetype
•   FTGL
•   FTGLES
•   picojson
じゃあ始めましょうか!
•   Xcodeではデバッグビルド設定の「プリプロセッ
    サ」に「_DEBUG」を追加しておく
    #ifdef _DEBUG
    /* デバッグ時の実装 */
    #endif
•   iOSでのビルド設定「Compress PNG Files」はオ
    フっておいてね!ぜったいだよ!
コード書くよ。
•   ソースの文字コードはUTF-8 BOMあり
•   しかもVS2010では
    printf(“ほげほげ”); がcp932に勝手に変換され
    る(怒) ので #pragma
    execution_character_set(“utf-8”)
    で回避
•   プログラムからテキストデータを読み込む場合は
    UTF-8で用意
今回決めたたったひとつのルール
• ゲーム本体を.cppに書
  かないで全てヘッダフ
  ァイルに書く
• 僕はコード編集は専ら
  emacsなので問題な
  いです
• ファイルを追加しても
  プロジェクトを触らな
  くてイイ!
こんな感じになってます
#include “app.hpp“    // 芋づる式に18000行程度読み込まれる

App *app;

int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  app = new App(/* 引数いろいろ */);

    /* 初期化いろいろ */

    glutMainLoop();
}
表示のコールバック
void displayCallback()
{
  glClear(GL_COLOR_BUFFER_BIT);

    app->update();
    glutSwapBuffers();
}
入力のコールバック
void mouseCallBack(const int button, const int state, const
int x, const int y)
{
  app->mouse.input(/* 引数いろいろ */);
}

void keyPushCallback(const u_char key, const int x, const
int y)
{
  app->key.input(/*引数いろいろ */);
}
それがiOSだと?
#include "app.hpp"

App *app;

- (void)awakeFromNib
{
  /* いろいろ初期化 */
  app = new App(/*引数いろいろ */);
}

ちなみにソースのファイル名を
ViewController.mm
にしないとC++のコードを混在させられないので注意!
- (void)drawFrame
{
  [(EAGLView *)self.view setFramebuffer];

    glClear(GL_COLOR_BUFFER_BIT);
    app->update();

    [(EAGLView *)self.view presentFramebuffer];
}
タッチ入力はこうします
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent
*)event
{
  app->mouse.input(/* 引数いろいろ */);
}
OpenGL → OpenGL ES 1.1
#if (TARGET_OS_IPHONE)

typedef GLfloat GLdouble;

#define glFrustum(left, right, bottom, top, znear, zfar)
glFrustumf(left, right, bottom, top, znear, zfar)

#define glOrtho(left, right, bottom, top, znear, zfar)
glOrthof(left, right, bottom, top, znear, zfar)

#define glFogi(pname, param) glFogx(pname, param)

#endif
OpenGL → OpenGL ES 1.1
さすがに gluUnProject() とか gluProject()
とか… glu系は実装されてない…

MESAの出番!
ソースが公開されているのでそれを見ながら自
分で実装
いったんまとめ
• 文字コード問題はだいたい収束
• ゲーム本体を1つのクラスと見立て、その内
  部をだいたいどんなコンパイラでも通るよう
  にお上品に書く。
• そのクラスを操作する箇所で入力とかセーブ
  とか画面の回転とか…環境依存な処理を行え
  ばよい。
• ちょっとした工夫でOpenGLとOpenGL ESの差
  異は充分吸収できる
バラバラな解像度をひとつにまとめる
バラバラな解像度をひとつにまとめる
• Windows/OSX/Linuxでは、「縦横非固定モー
  ド」を実装
• 初期サイズを960x640と決め、それより狭
  かった場合には起動時に調整するみたいな。
バラバラな解像度をひとつにまとめる
• iOSは320x480~1536x2048と、多種多様な解
  像度が存在するので「見た目固定モード」を
  実装
バラバラな解像度をひとつにまとめる
• カメラは2つ用意
3D
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(width, -width, -height, height, nearZ, farZ);

2D
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-width / 2.0, width / 2.0, height / 2.0, -height /
2.0, -1.0, 1.0);
バラバラな解像度をひとつにまとめる
• iPhone3GSは2Dのスケーリングを調整して見
  た目をiPhone4とあわせる
• 新しいiPadも同じように調整すれば対応でき
  る
width *= 0.5;
height *= 0.5;
glOrtho(-width / 2.0, width / 2.0, height / 2.0, -height /
2.0, -1.0, 1.0);
結局UIView
iOSの画面回転でメニューの表示が崩れるのは、
UIViewみたく「画面端基準」を自力で実装して
切り抜けた
そろそろ音周りも聞きたい!
そろそろ音周りも聞きたい!
• OpenALのサンプル通りに書いたら問題ないで
  す
• 自分はwav形式のデータを自力で読み込み、
  OpenALに渡してます
• ストリーミング関連よくわかんない
可搬性≒テキストファイル
可搬性≒テキストファイル
• GEOSPOTのセーブデータはすべての動作環境
  で互換性がある
• バイナリデータだと、とにかく環境依存に
  なってしまう。たとえばlong型。OSXのx64は
  8バイト。それ以外だと4バイト。
• 人間が読み書きできるので修正も楽
可搬性≒テキストファイル
• XML…ちょっと自分には読み書きできない
• JSON…素晴らしい!
可搬性≒テキストファイル
リプレイデータもプレイ内容をテキストで書き
出しているだけ

-21.16 1333466611
0.017 0.156676 -0.807913 -0.247557 0.511317 1955.81 0 0 0 0
0.0503333 0.156676 -0.807913 -0.247557 0.511317 1955.81 0 0
0 0
0.0836667 0.156676 -0.807913 -0.247557 0.511317 1955.81 0 0
0 0
0.100667 0.156676 -0.807913 -0.247557 0.511317 1955.81 0 0 0
0
ファイルはどこから読み込むの?
ファイルはどこから読み込むの?
Windows/Linuxの場合
実行ファイルがある場所がカレント

std::string path = “”;

read(path + “devdata/hoge.png”);
ファイルはどこから読み込むの?
OSXの場合
起動時に場所を取得
CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef resourcesURL =
CFBundleCopyResourcesDirectoryURL(mainBundle);
char str[PATH_MAX];
CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8
*)str, PATH_MAX);
std::string path = std::string(str) + std::string(“/”);

read(path + “devdata/hoge.png”);
ファイルはどこから読み込むの?
iOSの場合
起動時に場所を取得
std::string path = [[[NSBundle mainBundle] resourcePath]
UTF8String] + std::string("/");

read(path + “devdata/hoge.png”);
ファイルに関してのまとめ
• プログラムの開始時、OS別に読み込み時のパ
  スと書き出し時のパスを取得しておく
• iOSも、C++の標準ライブラリ関数を使った
  ファイルの読み書きができる
  fstream >> value;
  こういうファイル読み込みでいける
まさかの:乱数でハマった
まさかの:乱数でハマった
GEOSPOTではクイズの出題順序をランダムに
シャッフルしているのだが…デモプレイが環境
によって違う!
どうも、Widnowsとその他でrand()の実装が違
うっぽい?
まさかの:乱数でハマった
これは std::tr1::random を使う事で解決!

ただし…初期化時の引数はこうしとく
unsigned long seed = time(0) & 0x7fffffff;

OSXのx64はlongが8バイト、その他は4バイトなの
が原因で初期値が変わってしまうのを防ぐ
タイマー処理
60FPSで動かない場合でも、ゲーム内の時間経過
を正確に行うためにタイマーを使って時間を計
測してます

Windows
•   timeGetTime() ←精度が悪い(T T)

それ以外
• gettimeofday() ←最高!
ここまでのまとめ
• ファイルパスや型のサイズなど、実行環境の
  違いを吸収する必要のあるもの、必要ないも
  のを切り分けておけば怖くない!
• 週一くらいの間隔でよいので他の環境でもビ
  ルドが通るかの確認は怠らない。
もしかして:時間切れ?

デバッグ手法と最適化に関してはまた次の機会
にでも~
いまその瞬間のCPU
いまその瞬間のCPU
• VisualStudioもXcodeもデバッガが非常によ
  くできているので困る事はない。
• でもやっぱりプログラムからコンソールに文
  字を出力するのは手軽で便利
いまその瞬間のCPU
C++だとこう書きます

cout << “ほげほげ” << endl;
いまその瞬間のCPU
だがしかしWindowsでは出力されないのであった…!
class DbgStreambuf : public std::streambuf {
   std::vector<char> str_;
public:
   int_type overflow(int_type c = EOF) {
     str_.push_back(c);
     return c;
   }
   int sync() {
     str_.push_back('0'); // 念のため
     OutputDebugString(&str_[0]);
     str_.clear();
     return 0;
   }
};
いまその瞬間のCPU
だがしかしWindowsでは出力されないのであった…!
Class Os {
   DbgStreambuf dbgStream_;
   std::streambuf *stream_;
public:
   Os() {
     stream_ = std::cout.rdbuf(&dbgStream_);
   }
   ~Os() {
     std::cout.rdbuf(stream_);
   }
};
いまその瞬間のCPU
だがしかしWindowsでは出力されないのであった…!
int main() {
  Os os;

    cout << “ほげほげ” << endl;

    return 0;
}
いまその瞬間のCPU
リリースビルドでcoutを一網打尽

#ifdef _DEBUG
DOUT std::cout
#else
DOUT 0 && std::cout
#endif

DOUT << “ほげぴよ” << endl;
最適化は機械任せ☆
最適化は機械任せ☆
XcodeのInstrumentsやOpenGL ES Performance
Devectiveで計測すれば処理負荷の大きい場所が
特定できる。
最適化は機械任せ☆
GEOSPOTではCPUのコードは3GSでも120FPS超えて
るので、まったく最適化作業を行ってないてへ
ぺろ。

OpenGL ESは分析結果に「VBOとPVRTCテクスチャ
を使えばいいよ」と出たのでそうしたら、だい
たい60FPSになったが…iPhone4が一番重い。
終わりです

この場を与えてくださった岸川さんを始めお手
伝いの方々、今日ここに来てくださった皆さん、
聞いてくださったすべての人たちに感謝!

More Related Content

どこでも動くゲームを作るためのベタープラクティス