■ 新・ゲーム開発講座 |
■ へっぽこプログラミング入門♪ |
■第9夜:テキストインタプリタの基礎 前回簡単に解説した文字コードの話をもとに、テキストファイルを読み込んでウェイトをかけながら表示をする実行ファイルを作ってみましょう。なお、今後はいちいちリストの中身を全部解説しているとあまりに大変なので、アルゴリズムの話をメインにして具体的な部分はソースを読んで下さい、というスタンスで行きたいと思います。まずはこのソースをダウンロードしてコンパイル → 実行してみてください。(スケルトンをちょっとだけ進化させています) さて如何でしょうか。これはディスクから default.txt というテキストファイルを読み込んで、ウェイトをかけながらたらたらと表示するプログラムです。制御文字に対する処理を何も行っていないので、文字列を表示し終わってもメモリ内のゴミ(画面上では・になっている筈です)を延々と表示してしまいますが、まあそこはプロトタイプですからご勘弁願いたいところです(^^)。今回以降、このソースを発展させながらノベルゲームの実行ファイルを作っていくと致しましょう。 |
|
■各ソースの内容
テキストを1文字ずつ表示している部分を、ここではテキストインタプリタ、またはテキストエンジンと呼んでおこうと思います。今回は文字表示を行っているだけですが、ノベルゲームはこの部分を拡張して様々な機能を盛り込むことで完成します。この部分は、テキスト表示枠の範囲を変更することでADVの基本形にもなりますし、RPGの会話モジュールにもなります。事実、この講座で出てくる処理の多くは拙作 Demon's Eye (RPG)の会話モジュールを継承しています(コマンド名もそっくりですし ^^;)。 テキストインタプリタは、スクリプトファイル(ここでは普通のテキストファイル)を読み込んでその先頭から順次文字を表示していきます。制御コマンドにぶつかったときはその処理関数を呼んだりもしますが、ここではまだそこまでは作りこんでいません。なお、本講座ではスケルトンから始まって順次機能を拡張しながらゲームを作っていきます。突然ソースの内容が全然変わってしまう…ということはありませんので(ファイル分割くらいは時々やりますけど)、安心して読み進んでください♪ 小物関数は g_tool.cpp と BasicTips.cpp に雑多に放り込んでありますで、TextEngine.cpp で妙な関数が出てきたら適宜参照してください(^^)。では main.cpp から解説です。 ■main.cppでやっていること ソースを見れば、もうこれ以上ないくらい単純明解ですね。ゲームシステムの初期化を行う init_game() 関数を設け、その中でテキストエンジンの初期化関数 Init_Text_engine() を呼び、メインループではひたすらテキストエンジンの実行部 Text_engine() を呼びまくっています。この Mainloop() はプログラムが終了するまで無限に回り続けます。従ってテキストエンジン実行部 Text_engine() も延々と繰り返し呼ばれ続けている訳です。この繰り返しの中で、文字表示やコマンド解釈が順次進行していくという次第です。 |
void init_game()
void Mainloop(void)
} APIENTRY WinMain(HINSTANCE hIns,HINSTANCE hPI,LPSTR lpArg,int nCmdShow) {
} |
■TextEngine.cppでやっていること @初期化 デフォルトパラメータについては特に解説は必要ないと思います。単にフォントのサイズやテキスト展開エリア、文字送りピッチなどを決めているだけです。テキスト表示関係の変数はプログラムのあちこちから参照されることになるので、ここでは安直にグローバル変数で持つことにします(笑)
フォントサイズや表示ウェイトは #define してシンボルで持てばいいじゃないか、という声も聞こえて来そうですが、将来スクリプト上からコマンド指示で変更できるよう拡張する予定がありますので変数のカタチで持っておきます。 |
#define SIZE_OF_TEXT_BUF 0xffff //64KBもあればバッファとしては充分でしょう DWORD TEXT_TIMER; //タイマー //------------------------------------- void _Set_Default_params()
} void Init_Text_engine()
} |
ところで、イニシャライスの最後にタイマーを撃っていますが、これが 「1文字ずつ表示」 を実現するカギになります。タイマーについては、ここでは BasicTips.cpp 内の HLS_timer_start( ) 、HLS_timer_check() という2つの小物関数を使うことにします。やっていることといえば GetTickCount() で現在の時間を取得して保存し、チェック時には再び現在時刻を取得してその差分から経過時間を調べているだけですが、この程度でも充分実用性があります。この仕掛けを利用して、1文字表示毎のウェイトを取ってやろうという算段な訳ですね。 |
int HLS_timer_start( DWORD *timer
)
} int HLS_timer_check( DWORD timer
, DWORD wait_time )
} ※良く見たら、本来 bool 型にするべきところですね(爆 ^^;) int でもエラーにはなりませんが♪ |
Aループ 次に、Mainloop()からひたすら呼ばれる Text_engine() を見てみましょう。関数の最初で経過時間をチェックしています。初期化のときに定めた経過時間が過ぎるまで、関数の中身は実行されずに入り口段階で Mainloop() に戻される仕組みになってる訳です。ここが文字表示のウェイト部分に相当します。 指定時間が経過すると、とりあえずタイマーの関所は通過可能になります。関数の内部に入ってきたら、すかさず次のタイマーショットを放ちます。文字を表示して戻ってくるまでの間にも時間は過ぎてしまうので、なるべく近い位置でネクストショットを放っておこうという意図です(少しでも正確なウェイトをとるために ^^)。 さてその次が重要です。テキストポインタ TEXT の指す1バイトが 0x80 以上かどうかを調べています。2バイト文字か1バイト文字かの簡易判定ですね(ちょっと乱暴だけど ^^;)。それに応じて、2バイト文字用/1バイト文字用の表示を切り替えています。文字表示関数 ChrPut3D() は g_tool.cpp にありますので参照してください(第5夜で扱った StrPut3D() の変形版です)。 |
//------------------------------------- void Increment_textp_pos(int inc)
{
} //-------------------------------------
} |
なお ChrPut3D() で文字表示を行った後、次回のために Incriment_textp_pos() で次の文字の表示座標を計算しておきます。計算と言っても、現在座標に文字ピッチを加えるだけの単純なものです ( 注:TEXT_X_PITCH は半角1文字分の数値なので、全角2バイト文字では2倍して加算しています)。加算をしたらテキスト表示エリアをはみ出していないかのチェックを行い、文字がエリア右端まで行っていたら改行操作 ( Yを1段下げてXを0にする) を行います。 ・・・これを繰り返していくことで、英数字/漢字かな混じりの文章でも綺麗に表示していくことが可能になるのですね♪(^0^) ちょっと面倒かもしれませんが、一度心臓部が出来てしまえば後に追加する部分はどんどん定型化処理になって加速度的に楽になります。めげずに頑張りましょう。 |
|