Posts Tagged Programming

Quake IIIをVisual Studio 2008でビルド

id software(イド・ソフトウェア)、この会社は一人称視点のシューティングゲームをFirst Person Shooter通称FPSと言うジャンルとして広めた。
Wolfenstein 3Dを皮切りにDoomが欧米で大ヒット。
3D時代前夜にQuakeをリアルタイム3DCGでリリース。
少数精鋭主義を貫いたせいか、この前Bethesda Softworksかその親会社のZeniMaxに買収。
「洋ゲー=FPS」の様な状況を作り、今に至る。

で、id softwareは本当に素晴らしいソフト会社で、最新以外のQuakeシリーズのソースコードがGPLライセンスの元リリースされている。
例えばQuake 3はこれ。
ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip
他にもDoom / Doom 2やHexen / Heretic、当然Quake、Quake IIもリリースされているので、興味があったらググってみてもいいかもしれない。

そしてそして、お待たせしました。
本題。
このソース、落として解凍してVCにぶち込んでビルドすれば動くのか?と言う話。
答えはNO!
なので記事に・・・。
で、「luozhiyu – Compile Quake III arena」を参考にやってみましょう、と。
あ、先に書いておくけどQuake 3 Arenaを持ってないと動かないからね!
未確認ながら、Demo版があるから、それでも出来るかも?
Amazonでもほぼ売り切れのようなものだから、Steamで買うという手もある。

と言う前置きはさておき、はじめましょうか。
まず普通に解凍する。
解凍したディレクトリに「code」と言うディレクトリがあるはず。
とりあえずそこを開く。
で、「quake3.sln」を開く。


バックアップは作らなくても良いと思う。
だって、そもそもZipファイルにフルソースがあるし・・・。


これもとりあえず無視して「OK」。

これも消しちゃってOKのはず。

これで変換終了。

履歴を出すとこんな感じ。

ここからコンパイル。
でもいろいろ設定しなきゃいけない。


まず、ソリューションのプロパティでスタートアッププロジェクトを「quake3」にする。

で、ウィンドウ上部中央にあるソリューションの構成が「Debug Alpha」とかの場合は「Debug」に変更する。


「quake3」のプロジェクトのプロパティを開いて、「デバッグ」、「コマンド引数」を

+set fs_cdpath "C:\Program Files\Quake III Arena\" +set r_mode "4"

とかにする。
ここのオプションはQuake3のインストールディレクトリにある「baseq3」ディレクトリにある「q3config.cfg」の設定内容と同様と思われる。
ただ一点違うのはプログラムやらアセットやらへのパス。
それを「fs_cdpath “インストールパス”」としないとゲームが起動しない。
あと、もう一つのオプションは解像度のオプションで、今使っているマシンはこのゲームが発売されて軽く10年は過ぎたくらいの時に組んだから、当時としては考えられないような解像度(1920 x 1200)なので、どうしてもエラーが出た。。
製品版から起動してもエラーが出たので、起動時の解像度を抑えるようにしてみた。
自分の環境ではフルスクリーンで落ちるので、「q3config.cfg」の

seta r_fullscreen "1"

seta r_fullscreen "0"

として抑えた。

ちなみに、ビルドして実行してみるとこんなエラーが出る。

これが出なくてオープニングムービーが始まってCDキーを入力する画面が出たらもう問題ないでしょう。

id softwareらしい無骨なタイトル画面w






こんな感じ。

お粗末。

Post to Twitter

, , , , , , , , ,

No Comments

メモリのアラインメントを確認する

そもそもの話。
なぜ「ゲームプログラマになる前に覚えておきたい技術(以下「セガ本」)」を読んでる途中で「C言語ポインタ完全制覇(以下「ポインタ本」)」を読むことになったか?
それは、セガ本にデータ型のサイズで割りきれるアドレスでデータを読み込まないと、x86系(いわゆるIntel、AMD系)以外のCPUではクラッシュし、x86でもデータを2回読むことになってパフォーマンスが落ちる、と書いてあったから。
で、パッと見て理解できないでいたら、時を同じくしてポインタ本が届き、目次にアラインメントの記述が。
と言うわけで、ポインタ本を読み進めてみた次第。

そこで思ったのが、この本、本当に為になる。
俺みたいなCの見習いの肩もみの家の便所掃除みたいな人間にはいろいろ発見が多すぎて驚く。
例えば

#include <stdio.h>

int main()
{
    int i;
    char s[] = "abcdefghij";

    for ( i = 0; i < sizeof( s ) - 1; ++i ) {
        printf( "%d:%c\n", i, i[s] );
    }
}

なんてコード。
実行するとこう。

McLaren% gcc -o pointer_test pointer_test.c
McLaren% ./pointer_test
0:a
1:b
2:c
3:d
4:e
5:f
6:g
7:h
8:i
9:j

9行目のi[s]はs[i]の間違いと思いがちだけど、”これでも”問題ない。
出来るか出来ないかの話であって、常識的にこの書き方をして良いかは別の話(そしてもちろんダメ)。
この表記はコンパイル時に

printf( "%d:%c\n", i, *( i + s ) );

と変換しているだけ。
確かにポインタ+数値でそのポインタの型の分だけ先へ進む機能がある。
とまぁ、なかなか興味深い。

さてさて本題。
ようやくアラインメントが書いてあるページまでたどり着いた。
どうやら現代のコンパイラはかなりその辺りも配慮しているらしく、次に置かれるデータがそのデータ型で割り切れない場合、”詰め物”をしてくれるんだとか。
実際コードにして試してみまふ。

// alignment_check.c
#include <stdio.h>

typedef struct {
    char ch;
    double dn;
    short sn;
    int nu;
} MyStruct;

