Lesson8 メインタスクの修正

今現在メッセージの表示にはprintfを使っている。
今後ウィンドウズアプリにコンバートするとグラフィックとして表示しないといけない。
この時重要なのはその処理の仕方である。
実際のゲームでは1文字ずつ表示する。
そして表示している間にマウスを動かし、別の操作(メニューなど)をすることができる。
つまりより実践的なメッセージ処理を実装しないとこのままでは使えない。

また命令拡張をしやすいように構成を変える必要もある。
従来のプログラムではエラー処理が不完全だったのであちこち修正した。
よって以下に説明する部分を追加するよりはファイルをダウンロードした方が早い。

ダウンロード

1.スクリプトファイルの読み込み

スクリプトの元データにはコメントや不適切な改行、スペース、タブが含まれる。
その余分な文字を取り除き純粋なスクリプトテキストに変換するのがScriptDataFillterである。
これまでのData配列をやめてDataAdr配列に変更した。
1行ごとのコピーではなく元テキストの行始めのアドレスのみを記憶する方式である。

int ScriptDataFillter()
{
	//無駄なスペース、コメントを取り除く
	//最終的な文字数を返す
	int a;
	char* p1,*p2;
	int count=0;
	p1=p2=DataTemp;//元のデータをポインタに代入
	for(a=0;a<MAX_TEMP_BUFFER;a++){
		if(p2[0]==32 && (p2[1]==0x0d || (p2[1]=='/' && p2[2]=='/'))){
			p2++;//改行の前のスペースもしくはコメントは削除
		}
		if(p2[0]=='/' && p2[1]=='/'){//コメント削除
			while(*p2!=0x0d && *p2!=0){
				p2++;
			}
		}
		while(p2[0]==0x0d && p2[1]==0x0a && p2[2]==0x0d && p2[3]==0x0a){
			p2+=2;//ただの改行なら切り捨てる
		}
		while(p2[0]==32 && p2[1]==32){
			p2++;//2つ以上連続するスペースは1つにする
		}
		while(p2[0]==9 && p2[1]==9){
			p2++;//2つ以上連続するタブは1つにする
		}
		if(p2[0]==0x0a && p2[1]==32){
			//改行の後のスペースは削除
			while(p2[1]==32){
				p2++;
			}
			*p1++=0x0a;
			p2++;
			count++;
		}else{
			*p1++=*p2++;
			count++;
		}
		if(*p2==0)
			break;//最後なので終了
	}
	for(a=(p1-DataTemp);a<MAX_TEMP_BUFFER;a++){
		DataTemp[a]=0;//残りを0で埋める
	}
	return count;
}
bool ScriptRead()
{//ファイルからスクリプトを読み込む
	ScriptInit();
	FILE *fp;
	int len;
	int Adr=0,a,b=0;
	char c;
	fp=fopen("sample.txt","rb");
	if(fp==NULL){
		return false;
	}
	len=fread(DataTemp,1,MAX_TEMP_BUFFER,fp);
	fclose(fp);
	len=ScriptDataFillter();//無駄を取る


	ScriptAdrMax=Adr=0;
	for(a=0;a<len;a++){
		c=DataTemp[a];//1文字読み込み
		if(c==0x0d){//改行コードなら
			if(b>0){
				DataAdr[Adr]=a-b;//1行の最初のアドレス

				ScriptAdrMax++;
				if(ScriptAdrMax>MAX_SCRIPT_LINE)
					return false;
				Adr++;b=0;//次の行設定
			}
		}else if(c!=0x0a){//改行コード以外なら
			b++;//次の文字へ
		}
		if(Adr>=MAX_SCRIPT_LINE)
			return false;//行をオーバーした
	}
	if(b>0){//残りの文字
		DataAdr[Adr]=a-b;//1行の最初のアドレス

		ScriptAdrMax++;
		if(ScriptAdrMax>MAX_SCRIPT_LINE)
			return false;
	}
	ScriptAdr=0;
	printf("スクリプト行数=%d\n",ScriptAdrMax);
	return true;
}

        


2.メッセージの処理

SetMessageで1行分のメッセージを登録し、MessageTaskで毎フレーム表示する。
最初の1フレーム目で人物の名前と”「”を表示する。
最後のフレームでは”」”を表示する。
全角の1文字は2バイトなので全角と半角を区別して出力するように改良している。


