*目次 [#y067346b] #contents * gamelibとは何か [#s556f7a3] 2012年度のアドバンストプログラミングに向けて、 ~ 担当の森が開発した簡易的なゲームライブラリとアプリケーションのプログラムです。 ~ ライブラリとアプリケーションを分ける事で、 ~ 統一されたシステムをベースにした柔軟なゲーム開発が可能になります(はず…)。 ~ ゲームシステム(ヒットポイント管理、ゲームクリア・オーバー条件、ステージマップ管理、敵キャラ行動プログラム)等は ~ ライブラリに任せ、具体的なステージ構成はアプリケーション側にプログラムしましょう。 ~ 難易度の異なる複数のステージを作ったりする際には、ライブラリで構築した関数や構造体を使い、 ~ 使用する関数やパラメータ等を変更する事で実現します。 ~ ライブラリに任せる部分とアプリケーションに任せる部分を切り分けてプログラムする設計が重要です。 ~ 最初は提供されているライブラリやアプリケーションのプログラムを修正する事で課題を達成し、 ~ 次にライブラリを本格的に変更して独自のゲームシステムを構築しましょう。 * Open Dynamics Engine (ODE)について [#tdb9b966] ODEはこのゲームライブラリがベースにしている ~ オープンソースの物理シミュレーションライブラリです。 ~ 正確な物理シミュレーションより速度を重視した設計でゲーム用途に向いています。 ~ 今回はスクリプトファイルを実行する事で、自動的にインストールされます。 ODEは物理シミュレーションを行う部分と表示を行う部分に分かれています。 ~ 表示プログラムは自分でOpenGL等を用いて作成することも可能ですが、 ~ ODEには予めdrawstuffという簡易的な表示ライブラリが付属しています。 ~ 内部でOpenGLを用いてますが、特に意識する事はありません。 *サンプルコードのインストール [#k53b4fc1] 適当なディレクトリに移動して,以下のコマンドを実行します. $ wget jeap-res.ams.eng.osaka-u.ac.jp/~hiroki/AdvancedPrograming2012_gamelib_ode-0.11.1_20120516.tar.gz $ tar zxvf AdvancedPrograming2012_gamelib_ode-0.11.1_20120516.tar.gz $ cd AdvancedPrograming2012_gamelib_ode-0.11.1_20120516 $ ./MAKE ./MAKE スクリプトによりODEの解凍、コンパイル、インストールと 今回の課題のサンプルプログラムのコンパイルとインストールが行われます。 ~ サンプルプログラム(演習室 VineLinux 用)へのリンク http://jeap-res.ams.eng.osaka-u.ac.jp/~hiroki/AdvancedPrograming2012_gamelib_ode-0.11.1_20120516.tar.gz ~ Mac OSXや最新のlinuxで行いたい場合は以下のファイルをダウンロードしてください。 $ wget jeap-res.ams.eng.osaka-u.ac.jp/~hiroki/AdvancedPrograming2012_gamelib_ode-0.12_20120515.tar.gz ODEの最新版を使っています。 上記と同じコマンドを実行しますが、ODEをコンパイルするためには予めMacPortsやyum,apt-get等を用いて、 ~ freeglut等のOpenGL関係のライブラリをインストールしておく必要があります。 (最新のODEは演習室の環境でコンパイルできず、古いODEはOSXで動作しません) ~ サンプルプログラム(演習室 MacOS X 用)へのリンク http://jeap-res.ams.eng.osaka-u.ac.jp/~hiroki/AdvancedPrograming2012_gamelib_ode-0.12_20120515.tar.gz * 開発方法 [#sa09ce3d] 大規模なプログラム開発では、デバッグの容易さやコンパイル時間の短縮のために、 ~ プログラムの要素毎にファイルを作成し、それぞれ分割コンパイルを行い、最後に結合します。 ~ また、頻繁に使用する関数等は共有ライブラリとしてまとめ、 ~ 複数のアプリケーションプログラムから呼び出して使用します。 ~ さらに、コンパイルする際に一々コマンドを入力するのは面倒なので、 ~ makeによってコンパイルを自動化します。 以下では、大規模なプログラム開発で常識的に行われている以上の3点について解説します。 (例ではコンパイルのためにgccを使っていますが、 ~ gamelibではodeを使用する関係でC++のコンパイラが必要なため、g++を用いてコンパイルしています。) **分割コンパイル [#uc776c7f] 一つのアプリケーションプログラムを開発するために ~ 複数のファイルでプログラムソースを管理し、それぞれを別々にコンパイルして、 ~ 最後に結合します。 ~ - 分割コンパイル例 以下の.cファイルがあるとします。 main.c func.c 以上のファイルの中にはただ一つmain関数があるものとします。 まず、ぞれぞれを"-c"オプションをつけてコンパイルします。 $ gcc -c main.c $ gcc -c func.c $ ls main.o func.o "gcc -c"により.oファイルが生成されました。 最後に以下のように実行ファイルを生成します。 $ gcc -o test_func main.o func.o ただし、"-o"オプションは実行ファイルの名前を指定しています。 ** 共有ライブラリ開発 [#se80496f] ライブラリを用意する事、はアプリケーションプログラムで頻繁に使用する関数等を予め開発して、 ~ アプリケーション開発時に何度も同じプログラムを開発する手間を省いたり、 ~ 予め十分に構造を設計しておく事でバグの発生を防ぐ狙いがあります。 ~ 試しに普段使用しているコンピュータで使われている共有ライブラリを以下のコマンドで見てみましょう。 $ ls /usr/lib $ ls /usr/local/lib アプリケーションプログラム開発時には、そのライブラリの思想を十分汲み取る事で、 ~ 効率の良いプログラム開発ができ、アプリケーションプログラムのアイディア実現に集中する事ができます。 - MacOS Xの例 MacOS Xの場合、共有ライブラリのファイルは"lib"で始まり.dylibで終わる事になっており ~ 今回はlibfunc.dylibを作成するものとします。 ~ まず、以下の.cファイルがあるとします。 func1.c func2.c 以上のファイルの中にはmain関数があってはいけません。 まず、ぞれぞれを"-c"オプションをつけてコンパイルします。 $ gcc -c func1.c $ gcc -c func2.c $ ls main.o func.o "gcc -c"により.oファイルが生成されました。 最後に以下のように共有ファイルを作成します。 $ gcc -dynamiclib -o libfunc.dylib func1.o func2.o $ ls func1.c func1.o func2.c func2.o libfunc.dylib ただし、"-o"オプションは共有ファイルの名前を指定している。 - linuxの例 linuxの場合、共有ライブラリのファイルは"lib"で始まり.soで終わる事になっており ~ 今回はlibfunc.soを作成するものとします。 ~ まず、以下の.cファイルがあるとします。 func1.c func2.c 以上のファイルの中にはmain関数があってはいけません。 まず、ぞれぞれを"-c"オプションをつけてコンパイルします。 $ gcc -c func1.c $ gcc -c func2.c $ ls main.o func.o "gcc -c"により.oファイルが生成されました。 最後に以下のように共有ファイルを作成します。 $ gcc -shared -o libfunc.so func1.o func2.o $ ls func1.c func1.o func2.c func2.o libfunc.so ただし、"-o"オプションは共有ファイルの名前を指定しています。 ** 共有ライブラリの使用方法 [#l5ca86a5] 共有ライブラリを使用するためにはコンパイラにオプションを渡す必要があります。 gcc -L../lib -lfunc -o test_func main.c -L: ライブラリの場所を指定するオプション -l: リンクするライブラリを指定するオプション。ライブラリのファイル名はlib??.so (linux等)、lib??.dylib (MacOS X)のようになっているが、??の部分のみ記述します。 -実行する際の注意点(linuxの場合) ライブラリはldconfigで指定された場所か、 ~ 環境変数LD_LIBRARY_PATHで指定された場所を探すため、 ~ 適切に設定する必要があります。 本ライブラリでは"/usr/local/lib"等、通常システム用のディレクトリにインストールされるライブラリが ~ ユーザ領域にインストールされる設計になっています。 ~ これは演習室の環境がユーザにシステム領域へのアクセスを禁じているためで、 ~ LD_LIBRARY_PATH(MacOS Xの場合はDYLD_LIBRARY_PATH)を実行環境に合わせて設定し直す必要があります。 そのため以下のような記述をexec.shに自動的に書き込むようにスクリプトを作っていますので、 ~ exec.shを通して完成したプログラウを実行する事ができます。 $cat exec.sh #!/bin/sh export DYLD_LIBRARY_PATH="../../lib" #MacOS X用の記述 export LD_LIBRARY_PATH=../../lib:$LD_LIBRARY_PATH $* # スクリプトの引数を全て展開 $ ./exec.sh ./shoothing_game ** makeによるコンパイルの自動化 [#s5ab3edc] * gamelibのディレクトリ構造 [#ic9a84e2] ** application [#e02faab1] 具体的なゲームアプリケーションはここに作成します。 ~ shooting_gameディレクトリにサンプルとなる以下の基本ファイルが保存されています。 ~ main.c shooting_game.c shooting_game.h Makefile exec.sh ** include [#w7fb3b1b] - include/gamelib/ gamelibのヘッダファイルのディレクトリです。 以下のファイルが含まれています。 gamelib.h game_map.h self_agent.h enemy_agent.h interaction.h - include/ode/ Open Dynamics Engine(ODE)のヘッダファイルのディレクトリです。 - include/drawstuff/ ODEに付属しているODEを簡易に実行し、表示する環境のヘッダファイルのディレクトリです。 ~ ** src [#zf884f51] ソースファイルのディレクトリです。 - src/ode-0.12/ あるいは src/ode-0.11.1/ ODEのソースコードの圧縮ファイルを解凍すると現れるディレクトリです。 ~ 物理シミュレーション、衝突判定、表示や操作のためのライブラリ等がおさめられています。 ~ MAKEスクリプトを実行する事で自動的にコンパイルとインストールが行われます。 - src/gamelib/ 本ライブラリの本体のソースコードが保存されています。 ~ 以下のファイルが存在します。 gamelib.c game_map.c self_agent.c enemy_agent.c interaction.c Makefile * プログラム説明 [#afb835ca] ** アプリーケーションソースコード [#ode9390b] - Makefile makeを行う際の情報を書き込む。 - メイン関数を記述するファイル application/shooting_game/main.c --drawstuffへの関数等の登録 dsFunctions fn; // drawstuffに登録する情報を格納する構造体を宣言 fn.version = DS_VERSION; // drawstuffのバージョンを登録する。 fn.start = &start; //物理シミュレーション開始時に実行される関数(start)を登録する。 fn.step = &simLoop;//各ステップ毎に実行される関数(simLoop)を登録する。 fn.command = &command;//キーボードのキーが押された時に実行される関数(command)を登録する。 fn.stop = &stop;//シミュレーション終了時に実行される関数(stop)を登録する。 fn.path_to_textures = "./textures/";//テクスチャの画像ファイルを保存しているディレクトリの場所(./textures)を登録する。 -- ODEによる物理シミュレーションの実行 dInitODE2(0); // ODEの初期化(詳細は参考サイトか参考文献で) dsSimulationLoop (argc,argv,600,500,&fn); //物理シミュレーションと表示を開始(画面サイズ600x500) dCloseODE(); // ODEの修了処理 画面サイズを小さくすると処理が軽くなりますので、 ~ 演習室PCでは遅くなる場合に試してみて下さい。 - ステージ定義等を記述するファイル application/shooting_game/shooting_game.c application/shooting_game/shooting_game.h ODEを簡易に実行するdrawstuffに登録するコールバック関数を定義します。 ~ 定義された関数はmain関数の中で登録している。 -- void start(); //物理シミュレーション開始時に一度だけ呼び出される関数。 initialize_gamelib(); // gamelibの初期化。必ず最初に行う。 dAllocateODEDataForThread(dAllocateMaskAll); // ODEの初期化 float default_view_position[3] = {0.0f,0.0f,3.0f}; float default_view_direction[3] = {0.0f,30.0f,0.0f}; dsSetViewpoint (default_view_position, default_view_direction); // マップの作成 // マップの座標(x,y) x:西→東(0→2)、y:南→北(0→2) と設定 make_dead_end_walls(0,0, FROM_NORTH ); make_t_junction_walls(0,1, FROM_EAST ); make_dead_end_walls(0,2, FROM_SOUTH ); make_turn_walls(1,0, FROM_EAST_TO_NORTH ); make_cross_road_walls(1,1); make_turn_walls(1,2, FROM_SOUTH_TO_EAST ); make_turn_walls(2,0, FROM_NORTH_TO_WEST ); make_turn_walls(2,1,FROM_WEST_TO_SOUTH ); make_dead_end_walls(2,2, FROM_WEST ); self_agent = make_self_agent( 0,0, 100,M_PI/2 ); /*セルフエージェントを初期化。構造体のデータはself_agent.hで定義されている。 */ set_first_person_view_point( self_agent ); /* ファーストパーソンビューに切り替える */ enemy_agent0 = make_enemy_agent(0,1, 10); // enemy_agent 0番の定義(ポインタが代入される) enemy_agent1 = make_enemy_agent(0,2, 10); // enemy_agent 1番の定義(ポインタが代入される) -- void simLoop( int pause ); //シミュレーションステップ毎に呼び出される関数 update_gamelib(); /* gamelib全体のアップデート。主にODEの処理。中身はgamelib.cに記述*/ if( NULL != self_agent ){ /* self_agentポインタがNULLでない事を確認*/ update_self_agent( self_agent ); /*自機のアップデート。中身はself_agent.cに記述*/ /* 視点切り替え処理。view_point_switchのラベルに従って視点切り替え */ if( view_point_switch == BEHIND_VIEW ){ /* Behind mode */ set_behind_view_point( self_agent ); visualize_self_agent( self_agent ); }else{ /* First person view mode*/ set_first_person_view_point( self_agent ); } visualize_self_agent_bullets( self_agent ); } if( enemy_agent0 != NULL && 0 == enemy_agent0->hit_point ){ /* もしenemy_agent0のポインタがNULLでなく、enemy_agent0のヒットポイントが0の時 */ destroy_enemy_agent( enemy_agent0 ); // enemy_agent0のメモリを解放 enemy_agent0 = NULL; // ポインタにNULLを代入 } // ゲーム終了処理 if( NULL == enemy_agent0 && NULL == enemy_agent1 ){ printf("Congraturations! Game Clear!\n"); stop(); exit(0); } if( get_self_agent_hit_point() <= 0 ){ printf("You lose! Game Over!\n"); stop(); exit(0); } -- void command( int c ); キーボードからの入力を受け付ける。 -- void stop(); 終了処理 **ライブラリソースコード [#rb97bf25] - ゲームシステム全体を管理する関数を記述するファイル include/gamelib/gamelib.h src/gamelib/gamelib.c -- プログラム中に必要な資産を管理する仕組み 一旦全てのデータはGameObject構造体に格納されて管理される。 struct GameObject{ enum ObjectType object_type; void* data; }; enum ObjectTypeはGameObjectに格納されたデータがどのような種類かを示している。 この値に基づいて "void* data" をキャストすることが可能になる。 enum ObjectType{ GROUND, WALL, SELF_AGENT, SELF_AGENT_BULLET, ENEMY_AGENT, }; voidポインタはどのようなポインタでも格納できる万能なポインタである。 ~ ただし、ポインタで指し示している先がどのようなデータか把握していないとデータを読む事ができない。 ~ ~ キャストとは、ある型の変数を別の方の変数として使用するC言語の仕組みである。 ~ 以下のように用いる。 void* void_p; double* double_p double_p = (double*) void_p; int i = 10; double d; d = (double)i; -- gamelib.cに記述された関数 --- プログラムの最初にシステム全体を初期化するための関数 void initialize_gamelib(); --- プログラムの最後にシステム全体の最終処理をするための関数 void finalize_gamelib(); --- 毎回のアップデートをする関数 void update_gamelib(); 主にODEの物理シミュレーションのアップデートを行う。 dSpaceCollide (space,0,&nearCallback); /* ODEの衝突計算 */ dWorldStep (world,SIMULATION_TIME_STEP); /* ODEの力学シミュレーション1ステップ */ dJointGroupEmpty (contactgroup); /* ODEの衝突情報の開放 */ --- ODEで2つの物体が近づいて、衝突しそうになった時(衝突している時)に呼び出されるコールバック関数 void nearCallback( void *data, dGeomID geom1, dGeomID geom2 ); Self Agent と Enemy Agentの間等の衝突処理を行う。 ~ - 自機エージェント構造体定義と関数を記述するファイル include/gamelib/self_agent.h src/gamelib/self_agent.c ~ --自機の情報を格納する構造体 struct SelfAgentData{ dBodyID body_ID; /* ODEの物理計算に必要なID(実際はポインタ) */ dGeomID geom_ID; /* ODEの衝突計算に必要なID(実際はポインタ)*/ int hit_point; /* 自機のヒットポイント*/ dReal direction_angle; /* 自機の方向。東が0ラジアン */ dVector3 pulling_force; /* 自機を移動させるための力をセットする変数 */ }; ---direction_angleの角度と向き 東: 0 [rad] ( 0[deg]) 北: M_PI/2 [rad] ( 90[deg]) 西: M_PI [rad] (180[deg]) 南: -M_PI/2 [rad] (-90[deg]) --自機から発射される弾丸の情報を管理する構造体。 struct SelfAgentBulletData{ dBodyID body_ID; dGeomID geom_ID; int countdown_timer; /* 発射されると初速度を与え、タイマーに値をセットする。 ステップ毎にタイマーをカウントダウンして、0になったら位置をリセットする。 値が0の場合、物理シミュレーションと衝突判定を行わない。 */ }; --自機のデータを生成してポインタを返す関数 struct SelfAgentData* make_self_agent( int map_x, int map_y, int initial_hit_point, dReal initial_direction_angle); --自機のポインタを安全に消去する関数 void destroy_self_agent(struct SelfAgentData* self_agent_data); --自機をアップデートする関数 void update_self_agent(struct SelfAgentData* self_agent_data); -- 自機の背後から視点に切り替える関数 void set_behind_view_point(struct SelfAgentData* self_agent_data); --自機の一人称視点に切り替える関数 void set_first_person_view_point(struct SelfAgentData* self_agent_data); --自機を操作するコマンドを入力して、必要な処理を行う関数 void command_self_agent( int command, struct SelfAgentData* self_agent_data ); --自機から弾丸を発射する関数 void self_agent_shoot_gun(struct SelfAgentData* self_agent_data); --自機を可視化する関数 void visualize_self_agent(struct SelfAgentData* self_agent_data); --自機の弾丸を可視化する関数 void visualize_self_agent_bullets(struct SelfAgentData* self_agent_data); --自機の位置を返す関数 const dReal* get_self_agent_position(struct SelfAgentData* self_agent_data); --自機のヒットポイントを返す関数 int get_self_agent_hit_point(struct SelfAgentData* self_agent_data); 以下の値を直接参照する事もできるが、 ヒットポイントの処理について何らかの変更を行いたい時に関数化しておくと便利。 self_agent_data->hit_point; --自機のヒットポイントを増減する関数 void change_self_agent_hit_point( struct SelfAgentData* self_agent_data, int hit_point ); 正の値で増加し、負の値で減少する。 以下のように直接操作する事もできるが、 ヒットポイントの処理について何らかの変更を行いたい時に関数化しておくと便利。 self_agent_data->hit_point --; - 敵エージェント構造体定義と関数を記述するファイル include/gamelib/enemy_agent.h src/gamelib/enemy_agent.c --敵機の情報を格納する構造体 struct EnemyAgentData{ int hit_point; dBodyID body_ID; dGeomID geom_ID; }; --敵機のデータを生成してポインタを返す関数 struct EnemyAgentData* make_enemy_agent( int map_x, int map_y, int initial_hit_point ); --敵機を可視化する関数 void visualize_enemy_agent( struct EnemyAgentData* enemy_agent_data ); --敵機をアップデートする関数 void update_enemy_agent( struct EnemyAgentData* enemy_agent_data ); --敵機を安全に消去する関数 void destroy_enemy_agent( struct EnemyAgentData* enemy_agent_data ); --敵機のヒットポイントを返す関数 int get_enemy_agent_hit_point( struct EnemyAgentData* enemy_agent_data ); 以下の値を直接参照する事もできるが、 ヒットポイントの処理について何らかの変更を行いたい時に関数化しておくと便利。 enemy_agent_data->hit_point; --敵機のヒットポイントを増減する関数 void change_enemy_agent_hit_point( struct EnemyAgentData* enemy_agent_data, int hit_point ); 以下のように直接操作する事もできるが、 ヒットポイントの処理について何らかの変更を行いたい時に関数化しておくと便利。 self_agent_data->hit_point --; - ゲームマップの構造体定義と関数を記述するファイル include/gamelib/game_map.h src/gamelib/game_map.c - インタラクションのための関数を記述するファイル include/gamelib/interaction.h src/gamelib/interaction.c 自機と敵、弾と敵がぶつかった時の処理プログラム。 *参考になる情報 [#v3647a89] -公式ホームページ http://www.ode.org/ -demura.net: ロボットの開発と教育 http://demura.net/ode ~ 金沢工業大学の出村先生のページ ~ 非常に分かりやすい解説 -簡単!実践!ロボットシミュレーション - Open Dynamics Engineによるロボットプログラミング ~ http://www.amazon.co.jp/dp/4627846916 ~ 出村先生が執筆されたODEの解説本 -『Open Dynamics Engine(ODE)』のオリジナルマニュアル ~ http://www.koj-m.sakura.ne.jp/ode/ ~ 大阪大学の松下先生のページ