Comments
Description
Transcript
Windowsプログラミングによる ノベルゲーム製作ツールの作成
Windowsプログラミングによる ノベルゲーム製作ツールの作成 国際文化学部国際文化学科 情報処理演習 9970119 木下治之(製作協力:石井基之) 目的: プログラミングでノベルゲームの雛型を作った上で 簡単なノベライズゲームを製作する。 プログラムのできない人でもコマンド一覧を片手に 指定のファイル名でテキストデータを作ってやれば 簡単なノベライズゲームを作成することが出来る。 Windows 使用言語:VisualC++(基本として C ベース) 基本方針として、DirectX・MFC 等は使用しない。 製作環境:Windows98SE・XP 製作ソフト:MicrosoftVisualC++ 6.0 OS 実装目標: MIDI、WAVE の演奏 DIB による特殊描画 テキストファイルからデータを読み取り、文字の表示 グラフィック(BMP ファイル)の表示 フラグ・分岐の作成 セーブ&ロードの実行 最終実装: MCI による BGM の演奏(WAVE・MIDI の演奏が可能) 画面の特殊効果 テキストファイルからデータを読み取り、文字の表示 グラフィック(BMP ファイル)の表示 分岐・選択肢の作成 基本構成: このシステムは以下の四つのソースファイルからなる。 Main システムの土台となる基本的なシステム。 Text テキストファイル(スクリプト)の読み込み関係のプログラム。 Command スクリプト内で読み込まれたコマンドの実行関数。 Tips その他の下請け関数等 仕様説明: このプログラムを使用するためには まず、実行ファイルと同じ階層内に 4 つのフォルダを設置する。 各フォルダの役割は以下に列挙する。(フォルダ名は全て小文字) TXT ゲーム内で使用するシナリオを掲載したテキストファイルを設置する。 MIDI ゲーム内で使用する MIDI ファイルを設置する。 WAVE ゲーム内で使用する WAVE ファイルを設置する。 BACK ゲーム内で使用する画像ファイル(BMP)を設置する。 上記の 4 つのフォルダを実行ファイルと同階層に設置した上で TXT フォルダ内に、 「text」という名前のテキストファイルを設置します。 そのテキストファイルに、画面に表示したい文章(ゲーム中で使用したいシナリオ)を 一般の書式に基づき記入します。 これだけの状態で、実行ファイルを起動させると、ウィンドウが開き テキストファイル内に書き込まれた文字がそのウィンドウに表示される。 それ以外にも、指定されたコマンドを記入してやることにより 前もって登録された特定の行動を取ることができる。 また、基本的に、コマンドと普通の文章を区別する手段として「#」を用いている。 「#」が文章中に登場した時点で、そこから先の文字はコマンドと判定して そこから「Command.cpp」に処理を飛ばしてそちらの関数で処理を行う。 以下に、今回使用する基本コマンドを示しておく。 ; New Wait End delay Back_load 改行コマンド(これのみ「#」を必要としない) 改ページ処理を行うコマンド キー・マウス入力を待つコマンド システムを停止させウィンドウを閉じるコマンド 指定された時間だけ文字表示等を待機するコマンド 画像読み込みコマンド Back_copy Back_change Midi_play Midi_stop Wave_play Wave_stop 画像コピー・転送コマンド 画像切り替え効果 BGM(MIDI)を再生するコマンド BGM(MIDI)の再生を停止するコマンド 効果音(wave)を再生するコマンド 効果音(wave)を停止するコマンド (*上記のコマンドは全て小文字、半角とする。) 前回のプログラムと違う点として、「#」をコマンド記入の開始合図としているために 「#」が前になければ半角文字も表示することができるようになっている。 また、この形式の特徴として、流れ文字を制御する部分さえ増やせば もっと多数の機能を追加できると言う事である。 (コマンドとして使用する文字を Command_call 関数内に記入し、 対応する処理を他のコマンドと同じ様に関数を作ってやるだけで可能である。) よって使用者にプログラミングの知識さえあれば もっといろいろな機能を追加していく事が可能である。 プログラムソース説明: それではここからプログラムの主要関数の説明をしていく。 まず、第一にプログラム起動時の初期化とメッセージループ処理を行い、ウィンドウズプ ログラミングで大元になる、WINMAIN 関数から説明していく。 WinMain(HINSTANCE hIns,HINSTANCE hPI,LPSTR lpArg,int nCmdShow) { システムのメッセージ構造体 WNDCLASS ng; //Window クラス hinst=hIns; // グローバル変数に、起動時に与えられるインスタンス値を保存 //窓を開くための所要パラメータを Window クラスのメンバ変数に与えていく ng.hInstance=hIns; //インスタンス値 ng.lpszClassName="VNG 製作エンジン"; //クラス名 ng.lpfnWndProc=(WNDPROC)WndProc; //イベントハンドラを登録 MSG msg; ng.style=0; //Windows // アイコン表示 ng.hCursor=LoadCursor((HINSTANCE)NULL,IDC_ARROW); //カーソル表示 ng.hIcon=LoadIcon((HINSTANCE)NULL,IDI_APPLICATION);/ ng.lpszMenuName=0; ng.cbClsExtra=0; ng.cbWndExtra=0; ng.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); if(RegisterClass(&ng)==0)return 0; hwnd=CreateWindowEx( 拡張スタイル ng.lpszClassName,//クラス名 "ノベリングツール",//Window の左上に表示される窓の名前 WS_OVERLAPPEDWINDOW, //ウィンドウスタイル 20,20,600,400+20, //窓の配置や大きさ (HWND)NULL, //親 Window のハンドル 0, // (HMENU)NULL, // インスタンス値 hIns, // (LPVOID)NULL // ); を開くのに失敗したら終了する SetWinCenter(hwnd); //Window を画面中央にする ShowWindow(hwnd,nCmdShow); //外枠 UpdateWindow(hwnd); //クライアント領域 win_hdc=GetDC(hwnd); //画面操作の為に DC を取得 I_game(); //ゲームシステム初期化 //窓が開いたら、ここで無限ループに入ってイベント監視を行う if(!hwnd)return 0; //Window while(1){ メッセージキューになにか届いたか? // if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){ メッセージがあれば処理する // 終了メッセージならループ抜 TranslateMessage(&msg);//メッセージを文字メッセージに変換 DispatchMessage(&msg); //ウィンドウプロシージャ関数にメッセージを流す if(!GetMessage(&msg,(HWND)NULL,0,0))break;// }else{ メッセージが無ければ Main_loop()を実行 // Main_loop(); } } ↓終了時には wParam パラメータが持つ終了コードを返す規定になっている // return msg.wParam; } アプリケーションの実行開始位置となる関数。 DOS でいう main() に相当し、ここでウィンドウの初期設定を行い イベント監視ループを回していく。 基本的にはメッセージループ内の PeekMessage で Windows システムのメッセージキューをを監視し、 自分のウィンドウに関係あるようなメッセージがあれば それを TranslateMessage 関数で イベントハンドラが解釈しやすい文字メッセージに変換して、 次に説明する WinProc 関数にそのメッセージを受け流していく。 また、自分に関係のあるメッセージがこない場合は、 この後に説明する Main_loop 関数を絶えず呼び続けていく形となる。 またこの関数の中で、ShowWindow・UpdateWindow 関数を呼び出し、 作成したウィンドウの描画や I_game 関数を呼び出し、ゲームの状態を初期化している。 Windows 関数: この関数は、キーやマウス入力の処理を行う WndProc WndProc(HWND hwnd,UINT msg,WPARAM wprm,LPARAM lprm) { switch(msg){ case WM_CREATE: //Window の作成 //Window の消去 break; case WM_DESTROY: System_exit(); case WM_PAINT: ペイント命令 //バックサーフェイスを表にコピー // BitBlt(win_hdc,0,0,600,400,Back_DC,0,0,SRCCOPY); return DefWindowProc(hwnd,msg,wprm,lprm); ↑ にデフォルト処理を任せる。 //キーが押された // DefWindowProc() case WM_KEYDOWN: switch(wprm){ case VK_ESCAPE: System_exit(); break; case VK_SPACE: case VK_RETURN: リターンキーが押された // if( Mode_steatus.flag_cursor_blink == ON ){ Com_cursor_blink_end();} ブリンク中なら解除 // if( Mode_steatus.flag_end == ON ){ Com_end_end(); 終了待ち中なら終了処理 } // if( Mode_steatus.flag_npage2 == ON ){ Com_npage2_end(); } ページ変更の入力待ち中なら終了処理 // break; } break; case マウス左ボタンが押された WM_LBUTTONDOWN: // if( Mode_steatus.flag_cursor_blink == ON ){ Com_cursor_blink_end(); } if( Mode_steatus.flag_end == ON ){ Com_end_end(); } if( Mode_steatus.flag_npage2 == ON ){ Com_npage2_end(); } Mouse_steatus.Left_time_old = Mouse_steatus.Left_time; 時刻を確保しておく Mouse_steatus.Left_time = GetTickCount();// goto case WM_LBUTTONUP: goto case SET_MOUSE_STEATUS; マウスの左ボタンが離された // SET_MOUSE_STEATUS; マウス右ボタンが押された WM_RBUTTONDOWN: // Mouse_steatus.Right_time_old = Mouse_steatus.Right_time; 時間を確保しておく Mouse_steatus.Right_time = GetTickCount();// goto case WM_RBUTTONUP: goto case SET_MOUSE_STEATUS; マウスの右ボタンが離された // SET_MOUSE_STEATUS; WM_MOUSEMOVE: マウスが動いた // SET_MOUSE_STEATUS: リアルタイムでマウスの状態を獲得、保存しておく // ボタン状態 Mouse_steatus.xpos=(int)LOWORD(lprm); //X座標 Mouse_steatus.ypos=(int)HIWORD(lprm); //Y座標 Mouse_steatus.fwkeys=(int)wprm; break; case MM_MCINOTIFY: //MCI // の終了 if( MIDI_steatus.MIDI_play_stat == ON ){ Com_restart_midi(); // 再演奏 } break; default: return DefWindowProc(hwnd,msg,wprm,lprm); その他のは Windows に任せる // } return 0; } 基本的に、プログラム起動時に WinMain()内部で登録されるもので、 この関数はキー入力やその他のイベントの処理部分となる。 なので、基本的なウィンドウの描画、終了から、ここではキーのタイプ、マウスの左クリ ック・右クリック、MCI 関係の終了によるメッセージの取得等、この部分では様々なメッ セージの取得からその対応する関数への処理の移行等を行っています。 引数の hwnd、msg、wprm、lprm はいずれも Windows システム経由で流れてくるデ ータで、この引数がイベントの内容を表現している。 また、処理が面倒なところでは、DefWindowProc 関数を用い、既定のウィンドウプロシ ージャを呼び出して、アプリケーションが処理しないウィンドウメッセージに対して、既 定の処理をするようにしてある。 また、途中ででてきた MOUSE_STEATUS であるが、これはマウスの状態を格納する構 造体であり、次のようになっている。 Mouse_steatus { int fwkeys; int xpos,ypos; マウスの座標(自分のウィンドウ内だけ考えれば良い) DWORD Right_time;//右ボタンを押したときの GetTickTime()の値 // DWORD Right_time_old; 前回右ボタンを押したときの GetTickTime()の値 DWORD Left_time;//左ボタンを押したときの GetTickTime()の値 // DWORD Left_time_old; 前回左ボタンを押したときの GetTickTime()の値 // }_Mouse_steatus; 一度取得したマウス状態をこの構造体に放り込んでおいて、 状態更新がない間はこの構造体を参照しながらプログラムの実行を続ける。 マウス関連イベントではこの構造体のメンバ変数の内容更新のみ行うようにする。プログ ラム中でマウス状態を知りたいときは、この構造体の内容をチェックするだけで済ませる。 ちなみに、プロシージャ内でシステムから降ってくる汎用パラメータ wprm、lprm の内容 をこのマウス構造体に代入している。 続いてこの構造体を初期化する関数をつくる。 void I_Mouse_steatus(): Mouse_steatus 構造体初期化関数 { Mouse_steatus.fwkeys = 0; Mouse_steatus.xpos = 0; Mouse_steatus.ypos = 0; Mouse_steatus.Left_time = GetTickCount(); Mouse_steatus.Left_time_old = GetTickCount(); Mouse_steatus.Right_time = GetTickCount(); Mouse_steatus.Right_time_old = GetTickCount(); } マウスの入力がなかったことにするために全部初期化させる。 Main_loop void { 関数:メッセージ(マウスやキー入力等)が無いときに実行するループ Main_loop(void) テキスト表示 if( Mode_steatus.flag_text == ON ) T_engine();// if( Mode_steatus.flag_wait == ON ) Com_wait_task(); 待機 // ブリンク if( Mode_steatus.flag_cursor_blink == ON ) Com_cursor_blink_task();// 終了 if( Mode_steatus.flag_end == ON ) Com_end_task();// ページ変更 if( Mode_steatus.flag_g_change == ON ) Com_g_change_task();//画面効果 if( Mode_steatus.flag_Select == ON ) Com_Select_task();//選択肢 if( Mode_steatus.flag_npage2 == ON ) Com_npage2_task();// 裏画面から表画面に転送 BitBlt(win_hdc,0,0,600,400,Back_DC,0,0,SRCCOPY); // } } メッセージが無いときには、この関数の中を絶えず巡回していく形になる。 If 分形式でフラグをチェックしていき、それに該当するものがあるかどうかを絶えずチェッ クしていっている。 また、後述するが今回の基本的な画面操作は裏画面から表画面に随時転送するという形式 をとるために、最後に Bitblt 関数で裏画面から表画面に転送をしている。 また、関数中に登場する Mode_steatus はフラグ関係を収めた構造体で以下のようになっ ている。 struct _Mode_steatus { int int int int int int int テキスト表示:ON=進行 OFF=停止 flag_delay; //遅延処理:ON=遅延あり OFF=遅延なし flag_cursor_blink; //カーソル点滅:ON=点滅 OFF=点滅なし flag_end; //終了:ON=終了 OFF=終了ではない flag_npage2;//改ページ type2:ON=改ページ処理中 OFF=処理中でない flag_g_change; //画面切替効果:ON=切替中 OFF=処理はない flag_Select; //3択:ON=選択中 OFF=処理はない flag_text; // }; ここまでのこの三つの関数について簡単にまとめて説明すると Windows ではイベント処理を担当する関数(今回でいう WndProc 関数)を起動時に登 録しておき、あとは Windows のシステム内を行き来している、各種イベントメッセージの うち自分のウィンドウに関連するものだけを WndProc 関数に流して処理する、という形を とることとなり、メッセージ処理が行われない場合は、Main_loop 関数がひたすら何度も呼 び出され、タスク処理を行っていくこととなる。 次に、基本動作となるテキスト関係、グラフィック表示関係の関数について説明する。 テキストに関しては今回、シフトJISのコードの仕様を利用して、それから半角、全 角などのバイト数を判断して表示させていくようになっている。 シフト JIS について簡単に説明すると、このコードは第1バイトが 80h 以上であり、 / ; 等は、すべて 80h 未満のコードである、つまり、全角文字と競合する場合は必ず第2バイ トとぶつかることとなる。 よって、それが半角文字か全角文字かの判定は、{ } / ; 等を見 つけた場合、その直前の1バイトが 80h 以上かどうかで行なうことができる。80h 以上な ら全角、80h 以下なら半角文字という判断になる。 これを利用して今回は半角判定の「/」や「;」をコマンド制御文字として利用していくこ ととする。 また途中で出てくることになると思うのでここで記述しておくが 説明文中でヘッダーファイル参照としてある変数はそれぞれ以下のように宣言されている。 #define G_PATH ".¥¥back¥¥" #define TEXT_PATH ".¥¥txt¥¥" #define WAV_PATH ".¥¥wav¥¥" #define MIDI_PATH ".¥¥MIDI¥¥" グラフィックデータ置き場 //テキストスクリプト置き場 //WAV ファイル置き場 //MIDI ファイル置き場 // それでは、文字表示関数から順番に紹介していく。 StrPutFont 関数:文字列表示関数 int StrPutFont (HDC hdc,int x,int y,int fontSize,UINT color1,UINT color2,char *str) :デバイスコンテキストへのハンドル // x,y:表示座標 // fontSize:フォントのサイズ // color1:文字の色 // color2:影の色 // str:表示したい文字列へのポインタ // HDC { HFONT hfont,oldfont; hfont=CreateFont( 0, フォント高さ //フォント幅 0, // 0, // 500, // fontSize, // フォント太さ(0-1000 で 100 単位:標準=400) 0, //TRUE=イタリック設定 0, //下線 0, //打消線 SHIFTJIS_CHARSET, //キャラクタセット OUT_DEFAULT_PRECIS, //出力精度 CLIP_DEFAULT_PRECIS,//クリップ精度 DEFAULT_QUALITY, //文字品質 DEFAULT_PITCH | FF_DONTCARE,//文字ピッチ "MS ゴシック" //フォント種類 ); if(hfont==NULL){ エラー","エラー",MB_OK);//エラー処理 MessageBox(NULL," return 0;} 背景の指定関数:背景画像を背景に SetBkMode(hdc,TRANSPARENT);// 文字を表示 // SetTextColor(hdc,color1); oldfont=(HFONT)SelectObject(hdc,hfont); TextOut(hdc,x,y,str,strlen(str)); SelectObject(hdc,oldfont); 色指定関数 //フォント指定 //表示 //フォント指定戻し // DeleteObject(hfont); return 0; } 処理はまず、フォントオブジェクトを生成するところから始まる。 hfont=CreateFont(・・・)でフォントオブジェクトを作成するとともに、そのハンドルを取 得する。現時点ではノベライズゲームでよく使われるフォントサイズの変更のみ変数にし て、それ以外はここの関数で全部指定しておくことにする。 次に、oldfont=(HFONT)SelectObject(hdc,hfont) で、生成したフォントオブジェクトを選 択すると同時に旧フォントオブジェクトへのハンドルを取得しておく。これにより SelectObject の後は、文字表示はすべて新しいフォントに切り替わることになる。 そして、TextOut などでフォントを使用し終ったら、再度 SelectObject() で旧設定を復 活させ、用済みになったフォントオブジェクトを DeleteObject(hfont) で削除する。これ に、文字背景の指定関数と、色指定関数を加えたものをひとまとめにした。 int Load_Bmp :簡易型 BMP 表示関数 int Load_Bmp( HDC hdc, char *f_name_of_BMP) { HANDLE hbmp; HDC work_DC; hbmp=LoadImage( インスタンスハンドル f_name_of_BMP,//BMP のファイル名 IMAGE_BITMAP,//読み込むものの指定(ここでは BMP をロード) 0, //幅 0, //高さ LR_CREATEDIBSECTION | LR_LOADFROMFILE//ロードのオプション); hinst, if( hbmp==NULL ){ // 画像読み込みエラー ","Load_Bmp()",MB_OK); MessageBox(NULL," return false; } work_DC = CreateCompatibleDC( hdc ); SelectObject( work_DC, hbmp ); 転送先のDC 0, //転送先座標 x 0, //転送先座標 y 640, //転送するエリアの幅 480, //転送するエリアの高さ work_DC,//転送元の DC 0, //転送元の座標 x 0, //転送元の座標 y SRCCOPY//転送オプション:そのまま転送); BitBlt(hdc, // ReleaseDC( hwnd,work_DC ); DeleteObject( hbmp ); return true; } まず LoadImage であるが、この API はアイコンやカーソルなどの読み込みも可能な読 み込み関数となっています。今回は外部ファイルからBMPをDIB形式で読んで、その 読み込んだデータのハンドルを hbmp に取得しています。 次に work_DC = CreateCompatibleDC( hdc ) で、作業用に臨時のデバイスコンテキス トを生成する。DC とは、画像データにアクセスするときに必要な各種情報を盛り込んだオ ブジェクトであり、ここでは、プログラム起動時に開いたウィンドウと同じ属性のDCを 作成している。また、SelectObject 関数で以降の画面操作対象として選択しておくことによ り、画像データにをウィンドウに転送する準備が整う。 転送そのものは、BitBlt() によって行い、転送が終ったら、必要なくなった作業用DC を ReleaseDC で消去し、さらに読み込んだ画像データも DeleteObject() で消去しておき、 最終的には、転送先のウィンドウ上にのみ、画像データが残る事となる。 今回は再描画のために画面を二枚用意して 絶えず裏画面から表画面に画像を転送していく形式により、再描画をしていく。 同時に、文字と画像は一番上のサーフェスではなくここの裏のサーフェスに表示していき 表のサーフェスに随時転送していく形となっている。 つまりは、ここがいわば操作のメインとなるサーフェスである。 では、それに関するを列挙し、紹介していく。 まず、裏画面管理のためのグローバル変数を宣言する。 HDC Back_DC; //デバイスコンテキスト(裏画面用) HBITMAP H_Back_Bitmap; // バック画面本体のハンドル ==ここからが本体== void I_Back_Surface():裏画面の初期化関数 { HDC work_hdc; バック画面の初期化 // work_hdc=GetDC(hwnd); 作業用の DC // 主(表)画面の DC の内容を取得 // Back_DC=CreateCompatibleDC(work_hdc); 同じ設定でバック画面用の DC を生成 // H_Back_Bitmap=CreateCompatibleBitmap(work_hdc,600,400); 主(表)画面と同じ属性で画面生成 // SelectObject(Back_DC,H_Back_Bitmap); //DC と画面本体を関連付ける ReleaseDC(hwnd,work_hdc); 作業用 DC を開放 // } この関数内では、まず表画面の DC の内容を GetDC で取得し、それと同じ内容の裏画面 用 DC を CreateCompatibleDC で生成している。 HDC は、ここでは CreateCompatibleDC の戻り値として返ってきた値をグローバル変 数 Back_DC で受けている。 次に、その DC の内容に沿って実際の画面を CreateCompatibleBitmap で生成し、返さ れるハンドルを H_Back_Bitmap で受けておく。ここで、DC と画面本体を関連付けるため に SelectObject でその関係を結び付けておく。 ここまでで裏画面が確保でき、最後に、ワーク用 DC を消去すれば終わりとなる。 この裏画面に対してなにか操作をするときは、ほとんどの場合デバイスコンテキストの ハンドルを指定するだけで間に合う。 ここまでがグラフィックと文字の画面操作の根幹部分となるところである。 次にこのプログラムの本体ともいえる文字読み取り関数について説明していく。 まずその前に、このテキストエンジン部分で多く遣うことになる変数を宣言しておくこ とにする。役割は以下の通りである。 unsigned char unsigned char int スクリプトを読み込むエリア *TEXT; //Text pointer:テキスト読み込み位置判定 Font_Size; //フォントサイズ TEXT_BUF[SIZE_OF_TEXT_BUF];// RECT TEXT_AREA; int TEXT_WAIT; int TEXT_X; int TEXT_Y; int TEXT_X_PITCH; int TEXT_Y_PITCH; DWORD TEXT_TIMER; テキストを展開する画面領域 //1文字表示のウェイト //現在の表示位置X //現在の表示位置Y //文字送りピッチX //文字送りピッチY //タイマー // この中で最も重要なのはグローバル変数として宣言されている*TEXT である。 このポインタがテキスト処理の中で重要な役割を占め、1 文字表示するごとに ポイントを進めていき、スクリプトの中を進んでいくという形になる。 さて、変数の宣言が終わったところでそれぞれの関数の説明に入る。 T_Default_params:テキスト読み込みのデフォルトパラメータの設定 void T_Default_params() { int i; 1文字表示のウェイト TEXT_WAIT = 60; // Font_Size = 17; //Font size テキスト表示エリア(左端) TEXT_AREA.top = 20; //テキスト表示エリア(上限) TEXT_AREA.right= 600-20; //テキスト表示エリア(右端) TEXT_AREA.bottom= 400-20; //テキスト表示エリア(下限) TEXT_X=TEXT_AREA.left; //文字位置カウンタを初期化(横) TEXT_Y=TEXT_AREA.top; //文字位置カウンタを初期化(縦) TEXT_X_PITCH = Font_Size/2; //文字送りピッチ(縦) TEXT_Y_PITCH = Font_Size*130/100; //文字送りピッチ(横) //イベントフラグはゼロクリア TEXT_AREA.left= 20; // for( i=0;i<EVENT_FLAG_MAX;i++){ Mode_steatus.event_flag[i]=0; } } ここでは関数の名前どおり、 単にフォントのサイズやテキスト展開エリア、文字送りピッチ等の決定だけとなっている。 :初期化操作の親関数 Void I_T_engine { char str[256]; テキストポインタ TEXT をバッファ先頭にセット //初期設定のスクリプトファイルを読む TEXT=TEXT_BUF; // strcpy( str,TEXT_PATH ); strcat( str,"text.txt"); HLS_bload(str,(char *)TEXT_BUF); T_Default_params(); HLS_timer_start(&TEXT_TIMER); デフォルトパラメータの設定 //タイマースタート // } スクリプトの読み込み位置や、ファイル等その全てを初期化している。 この関数の中ではタイマー関数を使うことにより、GetTickCount から現在時刻を取得して その取得から何ミリ秒経過したという形で次の文字を表示させ 1 文字ずつ表示させるという効果をもたせている。 簡単にこのタイマー関数を紹介しておく int HLS_timer_start( DWORD *timer ) { *timer = GetTickCount(); return 0; } int HLS_timer_check( DWORD timer , DWORD wait_time ) { if( timer+wait_time <= GetTickCount() )return true; return false; } ではタイマーを初期化してカウント開始する。 DWORD *timer はカウント開始時刻を格納する変数。 関数本体内で保持しないのは、複数タイマーの同時稼動を考慮した為。 HLS_timer_check では HLS_timer_start()でカウント開始してから指定時間 経過したかどうかを調べる。 戻り値はそれぞれ、true は指定時間経過した、false=指定時間経過していない というふうになる。 HLS_timer_start また、このテキストエンジンの初期化操作関数のほかに マウス関係やグラフィック関係(前述の I_Back_Surface) の格納も全て初期化させたほうが便利なため 後々これらを一つにまとめてパッケージングした初期化関数を作る。 (Winmain 関数のところで前述した I_game 関数がそれである。) :文字表示位置のインクリメント void Inc_text_pos(int inc) //int inc:何バイトインクするか→1もしくは 2 Inc_text_pos { 最終行、なおかつ右端-1文字目の場合は「改ページ」扱いにしてしまう。 // if( (TEXT_Y+TEXT_Y_PITCH)>=(TEXT_AREA.bottom-TEXT_Y_PITCH) && (TEXT_X+TEXT_X_PITCH*inc)>=(TEXT_AREA.right-TEXT_X_PITCH*4) ){ カーソル位置が文字座標を参照しているのでインクリメント // TEXT_X += TEXT_X_PITCH*inc; Com_n_npage2(); // return; } 通常の文字表示場合 // if((TEXT_X+TEXT_X_PITCH*inc) < (TEXT_AREA.right-TEXT_X_PITCH*2) ){ 行の端に達していない場合はX座標を1文字分増やすだけ // TEXT_X += TEXT_X_PITCH*inc; }else{ 行端では改行処理 // TEXT_X = TEXT_AREA.left; TEXT_Y += TEXT_Y_PITCH; } } ここでは文字読み込み関数の説明の頭で宣言した変数を用いて テキストポインタがページの端まで達していないか、達していた場合は 改ぺージコマンドを呼び出して、改ページ処理を行う。 テキストポイントが通常の場合は X 座標を 1 文字分増やすだけで また、行が右端に達していた場合は、X座標を初期値にもどし Y座標を定められたピッチ分だけ増やしてやる。 :テキストエンジン本体 T_engine int T_engine() { int color1=RGB(255,255,255); 文字色 // int color2=RGB( 10, 10, 10); 影色 // 指定時間経過していない場合は Main_loop に戻る // if( HLS_timer_check(TEXT_TIMER,TEXT_WAIT)==false )return 0; タイマー再スタート HLS_timer_start(&TEXT_TIMER); // 1文字を表示する // if( (unsigned char)(*TEXT)<(unsigned char)0x80 ){ コントロール文字かどうかを判断、該当しなければ出力 // if( Check_con_chr()==false ){ ChrPut3D(Back_DC,TEXT_X,TEXT_Y,Font_Size,color1,color2,(char)TEXT,1); 文字表示位置のインクリメント=1byte Inc_text_pos(1); // } テキストポインタを1バイト進める TEXT++; }else{ // 2バイト文字の場合は2バイトずつ出力 // ChrPut3D(Back_DC,TEXT_X,TEXT_Y,Font_Size,color1,color2,(char*)TEXT,2); 文字表示位置のインクリメント=2byte TEXT+=2; //テキストポインタを2バイト進める Inc_text_pos(2); // } return 0; } 関数の最初で経過時間をチェックし、初期化のときに定めた経過時間が過ぎるまで、関 数の中身は実行されずに入り口段階で Main_loop() に戻される仕組みになってる。ここが 文字表示のウェイト部分に相当する。 指定時間が経過すると、タイマーの部が通過可能になり、関数の内部に入ると直ぐに次 のタイマーショットを放つようになっている。 また、ここではテキストポインタ TEXT の指す1バイトが 0x80 以上かどうかを調べて おり、2バイト文字か1バイト文字かの簡易判定し、それに応じて2バイト文字用、1バ イト文字用の表示を切り替えている。そうやって文字を表示した後に上で紹介した inc_text_pos で初期設定のピッチ分だけ現在座標を移動させてやって、次の表示の対策をと っている。 また、途中の Check_Con_Chr 関数で前述したように「#」等の制御文字が現れていない か(要するにコマンド文かそのまま一般の文章か)を判断しており、「#」が来ている場合 にはすかさずコマンドコントロール部分の関数に飛ばしてやるようにしている。 Check_con_chr 関数: int Check_con_chr() //*TEXT のポイントする1バイト文字をチェックし、 制御文字であれば処理して true を返し、それ以外は false を返す // { int i,label_flag; switch( *TEXT ){ case '#': コマンド処理部を呼ぶ Command_call(); // TEXT++; return true; case 0x09: //TAB case 0x0a: //LF case 0x0d: //CR case '{' : case '}' : return true; case '/' : jump_to_end_kakko(); そのまま戻る // return true; case ';': Com_linefeed(); //[;] 改行 return true; case '*': ラベルに遭遇したら読み捨てる (ラベル名を表示してはいけないので) // label_flag=OFF; i=0; //char str[256]; //for DEBUG do{ //str[i]=*(TEXT+i); if(*(TEXT+i)==':'){ label_flag=ON; i++; break; } if(*(TEXT+i)==' に出会わないままスペースに達した→ラベルではない ')break; //[:] i++; }while(i<=32); if( label_flag==ON ){ ラベル長だけ読み捨て TEXT+=i;// //str[i]=0; //msg(str,"aaa"); } return true; } return false; 制御文字に該当しなかった → false を返す // } ここでは、上でも書いたように、コマンドかどうかの判定を行っているわけですが、そ の内容としては、制御文字を switch 文で振り分け、「#」 を見つけた場合にはさらにコマ ンド処理部を呼ぶようにし、同時に TAB、などについてはそのまま無視して通過するよう にしてある。こうすることにより、テキストスクリプ内の TAB や改行が無効となり、いわ ゆるフリーフォーマット記述が出来るようになっている。つまりはスクリプトを文字、文 章として見やすいように TAB やスペースで行換えしていても、実際の画面表示には影響し なくなる。 それ以外では、選択肢関係のコマンド(選択肢表記やラベルジャンプ)、改行コマンドの 判定である「/」、「;」についても、その文字を読み込んだ時点でそれぞれのコマンド処理に 飛ぶようになっている。 「*」に関してだけはラベル名が文章と表示されてはいけないので、「*」を見つけたとこ ろで、空白を挟まずに32文字以内に「:」があればラベルとみなしてテキストポインタを 飛ばしている。 下に Chech_con_chr 関数内で紹介されたコマンド切り出し関数を紹介しておく。 Command_call 関数: void Command_call() 「 」を見つけたところでこの関数を呼ぶと、「#」に //続くコマンド名を解析してその処理関数を呼びます // # { char com_name[256]; int i=0; int flag=OFF; TEXT++; ここに飛んできた時点でコマンドとわかっているので#を飛ばす //コマンド文字列の切り出し // while( *TEXT>0x20 ){ com_name[i] = *TEXT; TEXT++; i++; } TEXT--; これは NULL 文字 //コマンド名の判定を行い、処理関数を呼び出す com_name[i]=0; // if( strcmp(com_name,"wait" 遅延 )==0 ){ Com_wait();flag=ON;} // if( strcmp(com_name,"wait" ブリンク )==0 ){ Com_cursor_blink();flag=ON;} )==0 ){ Com_cursor_blink();flag=ON;} // if( strcmp(com_name,"w" ブリンク(簡易表記版) // if( strcmp(com_name,"end" )==0 ){ Com_end();flag=ON;} )==0 ){ Com_npage(); flag=ON;} if( strcmp(com_name,"back_load" )==0 ){ Com_g_load(); flag=ON;} 終了 // if( strcmp(com_name,"new" 改ページ // //BMP のロード if( strcmp(com_name,"back_copy" )==0 ){ Com_g_copy(); //BMP のコピー flag=ON;} if( strcmp(com_name,"back_change")==0 ){ Com_g_change(); flag=ON;} 画面切替効果 // if( strcmp(com_name,"flag_on" )==0 ){ イベントフラグの ON Com_flag_on(); flag=ON;} // if( strcmp(com_name,"flag_off")==0 ){ イベントフラグの OFF Com_flag_off();flag=ON;} // if( strcmp(com_name,"disp_flag")==0 ){ イベントフラグの表示 Com_disp_flag(); flag=ON;} // if( strcmp(com_name,"if_flag" )==0 ){ フラグによる分岐 Com_if_flag();flag=ON;} // if( strcmp(com_name,"jump" ラベルジャンプ )==0 ){ Com_jump();flag=ON;} // if( strcmp(com_name,"file_change")==0 ){Com_file_change(); flag=ON;} ファイル間ジャンプ // if( strcmp(com_name,"select" )==0 ){ 選択肢 Com_Select(); flag=ON;} // if( strcmp(com_name,"play_wav")==0 ){ //WAVE 開始 if( strcmp(com_name,"stop_wav")==0 ){ //WAVE 停止 Com_play_wav(); flag=ON;} Com_stop_wav(); flag=ON;} if( strcmp(com_name,"play_midi")==0 ){ //MIDI 開始 if( strcmp(com_name,"stop_midi")==0 ){ //MIDI 停止 Com_play_midi();flag=ON;} Com_stop_midi();flag=ON;} この部分にコマンドを追加していくことで処理が増えていく //発見されたコマンド名が有効なコマンドだったかどうか確認する // if( flag == OFF ){ char str[256]; 警告:無効なコマンド [#%s] が見つかりました",com_name); printf( str," MessageBox(NULL,str,"Command_call()",MB_OK); } } ここでは「#」以降のコマンド名を切り出して strcmp() で比較するという簡易形式で、 ヒットするものが出たら該当するコマンド処理関数を呼んでいる。strcmp() での評価では、 コマンド数が増えた場合、処理が遅くなってしまう可能性があるが、ノベルゲームという 性質上、処理がそれほど重くなることもないと判断し、この評価形式で行くことにした。 尚、高速化したいのであれば、コマンド名は文字列で持たずに switch 文等でふりわける ちいう手段をとればよい。 また、プログラムがわかる人であれば、この関数の If 文の部分に、新たに比較評価して やるコマンドを追加してやれば、それだけで新しい機能の追加が可能となる。結果として ある程度の拡張性を持たせることができる。 さて、これまででシステムの根幹となる部分をしてきた。 この基本部分に対して、ここからはそれぞれのコマンド処理関係を行う関数を順番に列 挙し、紹介していこうと思う。 ■改行 int :改行関数 Com_lfeed() { if( TEXT_Y+TEXT_Y_PITCH > TEXT_AREA.bottom-TEXT_Y_PITCH ){ Com_npage2(); return 0; } 改行の定義としては表示位置をX座標は行先頭に Y 座標を1段下にすること // TEXT_X = TEXT_AREA.left; TEXT_Y += TEXT_Y_PITCH; return 0; } 改行というのは、文字の表示位置を変える操作で、テキスト読み取り関数でで1文字表 示毎に文字位置をインクリメントし、その値をグローバル変数 TEXT_X、TEXT_Y で保持 している。今回は、これを単純に変更してやるだけでできる。X座標葉は初期設定の行頭 に、Yはピッチ分を加えてやって1行下に変更しているだけである。 もし、右下にポイントが到達していた場合は、後述する自動ページ変更関数に処理を受 け渡して、自動処理するようになっている。 改行関数をコマンドで説明すると以下のようになる。 書式:; 内容:改行処理を行う ■ 終了 int { :終了関数 Com_end() テキスト表示を停止する // Mode_steatus.flag_text = OFF; 終了のループフラグを立てる // Mode_steatus.flag_end = ON; return 0; } コマンド初期化関数で行う内容は、フラグ操作のみで テキスト表示を止めて、終了処理のフラグを立てる。 :終了維持関数 int Com_end _task() { return 0; } ただ待機するだけ // ここではキー入力、マウス入力があれば即ウィンドウを閉じて終了するために 何もせずひたすら待つだけになる。 int Com_end _end() { 演奏などしていたらここで停止する //今は何もない //MIDI 終了 // System_exit(); return 0; } この関数の中でシステム関係を全て終了させる関数 System_exit を通過して、MIDI や WAVE 等が再生していた場合 それを全て閉じて完全に終了させる。 :システム終了関数 void System_exit() { //MIDI が鳴っていたら停止 Com_stop_midi(); //WAV が鳴っていたら停止 Com_stop_wav(); //Windows システムに終了メッセージを投げる PostQuitMessage(0); } ここでは、画面、画像関係のリソース返却は Windows のシステムが やってくれるのでここでは自分達で指定してやらないとリソースを開放しない 音源関係のリソースのみを指定して開放させてやる。 書式:#end 内容:終了処理を行う ■カーソルブリンク カーソルをブリンクさせるために、25 ピクセルの正方形をみつ並べる。 3つある理由としては左から順にカーソル本体、マスク、バッファ用エリアとなる。 右側のバッファ用は、カーソル表示で隠れてしまう部分の背景を一時的に待避するところ となる。 デバイスコンテキスト(パーツ類) H_Parts_Bitmap; //パーツ画面本体のハンドル HDC Parts_DC; HBITMAP // ):カーソルブリンクサーフェス初期化関数 void I_Parts_Surface( { HDC work_hdc; 作業用の DC // ■ バック画面の初期化 // work_hdc=GetDC(hwnd); 主(表)画面の DC の内容を取得 // Parts_DC=CreateCompatibleDC(work_hdc); 同じ設定でバック画面用の DC を生成 // hParts_Bitmap=CreateCompatibleBitmap(work_hdc,75,25); 表画面と同じ属性で画面生成 // SelectObject(Parts_DC,hParts_Bitmap); //DC と画面本体を関連付ける Load_Bmp( Parts_DC,".¥¥back¥¥Parts.bmp"); パーツ用 BMP を読み込んでおく // ReleaseDC(hwnd,work_hdc); } 作業用 DC を開放 // カーソルBMPを保持するサーフェイスを生成するため、まずはグローバル変数として DC のハンドルと BITMAP のハンドルを宣言する。 初期化については、根幹部分の時に説明したバックサーフェイスと同じ要領となる。 サーフェイスを作成したら、ここで忘れないように直ぐにBMPを読み込んでおくように しておく。今回はカーソルBMPに合せて75 × 25ピクセル分のサーフェイスを CreateCompatibleBitmap の引数で指定する。ここの数値を変えることにより、後からカ ーソルパーツを変更することになっても、それによって対応できるようになる。 これでプログラムが起動するのと同時にパーツ用サーフェイスにカーソル画像がロード されるようになる。 DWORD c_timer; DWORD c_wait = 200; int { タイマー用変数 //何ミリ秒待つのか記憶しておく変数 // :カーソルブリンク関数 Com_c_blink() テキスト表示を停止する // Mode_steatus.flag_text = OFF; カーソルブリンク用フラグを立てる // Mode_steatus.flag_cursor_blink = ON; カーソルによって隠れる背景領域を待避しておく // BitBlt(Parts_DC,50,0,25,25,Back_DC,TEXT_X,TEXT_Y,SRCCOPY); タイマーショット // HLS_timer_start( &cursor_timer ); 1発目のカーソル表示 // BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,25,0,SRCAND ); //AND BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC, 0,0,SRCPAINT); //OR return 0; } ブリンクの動作は遅延処理に似ているのでタイマー変数として c_timer を宣言する。 処理はまずフラグ操作からで、テキスト表示フラグ Mode_steatus.flag_text を OFF にし て、カーソルブリンク用のフラグ Mode_steatus.flag_cursor_blink を ON する。 これによって Main_loop 内ではこのフラグを参照して、テキスト表示を止めた状態でカ ーソルブリンクのタスク処理部を連続コールするようになる。 次に、これから行うカーソル表示で上書きされてしまう部分の画像をバッファ領域に待 避させ、タイマーを動かす。そして、1つ目のカーソルを表示する。 また、最後の 2 回の BitBlt()は最初の方がマスク処理、後の方がパターン本体の転送にあ たる。 :ブリンク維持関数 int Com_c _blink_task() { static int flag=OFF; 指定時間経過てない場合は Main_loop に戻る // if( HLS_timer_check( c_timer, c_wait )==false )return 0; HLS_timer_start( &cursor_timer ); 次回に向けてのタイマーショット // switch( flag ){ case カーソル表示 ON: // BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,25,0,SRCAND ); //AND BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC, 0,0,SRCPAINT); //OR flag = OFF; break; case OFF: 次は OFF // カーソル非表示 // BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,50,0,SRCCOPY); 背景書き戻し // flag = ON; 次は ON // break; } return 0; } 関数の最初でタイマーチェックを行い、指定時間に達していなければ何もしないでその まま Main_Loop 関数に戻る。指定時間に達した後にまた次のタイマーショットを放つこと により、カーソル表示のオンオフを切り替えることによってタイマー監視を繰り返すよう になっている。 :カーソルブリンク停止関数 int Com_c_blink_end() { テキスト表示再開 // Mode_steatus.flag_text = ON; カーソルブリンクフラグをオフに // Mode_steatus.flag_cursor_blink = OFF; カーソルの背景部分を描き戻して終了する // BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,50,0,SRCCOPY); 背景書き戻し // return 0; } ここでは、テキスト表示を再開し、カーソルブリンクのフラグをオフに戻すことにより、 フラグを初期状態に戻して、カーソルの背景部分を描き戻す。 ブリンク関数をコマンドで説明すると以下のようになる。 書式:#w もしくは #wait 内容:キーもしくはマウスの入力があるまで、待機状態にする。 ■ページ変更 :ページ変更関数(コマンドを認識した場合) int Com_npage() { 背景データでバックサーフェスを書き換える // BitBlt(Back_DC,0,0,640,480,BG_DC,0,0,SRCCOPY); 文字表示位置をテキストエリアの左肩にリセットする // TEXT_X = TEXT_AREA.left; TEXT_Y = TEXT_AREA.top; return 0; } 背景画像をバックサーフェスに転送し、今まで表示していた文字を消す。 その後、文字表示位置を初期設定の左上の位置に戻すようにしている。 今回のページ変更は、背景用画像保持サーフェスを用意しておき それを、バックサーフェスに転送して上書きしている。 その保持用のサーフェスの初期化関数は以下のようになる。 void I_BG_Surface() :画像保持サーフェス初期化関数 { HDC work_hdc; バック画面の初期化 // work_hdc=GetDC(hwnd); 作業用の DC // 主(表)画面の DC の内容を取得 // BG_DC=CreateCompatibleDC(work_hdc); 同じ設定でバック画面用の DC を生成 // H_BG_Bitmap=CreateCompatibleBitmap(work_hdc,600,400); 主(表)画面と同じ属性で画面生成 // SelectObject(BG_DC,H_BG_Bitmap); //DC と画面本体を関連付ける Load_Bmp( BG_DC,".¥¥back¥¥basic.bmp"); デフォルト背景 BMP の読み込み // ReleaseDC(hwnd,work_hdc); 作業用 DC を開放 // } 内容はテキスト・グラフィックの時に説明した バックサーフェスの初期化と同じで 最後にデフォルトの背景 BMP を読み込むようにしてある。 また、ページ変更はコマンドで指定するのみではなく そのページの最後まで到達した場合に自動でも行われないといけないため 以下のような、自動ページ変更関数も作る必要がある。 :自動ページ変更関数 int Com_npage2() { テキスト表示を停止する // Mode_steatus.flag_text = OFF; 改ページ用フラグを立てる // Mode_steatus.flag_npage2 = ON; カーソルによって隠れる背景領域を待避しておく // BitBlt(Parts_DC,50,0,25,25,Back_DC,TEXT_X,TEXT_Y,SRCCOPY); タイマーショット // HLS_timer_start( &cursor_timer ); カーソルブリンクのタイマー変数をとってくる //1発目のカーソル表示 // BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,25,0,SRCAND ); //AND BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC, 0,0,SRCPAINT); //OR return 0; } 処理はカーソルのブリンクのものとほとんどかわらず、フラグ操作から始まり、テキス ト表示フラグ Mode_steatus.flag_text を OFF にして、という形をとる。 以下の維持処理関数、終了関数も同じ内容となる。 :自動ページ変更維持関数 int Com_npage2_task() { static int flag=OFF; 指定時間経過したかどうか、していなければ Main_loop に戻る // if( HLS_timer_check( cursor_timer, cursor_wait )==false )return 0; HLS_timer_start( &cursor_timer ); switch( flag ){ 次回に向けてのタイマーショット // case カーソル表示 ON: // BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,25,0,SRCAND ); //AND BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC, 0,0,SRCPAINT); //OR flag = OFF; break; case OFF: 次回は OFF // カーソル非表示 // BitBlt(Back_DC,TEXT_X,TEXT_Y,25,25,Parts_DC,50,0,SRCCOPY); 背景書き戻し // flag = ON; 次回は ON // break; } return 0; } ここも、ブリンクと同じで、関数の最初でタイマーチェックを行い、指定時間に達して いなければ何もしないでそのまま Main_Loop 関数に戻って、指定時間に達した後にまた次 のタイマーショットを放つ、という形になっている。 :自動ページ変更終了関数 int Com_npage2_end() { テキスト表示フラグを戻す // Mode_steatus.flag_text = ON; 改ページ用フラグを解除 // Mode_steatus.flag_npage2 = OFF; 背景データでバックサーフェイスを書き換える // BitBlt(Back_DC,0,0,640,480,BG_DC,0,0,SRCCOPY); 文字表示位置をテキストエリアの左肩にリセットする // TEXT_X = TEXT_AREA.left; TEXT_Y = TEXT_AREA.top; return 0; } ここでは、テキスト表示を再開し ページ変更のフラグをオフに戻すことにより、フラグを初期状態に戻す。 そこから、テキストのポジションを初期値に戻す。 ここも最後が違うだけで基本はブリンクと同じである。 ページ変更関数をコマンドで説明すると以下のようになる。 書式:#new 内容:ページ変更処理を行う ■ 待機 DWORD w_timer; DWORD w_wait; タイマー用変数 //何ミリ秒待つのか記憶しておく変数 // :待機処理関数 //遅延処理のファーストショット。遅延タスクを //回すための準備をしておしまい int Com_wait() { 待ち時間を解析する // TEXT++; wait_wait = search_10(); テキスト中の 10 進数を解析する関数 // テキスト表示は停止 Mode_steatus.flag_text = OFF; // Mode_steatus.flag_wait = ON; 遅延フラグをオンに、Main_loop でこのフラグを参照 // HLS_timer_start(&wait_timer); 遅延のためのタイマー // return 0; } まず、グローバル変数でタイマー関係の関数を宣言する。 処理の内容は、まずはテキスト上のパラメータを解析し、解析した結果は変数 w_wait に 格納する。そして、テキスト表示フラグを停止して、Main_loop で参照しているフラグ flag_wait をオンにすることにより Main_loop から連続コールされるようにする。 最後に、遅延処理を行うためのタイマーを使用する。 タイマー処理は、前述したタイマー関数を用い、GetTickCount(でシステム起動からの経 過時間を取得して、後にふたたび GetTickCount で時間を取得して差を調べるという方法 を使用している。 :待機処理維持関数 int Com_wait_task() 遅延処理のタスク部分。Main_loop() から延々と呼ばれ続ける // { 指定時間経過していなければ何もせずに戻る(=待ち続ける) // if( HLS_timer_check( wait_timer,wait_wait)==false )return 0; 時間が経過したら/遅延フラグを解除する //これで Main_loop 側で文字表示が可能になる // Mode_steatus.flag_wait = OFF; Mode_steatus.flag_text = ON; return 0; } ここでは指定時間経過していなければ何もせず待機し 指定時間経過したらフラグを解除という動作だけを行う。 ・待機時間を計るための 10 進数解析の関数 int search_10:10 進数テキストの解析 { int val=0; int flag=0; int fugo=1; // char int 符号です(笑) buf[40],str[256]; i; while( *TEXT<=' ' ) TEXT++; 空白を読み飛ばす // do{ switch( *TEXT ){ case '0': val+=0; break; case '1': val+=1; break; case '2': val+=2; break; case '3': val+=3; break; case '4': val+=4; break; case '5': val+=5; break; case '6': val+=6; break; case '7': val+=7; break; case '8': val+=8; break; case '9': val+=9; break; default: //10 進数では無かった場合 for(i=0;i<32;i++){ buf[i]=*(TEXT+i); } buf[i]=0; : 進数ではありません",buf); msg(str,"エラー"); sprintf(str,"[%s] 10 PostMessage(hwnd,WM_CLOSE,0,0); break; } TEXT++; if( *TEXT>' ' && *TEXT!=',' ){ flag=0; val*=10; }else{ flag=1; } }while( flag==0 ); val *= fugo; return val; } テキストエンジンでは数字を文字として認識しているため 一度、その文字を数字形式の val に変換してやる必要がある。 そのために上記のような操作を行う。 待機関数をコマンドで説明すると以下のようになる。 書式:#delay n 内容:テキストの表示を n ミリ秒停止させる。 ■画像読み込み・転送・コピー :画像読み込み実行関数 int Com_g_load() { char vram[32]; char f_name[256]; 画面指定の解釈 // TEXT++; 画面指定切り出し strcpy( vram, Search_TextStr() ); // while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; ファイル名の解釈 // strcpy( f_name, G_PATH ); アドレス(ヘッダーファイルにて指定) // strcat( f_name, Search_TextStr() ); Load_Bmp( Get_game_DC(vram), f_name ); return 0; } この関数が呼びだされた時点でテキストポインタ *TEXT はテキストスクリプトの text.txt 上でコマンド名#g_load の最後の部分を指した状態になっている。これはコマンド 名解釈が終ってこの関数に飛んでいる時点で判断できる。よって、TEXT++して1バイト分 ポインタを進め、コマンド名に続く転送先のを拾う準備をする。 パラメータを拾う関数は今回は文字列を切り出す Search_TextStr を呼ぶ。これを用いて まず画面指定文字列を配列 vram に格納し、文字列解析後はポインタ *TEXT は画面指定 文字列の最後をポイントしているので、次のパラメータの先頭までポインタを進める。 また、パラメータとパラメータの間にはスペース等しか存在しないので、これらが続く 限りポインタを進めていって、次にファイル名を読み込む準備をする。 グラフィックデータはフォルダ back の下に置くように変更しているので、配列 f_name にまずパス名(ヘッダーファイルで定義)をコピーし、その後に解析したファイル名を足 して、BMP 読み込み関数 Load_Bmp() を呼んでいる。 :転送先サーフェイス判別関数 //サーフェイスに与えられたシンボル文字列に応じて //DC のハンドルを返す HDC Get_game_DC( char *str ) { char buf[256]; スクリプトで使うサーフェイス名は以下で指定 // if( strcmp( "BS" , str )==0 ) return Back_DC; if( strcmp( "BG" , str )==0 ) return BG_DC; バックサーフェイス //背景画面 // if( strcmp( "PT" , str )==0 ) return Parts_DC; // パーツ画面 認識できないシンボル [%s] があります¥n" "強制終了させます",str ); sprintf( buf, " msg( buf,"Get_game_DC()"); System_exit(); return Back_DC; // コンパイルエラー防止 } この関数は、スクリプト上からパラメータ指定で読み込み画面を選択できるようにして あるので、その転送先の画面判別をするための関数。 内容としては文字列比較をしてそれに対応した DC ハンドルを返しているのだが、この 部分を変更すれば テキストスクリプト上で使用できるシンボル名を好きな名前に変更で きるようになっている。 :サーフェイス間の転送実行関数 int Com_g_copy() { char dst_vram[256],src_vram[256]; int dx,dy,sx,sy,width,hight; TEXT++; コピー先画面 // strcpy( dst_vram, Search_TextStr() ); while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //dx dx = search_10(); while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //dy dy = search_10(); while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; コピー元画面 // strcpy( src_vram, Search_TextStr() ); while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //sx sx = search_10(); while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //sy sy = search_10(); while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //width width = search_10(); while( *TEXT==',' || *TEXT==' ' || *TEXT==0x0a )TEXT++; //hight hight = search_10(); ↓パラーメータをそのまま転送するだけ // BitBlt( Get_game_DC(dst_vram), dx, dy, width, hight, Get_game_DC(src_vram), sx, sy, SRCCOPY ); この関数自体は単純に与えられたパラメータを Bliblt で転送しているだけとなっている。 簡単に説明すると与えられた数字情報や文字列情報を Search_TextStr と search_10()でそ れぞれ読み込んでいる。 またパラメータ間にある while 文はパラメータを区切るカンマやスペース等を読み飛ば すためのものとなっている。 :文字列認識関数 char *Search_TextStr() { static char str[512]; int counter,flag; 空白(スペースと TAB)を飛ばす // while( *TEXT==' ' || *TEXT==0x9 )TEXT++; テキストをコピー :終了条件:「,」or「 」or「CR」or「TAB」 // counter=0; flag=0; while(counter<254 && flag==0){ switch( (unsigned char)*TEXT ){ case 0x0d: flag=1; break; 行末 //[CR]= case 0x0a: flag=1; break; case ' ': flag=1; break; 行末 //スペース case 0x09: flag=1; break; //[TAB] case ',': flag=1; break; //[,] //[LF]= } if(flag==0){ str[counter]=*TEXT; counter++; TEXT++; } } str[counter]=0; return &str[0]; } ファイル名認識のための関数。 最初の while 文でファイル名までのスペースを全部読み飛ばす。 また、次に行末やスペースや TAB が入った場合 それはファイル名・サーフェイス名の終了であるので 次の while 文ではそれらが来たときには読み取りを終了するようになっている。 この画像一連の関数をコマンドで使うときは以下のように記述する 書式:#back_load 転送先サーフェイス名 ファイル名 内容: 指定サーフェイスに指定ファイル名の BMP を読み込む サーフェイス名:バックサーフェイス=BS 背景画面=BG パーツ画面=PT ファイル名 :拡張子まで含めたファイル名 書式:#back_copy コピー先画面,コピー先 X 座標、コピー先 y 座標 コピー元画面,コピー元 X 座標,コピー先y座標,幅,高さ 内容:コピー元サーフェイスの座標などのデータを 転送先のサーフェイスにコピーする。 これを利用することにより、背景だけでなく キャラクターを画面に表示することも可能になる。 書式:#back_change 画面切り替え効果 内容:指定された形式の画面切り替え効果をもたらす STRETCH X(Y):横、縦方向への伸縮型 :すだれ型 :拡散型 CURTAIN SPREAD ■ラベルジャンプ int Com_jump():ラベルジャンプ処理関数 { char f_name[256]; char LABEL_1[256],LABEL_2[256]; char *ptr; int 作業用ポインタ cnt=0; //カウンタ(0で初期化しておく) int flag=OFF; // //loop flag パラメータ文字列からジャンプ先のファイル名を切り出す // TEXT++; テキストスクリプトのパス strcpy( f_name,TEXT_PATH ); // strcat( f_name,Search_TextStr() ); while( *TEXT==' ' || *TEXT==',' )TEXT++; 次のパラメータ先頭までポインタを進める //パラメータ文字列から目標となるラベルを切り出す // TEXT++; strcpy( LABEL_1,Search_TextStr() ); ジャンプ先スクリプトファイルを読む // HLS_bload( f_name, (char *)TEXT_BUF ); 作業用ポインタ *ptr をテキストバッファ先頭にフィット // ptr = (char *)TEXT_BUF; 1文字ずつ比較して「*」を探す // do{ if( *ptr=='*' ){ を見つけた //行頭かどうかの判断 //* int j=-1; int flag=OFF; while( *(ptr+j)=='¥x9' || *(ptr+j)=='¥x20' )j--; の前にあるのが前の行の「行末」ならジャンプ先候補 //* switch( *(ptr+j) ){ case 0xd: case 0xa: //CR flag=ON;//LF break; スクリプトの最初の1行の先頭だった場合を考慮 default:// if( TEXT_BUF - (unsigned char*)(ptr+j) > 0 ){ flag=ON; } } ラベルの切り出し // int i=0; do{ LABEL_2[i]=*ptr; ptr++; i++; cnt++; }while( i<32 && *ptr!=' ' && *ptr!='¥xa' && *ptr!='¥xd' && *ptr!='¥x9' ); LABEL_2[i]=0; 行頭フラグが立ち、かつラベルが一致したら脱出 // if( flag==ON && strcmp(LABEL_1,LABEL_2)==0 ){ //msg( LABEL_1,LABEL_2 ); goto OUTLOOP; } }else{ ではない //* ptr++; cnt++; } }while( cnt<SIZE_OF_TEXT_BUF && flag==OFF); char str[256]; ラベル [%s] が発見不可能。強制終了します",LABEL_1); sprintf(str," msg(str,"Com_jump()"); System_exit(); OUTLOOP: TEXT = (unsigned char *)ptr; テキストポインタジャンプ // return 0; } 連続する文字列の中から特定の文字列(ラベル)を抽出する方法として、「先頭文字」と 「終端文字」をそれぞれ「*」と「:」に特定してやる。 サーチの方法としてはスクリプトファイルの先頭から「*」を探してひたすら1バイトず つ比較をしていく。「*」がみつかったら、30文字を限度としてその先に「 : 」がないかど うか探して、「 *xxx: 」 のという形が成立していたらラベルと考え比較を行う。 合致したらテキストポインタを書き換え、合致しなければ次を探しくという形をとりま す。しかし、それだけではジャンプ元のラベルを発見した場合そこをジャンプ先のラベル と勘違いする可能性があるため、ここで【行頭にあるラベルのみがジャンプ先として有効】 とする。この条件を付ければ、ラベルの直前にあるのは1段上の行の改行コードか、タブ またはスペースということになるので区別しやすくなる。 また、行頭チェック部分の if( TEXT_BUF - (unsigned char*)(ptr+j) > 0 )で、前の行末が 見つかれば行頭という認識のさせかたをしているので、スクリプトの一番最初の行にラベ ルがあった場合に拾えなくなるということを回避するためのもの。 さらに、ファイル間ジャンプをさせることもソースの赤い部分をつけたしてやることで 可能となる。 ラベルジャンプをスクリプト内でコマンドとして使うときは以下のように記述する。 書式:#jump *ラベル名 *ラベル名: 「*」で始まり「:」で終る 30 字以内の英数字シンボル 内容:ラベル *ラベル名: をスクリプトの先頭から検索して、 その後にテキストポインタを移動する 書式:#file_change ファイル名,*ラベル名: ファイル名:ジャンプ先のスクリプトファイル名 *ラベル名 :「*」で始まり「:」で終る 30 文字以内の英数字シンボル 内容:ラベル *ラベル名: をスクリプト先頭からサーチして、 その後にテキストポインタを移動する ■ ・ WAVE MIDI ファイルの再生・停止 struct WAVE_steatus { int int char }; 再生中? ON=再生中 OFF=再生していない WAV_play_flag; //WAV:loop 再生かどうかのフラグ WAV_fname[256]; //WAV:再生中のファイル名 WAV_play_stat; //WAV 今後のことも考えて上のような WAVE 関係の構造体を宣言しておく。 : int Com_play_wav() WAVE 再生関数 { char f_name[256]; DWORD flag; TEXT++; switch( search_10() ){ case 0:flag=SND_FILENAME | SND_ASYNC; ↑ファイル名指定+非同期 // break; case 1:flag=SND_FILENAME | SND_ASYNC | SND_LOOP; ↑ファイル名指定+非同期+ループ再生 // break; } while( *TEXT==',' || *TEXT==' ' )TEXT++; strcpy(f_name,WAV_PATH ); 空白 or「,」を飛ばす // ファイル名切出し strcat(f_name,Search_TextStr()); // フラグその他の設定 // WAVE_steatus.WAV_play_stat = ON; WAVE_steatus.WAV_play_flag = flag; 演奏 ON //flag 内容を保存 // strcpy(WAVE_steatus.WAV_fname,f_name); PlaySound( f_name, NULL, flag ); return 0; } これまでと同じ要領で、search_10、Search_TextStr を用いてパラメータ解析を行う。 そこで見つかったパラメータの値に応じて PlaySound に渡すフラグの値を決定し、「一回 の再生」か「ループ再生」かを振り分ける。その後はステータスを保持して PlaySound を 呼んで、再生を行います。 : int Com_stop_wav() WAVE { 停止関数 演奏中でなければそのまま戻る // if(WAVE_steatus.WAV_play_stat==OFF)return 0; PlaySound( NULL, NULL, 0 ); ファイル名に NULL を指定すると演奏が停止する。 //フラグ類の解除 // WAVE_steatus.WAV_play_stat = OFF; WAVE_steatus.WAV_play_flag = 0; strcpy(WAVE_steatus.WAV_fname,""); 演奏 OFF //flag 内容をゼロクリア //ファイル名クリア // return 0; } 演奏の停止はパラメータ解釈がない、ファイル名に NULL を指定して PlaySound を呼 び、演奏を停止にする。また今までどおりフラグを解除する。 続いて、MIDI について説明する。 struct _MIDI_steatus { int char が再生中か ON=再生中 OFF=再生していない MIDI_fname[256];//MIDI:再生中のファイル名 MIDI_play_stat;//MIDI }; こちらもステータス保持用に WAVE と同じように上のような構造体を宣言しておく。 : int Com_play_midi() MIDI 再生関数 { char str[256],f_name[256]; 既になにか演奏中か? // if( MIDI_steatus.MIDI_play_stat == ON ){ msg( " 別の曲を演奏中です。演奏停止させてからコマンド実行してください ","Com_play_midi()"); return 0; } ファイル名解析 // TEXT++; strcpy(f_name,MIDI_PATH ); strcat(f_name,Search_TextStr()); //MIDI ファイルをオープン sprintf( str, "open %s type sequencer alias _MIDI_", f_name ); mciSendString(str,NULL,0,NULL); オープンできたか? // MCIERROR err_code; char err_mess[256]; err_code=mciSendString("capability _MIDI_ can play wait",str,255,NULL); if( strcmp( str,"true" )!=0 ) { // true 以外が返ってきた→エラー文字列の取得 mciGetErrorString( err_code,(LPTSTR)err_mess,255); sprintf( str, "MIDI ファイル %s が開けません",f_name,err_mess ); MessageBox( NULL,str,"Com_play_midi()",MB_OK ); return 0; } 演奏終了通知を指定しながら演奏開始♪ // mciSendString("play _MIDI_ notify",NULL,0,hwnd ); ステータス情報の更新 strcpy( MIDI_steatus.MIDI_fname,f_name ); //ファイル名のキャッシュ MIDI_steatus.MIDI_play_stat = ON; // return 0; } コマンド処理では、まず MIDI_steatus の値を調べ、既になにか演奏中ならメッセージを 表示してそのまま戻るようにしてある。構造体のメンバは VC++では0に初期化されるので、 プログラム起動時は自動的にオフが適用されることになり、最初に演奏開始するときには ここでは引っかかることはない。 次にコマンドパラメータとしてファイル名を解析する。ここはもう今までと同じなので 説明は割愛する。ファイル名を取得した後、それをバッファ str にコマンド文字列展開して mciSendString で送信する。 ここではBGMとしての演奏を想定しているので無条件でループ再生仕様だが、ループ 再生機能が欲しい場合はコマンド文字列から notify を削除すればよい。 オープンコマンドを送信したら、「capability _MIDI_ can play wait」 コマンドでその成 否を確認する。"true"以外の文字列が返ってきた場合は失敗なので、エラーメッセージを表 示して戻る。成功の場合は 「play _MIDI_ notify」を送信し、終了通知付きで演奏を開始 する。 最後に MIDI_steatus に「演奏中」のフラグと「MIDI ファイル名」をセットして戻る。 : int Com_stop_midi() MIDI { 停止関数 何も演奏していなければそのまま戻る // if( MIDI_steatus.MIDI_play_stat != ON )return 0; 停止 mciSendString("stop _MIDI_",NULL,0,NULL);// デバイスのCLOSE // 演奏ステータスのフラグ mciSendString("close _MIDI_",NULL,0,NULL);//MIDI MIDI_steatus.MIDI_play_stat = OFF; を解除 strcpy( MIDI_steatus.MIDI_fname,"" ); 演奏ファイル名をクリアしておく // return 0; } 終了はMCIコマンドそのままでできるが、何も演奏していない場合に演奏を停止させ ようとすると何が起こるかわからないので、まず MIDI_steatus をチェックし、演奏中で ない場合はなにもせずに戻るようにする。 演奏中であることが確認できたら、stop コマンドを発行して演奏を停止し、さらに close コマンドで MIDI デバイスを閉じる。最後に MIDI_steatus の中身をクリアして完了。 : int Com_restart_midi() MIDI ループ再生用 リスタート関数 // { char str[256]; 到達","Com_restart_midi()"); //一旦閉じる //msg(" //DEBUG 用 mciSendString("close _MIDI_",NULL,0,NULL); 再度開く // sprintf( str, "open %s type sequencer alias _MIDI_", MIDI_steatus.MIDI_fname ); mciSendString(str,NULL,0,NULL); オープンは成功したか? // MCIERROR err_code; char err_mess[256]; err_code=mciSendString("capability _MIDI_ can play wait",str,255,NULL); if( strcmp( str,"true" )!=0 ) { // true 以外が返ってきた→エラー文字列の取得 mciGetErrorString( err_code,(LPTSTR)err_mess,255); sprintf( str, "MIDI ファイル %s が開けません", MIDI_steatus.MIDI_fname,err_mess ); MessageBox( NULL,str,"Com_restart_midi()",MB_OK ); return 0; } 演奏開始 // mciSendString("play _MIDI_ notify",NULL,0,hwnd ); return 0; } リスタートコマンド Com_restart_midi の内容は上記の通りで、ここでは MIDI デバイス を一旦クローズして、もう一度再生しなおすという簡単な方法をとっている。 この関数をスクリプト内でコマンドとして使用しようとすると以下のようになる。 書式:#play_wav 再生種類 ファイル名 (#stop_wave ファイル名) 再生種類 :0=1 回のみの再生 1=ループ再生 ファイル名:WAVE ファイル 内容:ファイル名の WAVE ファイルを指定された状態で再生(停止)。 書式:#play_midi (#stop_midi)ファイル名 内容:ファイル名の MIDI ファイルを再生(停止させる) ■ 選択肢 まず、選択という処理に関して簡単に説明しておくと、マウスがクリックされた座標に 応じて、該当するテキストアドレスに制御を飛ばすということがあげられる。 今回はゲームらしさというものをもたせる為に、クリックを待つあいだ単に空ループを 回すのではなく、選択肢の上にマウスカーソルが来たら文字色を反転表示させてみること にする。 そのためには、まず新しい作業用のサーフェイスを2枚用意する。1 つは「選択肢文字列 +背景」を保持するもの、もう1枚は「反転文字+背景」を保持するものとなる。「文字な し背景」を保持するサーフェイスについては、既に BG 面として実装してるのでここでは 新たにつくらない。 では、実際に実行部分を書いていく HDC Select1_DC; //選択肢文字列用 HBITMAP Select1_Bitmap; HDC Select2_DC; HBITMAP Select2_Bitmap; RECT rect1,rect2,rect3;// 反転文字列用 // 選択肢1~3の画面表示領域 #define POSI_COLOR1 RGB(255,255,255) //選択肢の色 #define POSI_COLOR2 RGB(0,0,0) //選択肢の色(影) #define NEGA_COLOR1 RGB(255,50,50) //選択肢の反転色 #define NEGA_COLOR2 RGB(0,0,0) //選択肢の反転色(影) : 枚目のサーフェイス(選択肢展開画面)作成関数 void Create_select_Surface1() 1 { HDC work_hdc; 選択肢展開画面の初期化 作業用の DC // // 主(表)画面の DC の内容を取得 Select1_DC=CreateCompatibleDC(work_hdc); //同じ設定で DC を生成 work_hdc=GetDC(hwnd); // Select1_Bitmap=CreateCompatibleBitmap(work_hdc,640,480); 主(表)画面と同じ属性で画面生成 // と画面本体を関連付ける //作業用 DC を開放 SelectObject(Select1_DC,Select1_Bitmap); //DC ReleaseDC(hwnd,work_hdc); } : 枚目のサーフェイス(選択肢展開画面)削除関数 void Delete_select_Surface1() 1 { DeleteDC(Select1_DC); } この関数はもう説明する必要がないとおもうがグラフィックのところで説明したように 表画面のDCを取得して、同じ設定で選択肢展開画面をつくって(破棄)いる。 :2枚目のサーフェイス(選択肢反転画面)作成関数 void Create_select_Surface2() { HDC work_hdc; 選択肢反転画面の初期化 作業用の DC // // 主(表)画面の DC の内容を取得 Select2_DC=CreateCompatibleDC(work_hdc); //同じ設定で DC を生成 work_hdc=GetDC(hwnd); // Select2_Bitmap=CreateCompatibleBitmap(work_hdc,640,480); 主(表)画面と同じ属性で画面生成 // SelectObject(Select2_DC,Select2_Bitmap); //DC ReleaseDC(hwnd,work_hdc); } と画面本体を関連付ける //作業用 DC を開放 :2枚目のサーフェイス(選択肢反転画面)削除関数 void Delete_select_Surface2() { DeleteDC(Select2_DC);} 今度は、選択肢が反転した時ようの画面をここでつくって(破棄)いる。 int Com_Select() :選択肢関数 { char str_1[256],str_2[256],str_3[256]; 選択肢3行+予備ピッチ分を表示するだけの画面の余裕があるか? // if( TEXT_Y+TEXT_Y_PITCH*5 > TEXT_AREA.bottom ){ 選択肢を表示するだけ余裕がありません¥n" "強制終了します","Com_Select() "); msg(" System_exit(); return 0; } 選択肢文字列を解析 // while( *TEXT != '{' )TEXT++; 最初の「{」を探す // TEXT++; 選択肢1の先頭を探す strcpy( str_1,Search_TextStr_CR() ); //行末までをそっくり取り込む while( *TEXT <= ' ' )TEXT++; //選択肢2の先頭を探す strcpy( str_2,Search_TextStr_CR() ); //行末までをそっくり取り込む while( *TEXT <= ' ' )TEXT++; //選択肢2の先頭を探す strcpy( str_3,Search_TextStr_CR() ); //行末までをそっくり取り込む //作業用サーフェイスを作成 while( *TEXT <= ' ' )TEXT++; // Create_select_Surface1(); Create_select_Surface2(); 文字列を展開(長いが、1、2、3は同じことの繰り返しでできる。) //選択肢1 TEXT_Y += TEXT_Y_PITCH*3/2; //Y:1.5 行分だけ表示位置を下げる TEXT_X = TEXT_AREA.left+20; //X:20pixel だけインデントをつける // rect1.left = TEXT_X; rect1.right = TEXT_X + TEXT_X_PITCH*(strlen(str_1)+3); rect1.top = TEXT_Y; rect1.bottom = TEXT_Y + TEXT_Y_PITCH; BitBlt( Select1_DC,rect1.left,rect1.top,rect1.right-rect1.left, rect1.bottom-rect1.top,Back_DC,rect1.left,rect1.top, SRCCOPY); バックサーフェイスの矩形領域を作業画面1に転送 // BitBlt( Select2_DC,rect1.left,rect1.top,rect1.right-rect1.left, rect1.bottom-rect1.top,Back_DC,rect1.left,rect1.top, SRCCOPY); StrPutFont (Select1_DC,rect1.left,rect1.top,Font_Size,POSI_COLOR1,POSI_COLOR2,str_1); 選択肢 // StrPutFont (Select2_DC,rect1.left,rect1.top,Font_Size,NEGA_COLOR1,NEGA_COLOR2,str_1); 選択肢(反転) // 選択肢2 // Y:1 行分だけ表示位置を下げる TEXT_AREA.left+20; //X:20pixel だけインデントをつける TEXT_Y += TEXT_Y_PITCH; TEXT_X = rect2.left // = TEXT_X; rect2.right = TEXT_X + TEXT_X_PITCH*(strlen(str_2)+3); rect2.top = TEXT_Y; rect2.bottom = TEXT_Y + TEXT_Y_PITCH; BitBlt( Select1_DC,rect2.left,rect2.top,rect2.right-rect2.left, rect2.bottom-rect2.top,Back_DC,rect2.left,rect2.top, SRCCOPY); バックサーフェイスの矩形領域を作業画面1に転送 // BitBlt( Select2_DC,rect2.left,rect2.top,rect2.right-rect2.left, rect2.bottom-rect2.top,Back_DC,rect2.left,rect2.top, SRCCOPY); StrPutFont (Select1_DC,rect2.left,rect2.top,Font_Size,POSI_COLOR1,POSI_COLOR2,str_2); 選択肢 // StrPutFont (Select2_DC,rect2.left,rect2.top,Font_Size,NEGA_COLOR1,NEGA_COLOR2,str_2); 選択肢(反転) // 選択肢3 // Y:1 行分だけ表示位置を下げる TEXT_AREA.left+20; //X:20pixel だけインデントをつける TEXT_Y += TEXT_Y_PITCH; TEXT_X = rect3.left // = TEXT_X; rect3.right = TEXT_X + TEXT_X_PITCH*(strlen(str_3)+3); rect3.top = TEXT_Y; rect3.bottom = TEXT_Y + TEXT_Y_PITCH; BitBlt( Select1_DC,rect3.left,rect3.top,rect3.right-rect3.left, rect3.bottom-rect3.top,Back_DC,rect3.left,rect3.top,SRCCOPY ); バックサーフェイスの矩形領域を作業画面1に転送 // BitBlt( Select2_DC,rect3.left,rect3.top,rect3.right-rect3.left, rect3.bottom-rect3.top,Back_DC,rect3.left,rect3.top,SRCCOPY); StrPut3D (Select1_DC,rect3.left,rect3.top,Font_Size,POSI_COLOR1,POSI_COLOR2,str_3); 選択肢 // StrPut3D (Select2_DC,rect3.left,rect3.top,Font_Size,NEGA_COLOR1,NEGA_COLOR2,str_3); 選択肢(反転) // テキスト表示再開したときのために改行処理をしておく TEXT_Y += TEXT_Y_PITCH*3/2; //Y:3/2 行分だけ表示位置を下げる TEXT_X = TEXT_AREA.left; //X:20pixel だけインデントをつける //選択肢をバックサーフェイスに転送 // BitBlt( Back_DC,rect1.left,rect1.top,rect1.right-rect1.left, rect1.bottom-rect1.top,Select1_DC,rect1.left,rect1.top,SRCCOPY); 選択肢1 // BitBlt( Back_DC,rect2.left,rect2.top,rect2.right-rect2.left, rect2.bottom-rect2.top,Select1_DC,rect2.left,rect2.top,SRCCOPY); 選択肢2 // BitBlt( Back_DC,rect3.left,rect3.top,rect3.right-rect3.left, rect3.bottom-rect3.top,Select1_DC,rect3.left,rect3.top,SRCCOPY); 選択肢3 //テキスト表示を止める // Mode_steatus.flag_text = OFF; 選択のフラグを立てる // Mode_steatus.flag_Select = ON; return 0; } ここでは、簡単に事前チェック、選択肢文字列を解析、サーフェイスの準備、文字列展 開、フラグのセットまでを順番に行っている。まず一番最初は、事前チェックとして選択 肢を3行分表示する余裕があるかを調べる。領域がたらない場合は PostQuitMessage を投 げて終了する。次の選択肢文字列の取込みは、スクリプトの書式に従って最初の「{」を飛 ばし、さらに改行その他のコントロールコードを含んでスペースまで(ASCII コードでは全 部まとめて 0x20=SPACE 以下の値になる)飛ばしながらテキストポインタを進め、選択文 字列先頭にたどり着いたら順次 Search_TextStr_CR で文字列取込みをしていく。選択肢取 込みが終了したら、作業用サーフェイスを確保して選択肢を展開する。また文字ピッチと 文字列長さを用いて選択肢の矩形領域を決めて保持しておく。ここで計算に用いている文 字表示位置XYやピッチはテキストエンジンの部分でつくったものを参照。 選択肢展開は、背景となるグラフィックデータをバックサーフェイスからコピーしてそ の上に展開する。 次の処理関数に行く前に新しいテキスト解析関数を紹介しておく char *Search_TextStr_CR() { static char str[512]; int counter,flag; 空白(スペース と TAB)を飛ばす // while( *TEXT==' ' || *TEXT==0x9 )TEXT++; テキストをコピー //終了条件:行末 // counter=0; flag=0; while(counter<254 && flag==0){ switch( (unsigned char)*TEXT ){ case 0x0d: break; case if( *(TEXT+1)==0x0a )flag=1; 行末 //[CR]= 0x0a: if( *(TEXT-1)==0x0d )flag=1;counter--; break; //[LF]= 行末 } if(flag==0){ str[counter]=*TEXT; counter++; TEXT++; } } str[counter]=0; return &str[0]; } この関数は行末までを文字列として取り込みます。今まで紹介した解析関数と違うとこ ろは、[CR][LF]のみを区切りとして扱うところである。 いつものようにスペースを区切りに使わないのは、選択肢は単語1つに限らないこと、 また英文のようにスペースを頻繁に挟んだものが指定される可能性もある為。 bool _Check_mouse_in_Rect( RECT rect ) :マウス位置判断関数 { if( Mouse_steatus.xpos>rect.left && Mouse_steatus.xpos<rect.right && Mouse_steatus.ypos>rect.top && Mouse_steatus.ypos<rect.bottom ) { return true; } return false; } この関数はマウスカーソルが矩形領域 RECT 内にあれば true を返し そうでなければ false を返す下受け関数。 int Com_Select_task() { マウス左ボタンのチェック用 //マウスが各選択肢の矩形領域内に入っていないかチェック //選択肢1 int L_button_menu = 0; // if( _Check_mouse_in_Rect(rect1 )==true ){ BitBlt( Back_DC,rect1.left,rect1.top,rect1.right-rect1.left, rect1.bottom-rect1.top,Select2_DC,rect1.left,rect1.top,SRCCOPY); 反転画像 //メニュー領域内でマウス左ボタンが押されているか? // 号 if( Mouse_steatus.fwkeys == MK_LBUTTON )L_button_menu = 1;// メニュー番 }else{ BitBlt( Back_DC,rect1.left,rect1.top,rect1.right-rect1.left, rect1.bottom-rect1.top,Select1_DC,rect1.left,rect1.top,SRCCOPY); 通常画像 // } 選択肢2 // if( _Check_mouse_in_Rect(rect2 )==true ){ BitBlt( Back_DC,rect2.left,rect2.top,rect2.right-rect2.left, rect2.bottom-rect2.top,Select2_DC,rect2.left,rect2.top,SRCCOPY); 反転画像 //メニュー領域内でマウス左ボタンが押されているか? // 号 if( Mouse_steatus.fwkeys == MK_LBUTTON )L_button_menu = 2;// メニュー番 }else{ BitBlt( Back_DC,rect2.left,rect2.top,rect2.right-rect2.left, rect2.bottom-rect2.top,Select1_DC,rect2.left,rect2.top,SRCCOPY); 通常画像 // } 選択肢3 // if( _Check_mouse_in_Rect(rect3 )==true ){ BitBlt( Back_DC,rect3.left,rect3.top,rect3.right-rect3.left, rect3.bottom-rect3.top,Select2_DC,rect3.left,rect3.top,SRCCOPY); 反転画像 //メニュー領域内でマウス左ボタンが押されているか? // 号 if( Mouse_steatus.fwkeys == MK_LBUTTON )L_button_menu = 3;// メニュー番 }else{ BitBlt( Back_DC,rect3.left,rect3.top,rect3.right-rect3.left, rect3.bottom-rect3.top,Select1_DC,rect3.left,rect3.top,SRCCOPY); 通常画像 // } ボタン内容に応じて飛び先を変える // switch( L_button_menu ){ case 0: 押されていない // break; case 1: 選択肢1番 // next_slush(); 次の「/」の直後に飛ぶ //フラグを元に戻す(処理終了) // Mode_steatus.flag_text = ON; Mode_steatus.flag_Select = OFF; 作業用サーフェイス消去 Delete_select_Surface2(); //作業用サーフェイス消去 Delete_select_Surface1(); // break; case 2: 選択肢2番 // 2つ目の「/」の直後に飛ぶ next_slush(); // next_slush(); Mode_steatus.flag_text = ON; フラグを元に戻す(処理終了) // Mode_steatus.flag_Select = OFF; 作業用サーフェイス消去 Delete_select_Surface2(); //作業用サーフェイス消去 Delete_select_Surface1(); // break; case 3: next_slush(); next_slush(); 選択肢3番 // 3つ目の「/」の直後に飛ぶ // next_slush(); Mode_steatus.flag_text = ON; フラグを元に戻す(処理終了) // Mode_steatus.flag_Select = OFF; 作業用サーフェイス消去 Delete_select_Surface2(); //作業用サーフェイス消去 Delete_select_Surface1(); // break; } return 0; } この処理関数では、選択メニューの矩形部分毎に上で紹介した Check_mouse_in_Rect を 呼び出してマウス位置を確認し、それに応じて反転/非反転画像をバックサーフェイスに転 送してやることになっている。これにより、マウスがメニューをポイントしているときは 反転画像が、またメニューから外れれば通常メニュー画像が現われることとなる。 マウスが領域内をポイントしているときに左ボタンが押下げられていれば「選択」操作 が行われたことになり、該当するテキストブロックにテキストポインタを飛ばしてフラグ 解除、さらに作業用サーフェイスを消去して終了となる。 上の関数内で使われた next_slush は次の「/」までテキストポインタを飛ばすというもの で、選択肢の数は今現在3つであるが、この数を増減させることにより、選択肢の数を変 えることができる。 以上でコマンド関数の説明を終わる。 ここまでの関数を作るための参考となったのは http://www1.mahoroba.ne.jp/~jyama/progindex.htm http://web.kyoto-inet.or.jp/people/ysskondo/ http://www.geocities.co.jp/SiliconValley-PaloAlto/5989/ http://www.kumei.ne.jp/c_lang/ http://www7.plala.or.jp/keny01/index.html http://www.asahi-net.or.jp/~uk8t-ktu/ C言語 初めてのCプログラミング 初めてのC言語 完全入門 独習 C++ 以上のサイトと書籍である。 ■ 最後に 短いノベル形式のゲームならこれで十分プレイできる質のものができると思う。 唯一残りとするなら、セーブ&ロード機能が実装できなかったことか。 改行コマンドを利用して、セーブ機能を実装できそうな段階までいったが ロード機能のほうが上手く機能しなかったので今回はそれを削除しておいた。 正直、ウィンドウプログラミングを選らんだ当初はウィンドウを表示させるだけで精一 杯で、画像表示や、テキスト表示等夢のまた夢のようであった。 しかしながら、どうにかこうにかひとつの形にまでたどり着けて本当によかったと思う。 今現在の胸中を正直に語ると、感動と疲労で一杯といったところである。 そして、最後に 今回ともにプログラムの開発を行った石井氏にも感謝の意を伝えたいと思う。