int  MessageTask()
{//メッセージの処理
	//文字を1文字ずつ表示させます。
	//表示中は1を返し、終端にきたら0を返します。
	char out[100];
	int EndFlag=0;
	out[0]=0;
	if(WinMesMax==0){
		return 0;
	}
	Sleep(20);//表示のウェイト
	if(WinMesCount==0 && WinName[0]!=0){
		sprintf(out,"%s 「",WinName);
	}
	//全角は2文字、半角は1文字を取り出す
	char m[3]={0};
	m[0]=WinMessage[WinMesCount++];
	if((m[0]&0x80)!=0 && (m[0]<(char)0xa1 || m[0]>(char)0xdf))
		m[1]=WinMessage[WinMesCount++];
	strcat(out,m);
	if(WinMesCount>=WinMesMax){
		if(WinName[0]!=0)
			strcat(out,"」");
		strcat(out,"\n");
		WinMesMax=0;//メッセージ終端で終わり
		EndFlag=0;
	}else{
		EndFlag=1;
	}
	printf("%s",out);
	return EndFlag;
}

        


3.スクリプトの翻訳

現在は1単語ごとに場合分けしているが命令が増えた場合は関数ポインタを使うなどして
合理化を図る。

int Decode(char* mes)
{//翻訳
	if(mes[0]!='&'){
		return 0;//命令ではなかった
	}
	int adr=0;
	char* p=mes+1;
	int num=0;
	//処理しやすいように分割
	MesParamCount=WordSplit(mes+1,(char**)MesParam,sizeof(MesParam[0]),20);
	//命令ごとに分岐
	if(strcmp(MesParam[0],"GOTO")==0){
		return C_GOTO;
	}
	if(strcmp(MesParam[0],"JUMP")==0){
		return C_JUMP;
	}
	if(strcmp(MesParam[0],"END")==0){
		return C_END;
	}
	return -1;
}
int ScriptTask(char* scriptdata)
{//スクリプト判定
		int code=Decode(scriptdata);
		char* MessageText=scriptdata;
		int a;
		if(NextScriptData()==false)
			return 1;//最後まで達した
		switch(code){
			case C_END://終了
				return 1;
				break;
			case C_GOTO://ジャンプ
				ScriptAdr=GetLabel(MesParam[1]);
				return 0;
				break;
			case C_JUMP://選択肢表示
				{
					for(a=1;a<MesParamCount/2;a++){
						printf("%d.%s\n",a,MesParam[a*2-1]);
					}
					while(1){
						int ch=getch();//入力
						if(ch>='1' && ch<='9'){
							ScriptAdr=GetLabel(MesParam[(ch-'0')*2]);//アドレスをセット
							break;
						}
					}
				return 0;
				}
				break;
			default://メッセージ表示
				if(MessageText[0]=='#'){
					SetMessage(MessageText+1,1);//セリフ
				}else{
					SetMessage(MessageText,0);//文章
				}
				break;
		}
		return 0;
}

        

4.全体の流れ

命令読み込み、メッセージの表示をメインループで回す。


int main()
{
	int result;
	//初期設定
	if(ScriptRead()==false){//ここでファイルから読み出し
		printf("ファイルがありません。もしくはスクリプト最大行数を越えました。\n");
		exit(0);
	}
	if(SetLabel()==false){//ラベル設定
		printf("ラベル登録限界に達しました\n");
		exit(0);
	}
	printf("スクリプト開始します。\n");
	//メインループ
	char out[200];
	//メインループ
	while(1){//先頭のコードを見る
		char* scdata=GetScriptData(out);//1行読み込み
		if(scdata[0]==0){
			if(NextScriptData()==false)
				break;//最後までいった
			continue;//何もなければスキップ
		}
		result=ScriptTask(scdata);//スクリプト実行
		if(result==1)
			break;//終了
		while(IsMessage()==true){
			result=MessageTask();
			if(result==0){
				getch();//入力待ち
			}
		}
	}
	printf("\nスクリプト終了します。\n");
	getch();//入力待ち
	return 0;
}

        

対応しているスクリプトは次のものです。

★&GOTO ラベル
ラベルに飛ぶ

★&JUMP(選択肢1,ラベル1,選択肢2,ラベル2・・・)
選択肢を選ぶとラベルに飛ぶ。
選択肢は最大9まで。

★&END
スクリプトを終了する。

サンプルは前回の使ってください。

[前に戻る][メニューに戻る][次へ]


テレワークならECナビ Yahoo 楽天 LINEがデータ消費ゼロで月額500円〜!
無料ホームページ 無料のクレジットカード 海外格安航空券 海外旅行保険が無料! 海外ホテル