int main()
{
    MyStruct hoge;
    unsigned long gap1, gap2, gap3;

    printf( "sizeof char\t%lu\n", sizeof( char ) );
    printf( "sizeof double\t%lu\n", sizeof( double ) );
    printf( "sizeof short\t%lu\n", sizeof( short ) );
    printf( "sizeof int\t%lu\n\n", sizeof( int ) );

    printf( "sizeof MyStruct\t%lu\n\n", sizeof( MyStruct ) );

    printf( "\t&hoge's hexadecimal memory address.\n" );
    printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
    printf( "\t&hoge\t\tMyStruct\t%p\t%4lu\n", &hoge, sizeof( hoge ) );

    printf( "\t&hoge.ch\tchar\t\t%p\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
    gap1 = (unsigned long)&hoge.dn -
        ( (unsigned long)&hoge.ch + sizeof( hoge.ch ) );
    printf( "gap1:%lu\n", gap1 );

    printf( "\t&hoge.dn\tdouble\t\t%p\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
    gap2 = (unsigned long)&hoge.sn -
        ( (unsigned long)&hoge.dn + sizeof( hoge.dn ) );
    printf( "gap2:%lu\n", gap2 );

    printf( "\t&hoge.sn\tshort\t\t%p\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
    gap3 = (unsigned long)&hoge.nu -
        ( (unsigned long)&hoge.sn + sizeof( hoge.sn ) );
    printf( "gap3:%lu\n", gap3 );

    printf( "\t&hoge.nu\tint\t\t%p\t%4lu\n\n", &hoge.nu, sizeof( hoge.nu ) );

    printf( "char + gap1 + double + gap2 + short + gap3 + int = %lu\n\n",
            sizeof( char ) + gap1 +
            sizeof( double ) + gap2 +
            sizeof( short ) + gap3 +
            sizeof( int ) );

    printf( "&hoge's decimal memory address (with alighnment gap size).\n" );
    printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
    printf( "\t&hoge\t\tMyStruct\t%ll\t%4lu\n", &hoge, sizeof( hoge ) );

    printf( "\t&hoge.ch\tchar\t\t%lu\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
    printf( "gap1:%lu\n", gap1 );

    printf( "\t&hoge.dn\tdouble\t\t%lu\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
    printf( "gap2:%lu\n", gap2 );

    printf( "\t&hoge.sn\tshort\t\t%lu\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
    printf( "gap3:%lu\n", gap3 );

    printf( "\t&hoge.nu\tint\t\t%lu\t%4lu\n", &hoge.nu, sizeof( hoge.nu ) );

    return 0;
}

まぁ、こんだけ親切に情報盛ったから、アドレス表記だけで良いとは思ったけど、一応アドレスを10進数にしたやつも記載。
(つっても、64ビット環境においてunsigned long程度でアドレス表示して間に合わない気がするけど、ギャップを目視することが目的だから、あえてこのまま。
あれ?ラップトップのMacでgcc動かしたときって64ビット扱い?
・・・ま、今回は気にしないことに。
どうせテストだし。。。
まともに64ビットアドレス書いてたら、京単位らしいから・・・ね。)

で、結果。

McLaren% gcc -o alignment_check alignment_check.c
alignment_check.c: In function ‘main’:
alignment_check.c:48: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘struct MyStruct *’
alignment_check.c:49: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘char *’
alignment_check.c:52: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘double *’
alignment_check.c:55: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘short int *’
alignment_check.c:58: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘int *’
McLaren% ./alignment_check
sizeof char     1
sizeof double   8
sizeof short    2
sizeof int      4

sizeof MyStruct 24

        &hoge's hexadecimal memory address.
        name            type            address         size
        &hoge           MyStruct        0x7fff5fbfef10    24
        &hoge.ch        char            0x7fff5fbfef10     1
gap1:7
        &hoge.dn        double          0x7fff5fbfef18     8
gap2:0
        &hoge.sn        short           0x7fff5fbfef20     2
gap3:2
        &hoge.nu        int             0x7fff5fbfef24     4

char + gap1 + double + gap2 + short + gap3 + int = 24

&hoge's decimal memory address (with alighnment gap size).
        name            type            address         size
        &hoge           MyStruct        140734799802128   24
        &hoge.ch        char            140734799802128    1
gap1:7
        &hoge.dn        double          140734799802136    8
gap2:0
        &hoge.sn        short           140734799802144    2
gap3:2
        &hoge.nu        int             140734799802148    4

こんな塩梅。
ワーニングは全部アドレスを無理矢理10進数で出したため。
「ポインタ本」と同じように構造体で確認した。
こうすれば普通の処理系では間に別の変数を入れたりする余地はあるまい:-)
で、確かにアドレスを次のメンバ変数のサイズで割り切れない場合、ギャップが出ている。
なるほど、こうすればどのCPUでもスマートに動くというわけですかい。
勉強になった。

C言語ポインタ完全制覇 (標準プログラマーズライブラリ)

著者/訳者:前橋 和弥

出版社:技術評論社( 2001-01 )

定価:¥ 2,394

Amazon価格:¥ 2,394

単行本 ( 323 ページ )

ISBN-10 : 4774111422

ISBN-13 : 9784774111421



ゲームプログラマになる前に覚えておきたい技術

著者/訳者:平山 尚(株式会社セガ)

出版社:秀和システム( 2008-11-15 )

定価:¥ 4,725

Amazon価格:¥ 4,725

単行本 ( 872 ページ )

ISBN-10 : 4798021180

ISBN-13 : 9784798021188


Post to Twitter

, , , ,

No Comments

スタックオーバーフローをテスト

最近、「C言語ポインタ完全制覇 (標準プログラマーズライブラリ)」という本を読んでる。
タイトルの通り、C言語の、それもポインタだけに的を絞った本。
これがなかなかどうして面白くて、文字も大きいのですらすら進んでしまう。
ただ、簡単かと言われれば全く違って、興味深くて考えさせられるような話題ばかり。

ようやく100ページまで進んだ。
そしたらセキュリティーホールの温床になると言うスタックオーバーフローをテストしようという楽しいコードが書いてあった。
で、試したくなるじゃないですか。
若干情報多めに書いて写経してみたのが下記。

// stack_overflow_test.c
#include <stdio.h>

void hello()
{
    static int i = 0;

    fprintf( stderr, "stderr:\tHello xD\n" );
    printf( "\tNo.%d Hello:P\n", i++ );
}

void func()
{
    void        *buf[10];
    static int  i;

    printf( "buf\t%p\n*buf\t%p\n&i\t%p\n&hello\t%p\n\n", buf, *buf, &i, &hello );

    for ( i = 0; i < 16; ++i ) {
        buf[i] = hello;
        printf( "buf[%d]\t%p\n", i, &buf[i] );
    }

    printf( "\n\tEnd of func()\n\n" );
}

int main()
{
    int buf[1000];

    printf( "Start\n\n" );
    func();
    printf( "End\n\n" );

    return 0;
}

で、実行するとこう。

% gcc -o stack_overflow_test stack_overflow_test.c
% ./stack_overflow_test
Start

buf     0x7fff5fbfdf20
*buf    0x100000eee
&i      0x10000108c
&hello  0x100000d1c

buf[0]  0x7fff5fbfdf20
buf[1]  0x7fff5fbfdf28
buf[2]  0x7fff5fbfdf30
buf[3]  0x7fff5fbfdf38
buf[4]  0x7fff5fbfdf40
buf[5]  0x7fff5fbfdf48
buf[6]  0x7fff5fbfdf50
buf[7]  0x7fff5fbfdf58
buf[8]  0x7fff5fbfdf60
buf[9]  0x7fff5fbfdf68
buf[10] 0x7fff5fbfdf70
buf[11] 0x7fff5fbfdf78
buf[12] 0x7fff5fbfdf80
buf[13] 0x7fff5fbfdf88
buf[14] 0x7fff5fbfdf90
buf[15] 0x7fff5fbfdf98

        End of func()

zsh: segmentation fault  ./stack_overflow_test

あれ~?
hello()が実行されてない・・・??
と言うわけで、落ちる前に一度hello()を呼んでみた。

#include <stdio.h>

void hello()
{
    static int i = 0;

    fprintf( stderr, "stderr:\tHello xD\n" );
    printf( "\tNo.%d Hello:P\n", i++ );
}

void func()
{
    void        *buf[10];
    static int  i;

    printf( "buf\t%p\n*buf\t%p\n&i\t%p\n&hello\t%p\n\n", buf, *buf, &i, &hello );
    hello();

    for ( i = 0; i < 16; ++i ) {
        buf[i] = hello;
        printf( "buf[%d]\t%p\n", i, &buf[i] );
    }

    printf( "\n\tEnd of func()\n\n" );
}

int main()
{
    int buf[1000];

    printf( "Start\n\n" );
    func();
    printf( "End\n\n" );

    return 0;
}

するとこうなる。

% gcc -o stack_overflow_test stack_overflow_test.c
Start

buf     0x7fff5fbfdf20
*buf    0x100000eee
&i      0x10000108c
&hello  0x100000d10

stderr: Hello xD
        No.0 Hello:P
buf[0]  0x7fff5fbfdf20
buf[1]  0x7fff5fbfdf28
buf[2]  0x7fff5fbfdf30
buf[3]  0x7fff5fbfdf38
buf[4]  0x7fff5fbfdf40
buf[5]  0x7fff5fbfdf48
buf[6]  0x7fff5fbfdf50
buf[7]  0x7fff5fbfdf58
buf[8]  0x7fff5fbfdf60
buf[9]  0x7fff5fbfdf68
buf[10] 0x7fff5fbfdf70
buf[11] 0x7fff5fbfdf78
buf[12] 0x7fff5fbfdf80
buf[13] 0x7fff5fbfdf88
buf[14] 0x7fff5fbfdf90
buf[15] 0x7fff5fbfdf98

        End of func()

stderr: Hello xD
        No.1 Hello:P
stderr: Hello xD
        No.2 Hello:P
stderr: Hello xD
        No.3 Hello:P
stderr: Hello xD
        No.4 Hello:P
stderr: Hello xD
        No.5 Hello:P
zsh: segmentation fault  ./stack_overflow_test

これでしっかり動いた。
5回勝手(不正)に実行されちょる。

本の説明によるとメモリの状態は下記のような感じとなるらしい。
–ここ移行に別の変数が追加されていく–
buf[0] 0x7fff5fbfdf20
buf[1] 0x7fff5fbfdf28
buf[2] 0x7fff5fbfdf30
buf[3] 0x7fff5fbfdf38
buf[4] 0x7fff5fbfdf40
buf[5] 0x7fff5fbfdf48
buf[6] 0x7fff5fbfdf50
buf[7] 0x7fff5fbfdf58
buf[8] 0x7fff5fbfdf60
buf[9] 0x7fff5fbfdf68
==他の自動変数==
==関数終了時に戻るアドレス情報など==
==その他の領域==

考察:buf[10]~buf[15]までの6個分余分に配列があると仮定して上がいてます、と(・・アレ?5回しか実行されてない??)。
自動変数を食い破り、呼び出し元の関数情報を破壊して行きます、と。
あ、わかった。。。
funcに自動変数iがあるから1回少ないんだ。
funcをこう変えた。

void func()
{
    void        *buf[10];
    static int  i;
    int a, b, c, d, e; // 追加

    printf( "buf\t%p\n*buf\t%p\n&i\t%p\n&hello\t%p\n\n", buf, *buf, &i, &hello );
    hello();

    for ( i = 0; i < 15; ++i ) {
        buf[i] = hello;
        printf( "buf[%d]\t%p\n", i, &buf[i] );
    }

    printf( "\n\tEnd of func()\n\n" );
}

結果はこう。
(ちなみに、ちょっとプログラムいじっちゃったからアドレスと表記が若干違う。。)

Start

&func   0x100000d3f
buf     0x7fff5fbfeea0
*buf    0xffffffffffffffff
&i      0x10000108c
&hello  0x100000cf8

stderr: Hello xD
        No.0 Hello:P
buf[0]  0x7fff5fbfeea0
buf[1]  0x7fff5fbfeea8
buf[2]  0x7fff5fbfeeb0
buf[3]  0x7fff5fbfeeb8
buf[4]  0x7fff5fbfeec0
buf[5]  0x7fff5fbfeec8
buf[6]  0x7fff5fbfeed0
buf[7]  0x7fff5fbfeed8
buf[8]  0x7fff5fbfeee0
buf[9]  0x7fff5fbfeee8
buf[10] 0x7fff5fbfeef0
buf[11] 0x7fff5fbfeef8
buf[12] 0x7fff5fbfef00
buf[13] 0x7fff5fbfef08
buf[14] 0x7fff5fbfef10

        End of func()

End

zsh: segmentation fault  ./stack_overflow_test

ほら、実行されない。
ただ不思議なことに、

int a;

だけにした場合と、

int a, b, c, d;

までにした場合、

Start

&func   0x100000d3f
buf     0x7fff5fbfeeb0
*buf    0x7365745f776f6c66
&i      0x10000108c
&hello  0x100000cf8

stderr: Hello xD
        No.0 Hello:P
buf[0]  0x7fff5fbfeeb0
buf[1]  0x7fff5fbfeeb8
buf[2]  0x7fff5fbfeec0
buf[3]  0x7fff5fbfeec8
buf[4]  0x7fff5fbfeed0
buf[5]  0x7fff5fbfeed8
buf[6]  0x7fff5fbfeee0
buf[7]  0x7fff5fbfeee8
buf[8]  0x7fff5fbfeef0
buf[9]  0x7fff5fbfeef8
buf[10] 0x7fff5fbfef00
buf[11] 0x7fff5fbfef08
buf[12] 0x7fff5fbfef10
buf[13] 0x7fff5fbfef18
buf[14] 0x7fff5fbfef20

        End of func()

stderr: Hello xD
        No.1 Hello:P
stderr: Hello xD
        No.2 Hello:P

となる。
エラーは起きない。
けど変。

結論:戻り先アドレスまで上がいたら、そこにある情報を実行しちゃいました、的な感じの認識。

まぁ、何にせよ、バッファが溢れないように注意しないといつか痛い目見ますよ、と。
精進します。

C言語ポインタ完全制覇 (標準プログラマーズライブラリ)

著者/訳者:前橋 和弥

出版社:技術評論社( 2001-01 )

定価:¥ 2,394

Amazon価格:¥ 2,394

単行本 ( 323 ページ )

ISBN-10 : 4774111422

ISBN-13 : 9784774111421


Post to Twitter

, , , ,

No Comments

c++でのファイル読み込みテスト

ゲームプログラマになる前に覚えておきたい技術」を読んでいて、C++のファイル読み込みをやっていた。
Cの時でさえ、そんなにファイルいじったりしていなかったので、ちょっと自分でも書いてみる。
まぁ、写経的なコードになっちゃうんだけれども。

main.cpp

#include <iostream>

using namespace std;

bool readFile( char** buff, int* size, const char* filename );

int main()
{
	char *fBuff = 0;
	int fSize = 0;
	if ( readFile( &fBuff, &fSize, "test_dat.txt" ) ) {
		cout.write( fBuff, fSize );
	}

	delete[] fBuff;
	return 0;
}

fileRead.cpp

#include <fstream>

using namespace std;

bool readFile( char** buff, int* size, const char* filename )
{
	// 初期化
	*buff = 0;
	*size = 0;

	// ファイルストリームをインスタンス化しつつストリームオープン
	ifstream ifs( filename, ios::binary );

	// 失敗
	if ( !ifs ) { return false; }

	ifs.seekg( 0, ifstream::end ); // ファイルストリームを最後まで移動

	// 現在の読み込み位置(詰まるところサイズ)を取得
	// tellg()はpos_type型を返すので、実体はintでも一応キャスト
	*size = static_cast< int >( ifs.tellg() );

	ifs.seekg( 0, ifstream::beg );	// ファイルストリームを先頭へ

	// メモリ確保
	*buff = new char [ *size ];

	// ファイルの内容をメモリに読み込む
	ifs.read( *buff, *size );

	return true;
}

test_dat.txt

Hello.
And say goodbye.

で、結果。

% g++ -o test main.cpp fileRead.cpp
% ./test
Hello.
And say goodbye.

ちなみに、

delete[] *fBuff;

とても、

delete[] &fBuff;

としても怒られた。
ダブルポインタは大本の変数名だけ指定すれば良いんですかい?
メモリリークとかしないのか?
この前紹介した「OpenGLで作るiPhone SDKゲームプログラミング」では

ParticleSystem::~ParticleSystem()
{
	int i;

	for (i = 0; i < this->amount; ++i) {
		delete this->particle[i];
	}
	delete this->particle;
}

なんてコードがあったから、面倒くさいけど個別に解放しないとリークしちゃうのかと思った。
そうか、その配列内でさらにnewしているから個別に解放しているのか。
そうじゃない場合はそのまま解放して良い、と。
ポインタをもっとよく勉強せねば。。。

そんだけ。

Post to Twitter

, , ,

2 Comments

「OpenGLで作るiPhone SDKゲームプログラミング」が面白かった

OpenGLで作るiPhone SDKゲームプログラミング

著者/訳者:横江 宗太(株式会社パンカク)

出版社:インプレスジャパン( 2009-12-18 )

定価:¥ 2,940

Amazon価格:¥ 2,940

単行本 ( 352 ページ )

ISBN-10 : 4844328085

ISBN-13 : 9784844328087


一言で言うと、この本がとても面白かった。
内容は非常にストレートで、「iPhone向けに簡単なレースゲームを作る」という趣旨の本。
1章がOpenGL ES 1.0を使った2Dの取り扱い。画面への描画。
2章がその応用で「はえたたきゲーム」を作る。

はえたたきゲーム


3章が”パーティクルシステム”と呼ばれる煙などの表現に使われる演出の実装。

パーティクルシステム


4章が「2Dレースゲーム」を作る。

2Dレースゲーム


5章が”衝突判定”の実装。

衝突判定の実装と画面調整


6章が全ての章を応用して「3Dレースゲーム」を作る。

3Dレースゲーム

まず、Objective-CとC++の知識が最低限求められる。
あと、最終章の3Dレースゲームと言ってもそれほどのものを期待してはいけない。
ただ、ゲームの骨組みを組む方法や、画面描画と操作の連携などを学ぶことが出来る。
俺みたいな脳たりんには丁度良い内容となった。

基本的なゲームロジックはC++で実装されているため、そのままではつまらないのでObjective-Cで実装することにした。
これがC++のコードとの対比が出来てなかなか面白かった。
実際問題、C++を前提に書いてあるので付け焼き刃な俺のObjective-C知識ではなかなか無駄な処理が出まくっているのはわかっているのだけれども、それでもやって良かったと思う。
書店で見かけたら、ちょっと目を通してみるのもいいかもしれない。
これだけでゲームは作れないけど、本当に良いきっかけを作ってくれると思う。

ちなみに、上記のゲーム画像はアセットこそお借りしたものの、ソースコードは写経+Objective-Cで書き直したものをiPhone 3G上で実際に動かしたもののスクリーンショット。
一応この程度のものは出来る。

また、Windows用になってしまうけれど、この本の冒頭に書いてある参考文献である「ゲームプログラマになる前に覚えておきたい技術」をこの本の次に読むといい気がしてきた。
実は「ゲームプログラマになる前に~」も持ってはいるものの、なかなか読み進められないでいた。
けど、今回の「OpenGLで作る~」を読んでからなんとなく進められるようになってきた。
基本的に本に書かれているコードは執筆時のいずれかの段階のコードなので、実は動かないものも多いけど、ソースコードに当たれば問題ないレベル。
徐々にステップアップして行くには良いかなぁ、と自分に言い聞かせつつ読み進めてまふ。
参考までに。

ゲームプログラマになる前に覚えておきたい技術

著者/訳者:平山 尚(株式会社セガ)

出版社:秀和システム( 2008-11-15 )

定価:¥ 4,725

Amazon価格:¥ 4,725

単行本 ( 872 ページ )

ISBN-10 : 4798021180

ISBN-13 : 9784798021188


そんだけ。

Post to Twitter

, , , , ,

No Comments

Objective-Cのお勉強 #3 セレクタ

iPhone SDK アプリケーション開発ガイドを読んでいた昨今、ど〜しても理解しがたかったのが、メソッド宣言してなくて、しかもそのメソッドが書かれている箇所より上のメソッドより上から呼び出されていて、それがすんなり通る。
でも、その呼び方が不自然で、

[self hoge];

などじゃなく、

        hogeButton = [[UIBarButtonItem alloc] initWithTitle:@"Hoge"
                                                      style:UIBarButtonItemStylePlain
                                                     target:appDelegate
                                                     action:@selector(hoge)];

と言う書き方。
ちなみに、これはアプリケーション開発ガイドのPageDemoと言うプログラムで見ることが出来る。

で、@selectorと言うのがどうにも怪しいので詳解 Objective-C 2.0を手に取る。
で、こんなメソッドが用意されているらしい。

// 引数無し実行
- (id)performSelector:(SEL)aSelector;
// 引数あり実行
- (id)performSelector:(SEL)aSelector withObject:(id)anObject;

こんな感じで呼べるらしい。

内容読んだら関数ポインタをダイナミックにしたような物、と言う雰囲気。
実感湧かないから書いてみることに。
hoge.h

// hoge.h
#import <Foundation/NSObject.h>

@interface Hoge : NSObject
- (void) foo;
@end

@interface Moge : NSObject
@end

hoge.m

// hoge.m
#import "hoge.h"
#import <stdio.h>

@implementation Hoge

- (void) foo {
    printf( "Message from Hoge's foo.\n" );

    // このコードは動かないから、コメントアウト。
    //[self bar];

    // このコードは動く
    [self performSelector:@selector(bar)];

    // モゲが動く
    id moge = [[Moge alloc] init];
    [moge performSelector:@selector(bar)];
    [moge dealloc];
    // ホゲが動く
    moge = self;
    [moge performSelector:@selector(bar)];
}

- (void) bar {
    printf("Message from Hoge's bar.\n");
}
@end

@implementation Moge
- (void) bar {
    printf("Message from Moge's bar.\n");
}
@end

main.m

main.m
#import "hoge.h"

int main()
{
    // idと言う型の変数を定義します
    id obj;

    // クラスのインスタンス化
    obj = [[Hoge alloc] init];

    // メソッド呼び出し
    [obj foo];

    [obj dealloc];

    return 0;
}

実行結果

McLaren% gcc -o hoge_test main_hoge.m hoge.m -framework Foundation
McLaren% ./hoge_test
Message from Hoge's foo.
Message from Hoge's bar.
Message from Moge's bar.
Message from Hoge's bar.

なるほどね。。
俺ほどの実力になると、使い時さえ思いつかないと言う有様。
精進します。。。

そんだけ。

Post to Twitter

,

No Comments

iPhone プログラミングノート #1

さてさて、iPhone SDK アプリケーション開発ガイドをパラパラ見ること3章、おぼろげながらiPhoneのプログラミング方法がわかって来たのでちょいと備忘録でも。

で、とりあえずテキトーにiPhoneのプロジェクトを作る。
とりあえずWindow-based Applicationとかで。
恐らく下記のようなファイルが出来るはず。
Classes
・プロジェクト名AppDelegate.h
・プロジェクト名AppDelegate.m
Other Resources
・main.m
・プロジェクト名-Info.plist

あと、その他諸々ついてくるけど、とりあえず気にしないことに。
ただこのままだと○○.xibというファイルを読み込んでしまうので、それを防がなくてはならない。
とりあえずInfo.plistを開いて「Main nib file base name」の項目自体を削除、と。

XML形式で開いてしまったら、当該箇所をやはり削除。
例えば下記のような2行があったら削除。

	<key>NSMainNibFile</key>
	<string>MainView.nib</string>

ちなみに、プロジェクト名はHelloWorld・・・と言うことで。

で、エントリーポイント(プログラム実行時、最初に呼び出される関数)はC/C++と同様main。
で、Windowsのように自動でプログラムのループを司るWinMainのような仕組みは、mainに書くことになっているみたい。
ひな形として与えられるのは下記の通り。

#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {

	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	int retVal = UIApplicationMain(argc, argv, nil, nil);
	[pool release];
	return retVal;
}

なんか、argcとargvをUIApplicationMainに突っ込んでる所からして、ここがプログラムループ、らしい。。
ここの第4引数を下記のように修正。

	int retVal = UIApplicationMain(argc, argv, nil, @"HelloWorldAppDelegate");

ちなみに、.mとか.hとか付けていないのはわざとなので付けないこと。

お次はHelloWorldAppDelegate.hと同.mを編集。

// HelloWorldAppDelegate.h
#import <UIKit/UIKit.h>

// メッセージ表示用にUITextViewと言うビューを継承し、カスタムビューを作成
@interface CustomView : UITextView
{
	// カスタムビュー用のメンバ変数
	UITextView *myTextView;
}

@end

@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> {
	UIWindow *window;
	CustomView *mainView;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end
// HelloWorldAppDelegate.m
#import "HelloWorldAppDelegate.h"

@implementation CustomView

- (id)initWithFrame:(CGRect)frame {
	// 親クラスの親メソッド呼び出し
	self = [super initWithFrame:frame];

	// 親メソッド取得成功時のみ実行
	if (self != nil) {
		// テキストビューを初期化
		myTextView = [[UITextView alloc] initWithFrame:frame];
		// テキストビューへテキスト追加
		myTextView.text = @"おはこんばんちは";
		// テキストビューをこのカスタムビューに適用?
		[self addSubview:myTextView];
	}
	return self;
}

- (void)dealloc {
	// メモリ解放
	[myTextView release];
	[super dealloc];
}
@end

@implementation HelloWorldAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
	// バウンズ(画面領域)を取得
	CGRect bounds = [[UIScreen mainScreen] applicationFrame];

	// ウィンドウの範囲を画面サイズギリギリまで占有
	window = [[UIWindow alloc] initWithFrame:bounds];
	// ビューのサイズをステータスバーを除いて占有
	mainView = [[CustomView alloc] initWithFrame:CGRectMake(0, 0, bounds.size.width, bounds.size.height)];

	// ウィンドウへビューを設定
	[window addSubview:mainView];
	// ビューを表示
	[window makeKeyAndVisible];
}

- (void)dealloc {
	[mainView release];
	[window release];
	[super dealloc];
}
@end

これをコンパイルするとこんな感じ。

ふむふむ、何となくわかったような、わからないような。
実際はこの次のViewControllerまでやってるんだけど、復習はここまで。
本のコードとちょっと違うのはやはりわざと。
行儀が良いのか悪いのかは知らんけど、これでも動く。

そんだけ。

Post to Twitter

, , , , ,

No Comments

iPhone SDK アプリケーション開発ガイド

いろいろObjective-C 2.0で迷い、Erica Sadunの「iPhone デベロッパーズ クックブック」を試したりもした(ただし俺が持ってるのは英語版)。
これがなかなかどうして、敷居が高くて困っていた。

素直にO’Reillyの、しかも日本語版の書籍を手に取っておけば良かった・・・。
と言うわけで、先週買いに行ったのがこれ。
iPhone SDK アプリケーション開発ガイド

この本はかなりかゆいところに手が届くと言うか、Erica Sadunと同様iPhone入門書の中で珍しいInterface Builderを使わないiPhoneプログラミングで進んで行く。
そのため、ツール嫌いのコード人間で、俺みたいな脳足りんにはちょうど良い。
敷居はCとオブジェクト指向の知識、あとiPhoneとかiPod Touchを使っていればおおむね理解できる。
今3章までパチパチ打ったり修正したりしてるけど、これがなかなかどうして面白い。
Cookbookではどうにもコードが断片的で、コードの意図するところはとりあえず書いてあるものの、それ以外の変更点その他は女史のサイトからDLして自分で読み解かないといけない。
それに対して、図解が乏しい本書は逆にコード量とその前のコード説明がしっかりとなされている感じ。
そのため、読んでいていろいろ考えさせる。
本来学ぶ上で気にしなくていいようなどうでも良い事柄にはまることが無いため、読んでいて素直に学習できる。

そんなわけで、この本を元にチョイチョイメモを残そうと思った。
ちなみに、この本の作者であるJonathan Zdziarskiは前著「iPhone Open Application Development」でJailbreak前提のオープンソース開発を披露していた偉大なるハッカー。
そのためか、iPhone SDKには隠された機能が多いとして前書きで不満をこぼしているw
この本のサンプルコードに関してはほぼパブリックドメインの形を取っていて、似るなり、焼くなり、売るなり、改変して公開するなり、非公開するなり自由とのこと。
(ただし、サンプルコードの多くの部分を使う場合はO’Reillyとの協議が必要とのこと。)

とりあえず良い本とだけご紹介。

Post to Twitter

, , ,

No Comments

WindowsでBitBltなるものを勉強

最近ちょっと落ち着いてきたので、「プログラミングWindows」を読み進めていたんだけど、どーにもつまらない。
俺がやりたいのは、ドット単位での画面描画なのに、ずーっとベクター系。
で、終わったと思えば「キーボード」の章。
ん~・・・。

やりたいことは結構単純で、リアルタイム3DCGの本があるからその公式やらを元にウィンドウ内に書き込みたいなぁ、と。
だから、勉強方法を変えてみることにした。
ウィンドウ描いてその中をラスタライズして2Dにしたリアルタイム3Dを表示したいだけ。
思い切ってページをすっ飛ばして、画面のビットマップ情報をいじることに。
その手始めとして「BitBlt(Bit-block transfer:ビットブリット)」というのをやってみた。

#include <windows.h>

LRESULT CALLBACK MyWindow(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance, LPSTR lpCmdLine,int iCmdShow)
{
	static	TCHAR	szAppName[] =
		TEXT("おいらのアプリ");	//アプリ名?
	HWND			hwnd;	//ウィンドウハンドル
	MSG				msg;	//メッセージ
	WNDCLASS		wndCls;	//ウィンドウクラス構造体

	//ウィンドウスタイルを指定
	wndCls.style			= CS_HREDRAW | CS_VREDRAW;
	//ウィンドウプロシージャを指定
	wndCls.lpfnWndProc		= MyWindow;
	wndCls.cbClsExtra		= 0;
	wndCls.cbWndExtra		= 0;
	//インスタンスハンドル変数を指定
	wndCls.hInstance		= hInstance;
	//ウィンドウのアイコンを指定
	wndCls.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	//カーソルの形を指定
	wndCls.hCursor			= LoadCursor(NULL, IDC_CROSS);
	//バックグラウンド色を指定
	wndCls.hbrBackground	= (HBRUSH) GetStockObject(WHITE_BRUSH);
	wndCls.lpszMenuName		= NULL;
	wndCls.lpszClassName	= szAppName;

	if (!RegisterClass(&wndCls)) {
		MessageBox(NULL,
			TEXT("このプログラムはWindowsNT以降対応です。"),
			TEXT("そーりー"), MB_ICONERROR);
		return 0;
	}

	hwnd = CreateWindow(szAppName, TEXT("どうよ? - Bitmap Viewer"),
						WS_OVERLAPPEDWINDOW,
						CW_USEDEFAULT, CW_USEDEFAULT,
						CW_USEDEFAULT, CW_USEDEFAULT,
						NULL, NULL, hInstance, NULL);

	//ウィンドウ表示状態を指定
	ShowWindow(hwnd, iCmdShow);
	//ウィンドウ更新実行
	UpdateWindow(hwnd);

	while (GetMessage(&msg, NULL, 0, 0)) { //メッセージループ
		TranslateMessage(&msg);	//命令変換?
		DispatchMessage(&msg);	//命令転送
	}

	return msg.wParam;
}

LRESULT CALLBACK MyWindow(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int	cxClient, cyClient, cxSource, cySource;
	HDC			hdcClient, hdcWindow;
	PAINTSTRUCT ps;
	int			x, y;
	TCHAR		buff[512];

	switch (message) {
		case WM_CREATE:
			cxSource = GetSystemMetrics (SM_CXSIZEFRAME) +
					GetSystemMetrics (SM_CXSIZE);

			cySource = GetSystemMetrics (SM_CYSIZEFRAME) +
					GetSystemMetrics (SM_CYCAPTION);

			wsprintf(buff,
				TEXT("GetSystemMetrics (SM_CXSIZEFRAME):%d\n")
				TEXT("GetSystemMetrics (SM_CXSIZE):%d\n")
				TEXT("GetSystemMetrics (SM_CYSIZEFRAME):%d\n")
				TEXT("GetSystemMetrics (SM_CYCAPTION):%d"),
				GetSystemMetrics (SM_CXSIZEFRAME),
				GetSystemMetrics (SM_CXSIZE),
				GetSystemMetrics (SM_CYSIZEFRAME),
				GetSystemMetrics (SM_CYCAPTION));
			MessageBox(NULL, buff, TEXT("情報"), MB_OK);
			return 0;

		case WM_SIZE:
			cxClient = LOWORD (lParam);
			cyClient = HIWORD (lParam);
			return 0;

		case WM_PAINT:
			hdcClient = BeginPaint (hwnd, &ps);
			hdcWindow = GetWindowDC (hwnd);

			for (y = 0; y < cyClient; y += cySource)
				for (x = 0; x < cxClient; x += cxSource) {
					BitBlt (hdcClient, x, y, cxSource, cySource,
							hdcWindow, 0, 0, SRCCOPY);
				}

			ReleaseDC (hwnd, hdcWindow);
			EndPaint (hwnd, &ps);
			return 0;

		case WM_DESTROY:
			PostQuitMessage (0);
			return 0;
	}

	return DefWindowProc (hwnd, message, wParam, lParam);
}

ほぼほぼ「プログラミングWindows」上のソースの写経。
何がやりたいかというと、タイトルバーの左端にあるアイコンをウィンドウ内に敷き詰めるというもの。
改造点はプログラムが立ち上がる際に、アイコンの左端の取得アイコン座標の情報を表示する点。

一生やってろと言いたくなるくらいのペースに苦笑い。

そんだけ。

Post to Twitter

, ,

No Comments

C++でのクラスと構造体

前回C++のクラスは構造体の拡張であるという話し。
まず一般的な構造体から見ていきましょう、と。

#include <iostream>
#include <string.h>
using namespace std;

struct hoge
{
    int a;
    double b;
    char c[128];
} foo;

int main()
{
    hoge* pFoo = &foo;

    foo.a = 100;
    foo.b = 3.141592674;
    strcpy(foo.c, "構造体のテストじゃ~。");

    cout << "直接参照:\n" <<
        "foo.a = " << foo.a <<
        "\nfoo.b = " << foo.b <<
        "\nfoo.c = " << foo.c << endl;
    cout << "間接参照:\n" <<
        "pFoo->a = " << pFoo->a <<
        "\npFoo->b = " << pFoo->b <<
        "\npFoo->c = " << pFoo->c << endl;

    cout << "\n\n";

    pFoo->a = 500;
    pFoo->b = 1.05;
    strcpy(pFoo->c, "ポインタから更新じゃじゃ~。");

    cout << "直接参照:\n" <<
        "foo.a = " << foo.a <<
        "\nfoo.b = " << foo.b <<
        "\nfoo.c = " << foo.c << endl;
    cout << "間接参照:\n" <<
        "pFoo->a = " << pFoo->a <<
        "\npFoo->b = " << pFoo->b <<
        "\npFoo->c = " << pFoo->c << endl;

    return 0;
}

まぁ、これは普通に読めること前提。
一応実行結果はこれ。

直接参照:
foo.a = 100
foo.b = 3.14159
foo.c = 構造体のテストじゃ~。
間接参照:
pFoo->a = 100
pFoo->b = 3.14159
pFoo->c = 構造体のテストじゃ~。

直接参照:
foo.a = 500
foo.b = 1.05
foo.c = ポインタから更新じゃじゃ~。
間接参照:
pFoo->a = 500
pFoo->b = 1.05
pFoo->c = ポインタから更新じゃじゃ~。

で、これをそっくりクラスで書くと下記の通り。

#include <iostream>
#include <string.h>
using namespace std;

class hoge
{
    public:
        int a;
        double b;
        char c[128];
} foo;

int main()
{
    hoge* pFoo = &foo;

    foo.a = 100;
    foo.b = 3.141592674;
    strcpy(foo.c, "クラスのテストじゃ~。");

    cout << "直接参照:\n" <<
        "foo.a = " << foo.a <<
        "\nfoo.b = " << foo.b <<
        "\nfoo.c = " << foo.c << endl;
    cout << "間接参照:\n" <<
        "pFoo->a = " << pFoo->a <<
        "\npFoo->b = " << pFoo->b <<
        "\npFoo->c = " << pFoo->c << endl;

    cout << "\n\n";

    pFoo->a = 500;
    pFoo->b = 1.05;
    strcpy(pFoo->c, "ポインタから更新じゃじゃ~。");

    cout << "直接参照:\n" <<
        "foo.a = " << foo.a <<
        "\nfoo.b = " << foo.b <<
        "\nfoo.c = " << foo.c << endl;
    cout << "間接参照:\n" <<
        "pFoo->a = " << pFoo->a <<
        "\npFoo->b = " << pFoo->b <<
        "\npFoo->c = " << pFoo->c << endl;

    return 0;
}

こんな風にpublic:をつけるだけ。
感がいい人はわかってると思うけど、構造体でもprivate:を付けてしまえばクラスっぽくなると言うね。
となると、構造体でもメンバ関数(メソッド)を用意することができる。

同じようなコードにすると下記なような感じになる。

#include <iostream>
#include <string.h>
using namespace std;

class CFoo
{
    private:
        int a;
        double b;
        char c[128];
    public:
        CFoo():a(0),b(0.0)
        {
            for (int i = 0; i < 128; ++i)
                c[i] = '\0';
        }
        ~CFoo() {}
        int setA(int num)
        {
            a = num;
            return 0;
        }
        int setB(double num)
        {
            b = num;
            return 0;
        }
        int setC(char* szStr)
        {
            if (strlen(szStr) > 127) {
                return 1;
            }
            strcpy(c, szStr);
            return 0;
        }

        int getA() { return a; }
        double getB() { return b; }
        char* getC() { return c; }
};

struct Bar_t
{
    private:
        int a;
        double b;
        char c[128];
    public:
        Bar_t():a(0),b(0.0)
        {
            for (int i = 0; i < 128; ++i)
                c[i] = '\0';
        }
        ~Bar_t() {}
        int setA(int num)
        {
            a = num;
            return 0;
        }
        int setB(double num)
        {
            b = num;
            return 0;
        }
        int setC(char* szStr)
        {
            if (strlen(szStr) > 127) {
                return 1;
            }
            strcpy(c, szStr);
            return 0;
        }

        int getA() { return a; }
        double getB() { return b; }
        char* getC() { return c; }
};

int main()
{
    CFoo obj;
    Bar_t obj2;

    cout << "初期化時のクラス:\n" <<
        "\tobj.getA() = " << obj.getA() <<
        "\n\tobj.getB() = " << obj.getB() <<
        "\n\tobj.getC() = " << obj.getC() << endl;
    cout << "初期化時の構造体:\n" <<
        "\tobj2.getA() = " << obj2.getA() <<
        "\n\tobj2.getB() = " << obj2.getB() <<
        "\n\tobj2.getC() = " << obj2.getC() << endl;

    obj.setA(10000);
    obj.setB(11.11);
    obj.setC("我が輩の年齢は10万と26歳である、フハハハハ!");

    obj2.setA(-1);
    obj2.setB(42.195);
    obj2.setC("オラ、ワクワクしてきたぞ!");

    cout << "代入時のクラス:\n" <<
        "\tobj.getA() = " << obj.getA() <<
        "\n\tobj.getB() = " << obj.getB() <<
        "\n\tobj.getC() = " << obj.getC() << endl;
    cout << "代入時の構造体:\n" <<
        "\tobj2.getA() = " << obj2.getA() <<
        "\n\tobj2.getB() = " << obj2.getB() <<
        "\n\tobj2.getC() = " << obj2.getC() << endl;

    return 0;
}

何となく命名規則をそれっぽくしてみたけど、こんな感じ?
ほとんどというか、private:とpublic:を付けてしまえば両方とも一緒という結果。

そんだけ。

Post to Twitter

, , ,

No Comments

Bad Behavior has blocked 24 access attempts in the last 7 days.