ノベルゲームのスクリプトエンジンについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ベル

ノベルゲームのスクリプトエンジンについて

#1

投稿記事 by ベル » 13年前

こんばんわ。 題名のとおりノベルゲームのスクリプトエンジンの作り方について勉強していたのですが、どうしても理解できないのでお知恵を貸してください。 長文になってしまい恐縮ですがお願いいたします。

コード:

//main.cppのソースです
#include "main.h"

int main()
{
        int i;
        ScriptInformation script;

        loadScript( "default.txt", &script );

        //10行目までを表示
        for( i = 0; i < 10; i++ ) {
                printf("%d : %s\n", i + 1, script.script[i] ); //疑問1"%s\n"って?
		}
}

コード:

//main.hのソースです
//////////////////////////////////////
//スクリプトエンジン                //
//////////////////////////////////////

#include <stdio.h>
#include <string.h>

#define SCRIPT_MAX_LINE 1000 //スクリプトは最大1000行まで読み込む
#define SCRIPT_MAX_STRING_LENGTH 300 //スクリプト一行の最大文字数

typedef struct ScriptInformation_tag {
        int maxLineNumber;									   //スクリプト行数
        int currentLine;										//現在何行目を実行しているか
        const char* filename;								    //ファイル名
        char script[SCRIPT_MAX_LINE][SCRIPT_MAX_STRING_LENGTH];//メモリ確保3,000,000も確保しており無駄じゃないのか?
} ScriptInformation;

//スクリプトファイルを読み込む
//戻り値 -1 : 失敗  0 : 成功
int loadScript(const char* filename, ScriptInformation* scriptInfo)
{
        int pos;
        char c;
        
        FILE* fp; //スクリプトファイル

        memset( scriptInfo , 0, sizeof(ScriptInformation) ); //スクリプト情報を初期化

        fp = fopen(filename, "r");      //スクリプトファイルを開く
        if( fp == NULL ) {				//ファイル読み込みに失敗
                printf("スクリプト %s を読み込めませんでした\n", filename);
                return -1;
        }

        //script書き込み時に使用 //疑問2 書き込み? ”どこに””なにを”書き込むのか
        pos = 0;

        for( ;; ) {
                //一文字読み込み //スクリプトファイルの中を見て、一文字取り出し、feofでなければcに一文字を格納する?
                c = fgetc( fp );
                //ファイルの終わりかどうか
                if( feof( fp ) ) {
                        break;
                }

//ここから下が特に意味分かりませんorz

                if( pos >= SCRIPT_MAX_STRING_LENGTH - 1 ) {          //疑問3 posが299より大きい場合? この時点ではposは0ではないのか?
                        printf("error: 文字数が多すぎます (%d行目)", scriptInfo->currentLine ); //1行の文字数が多すぎる
                        return -1;
                }

                //改行文字が出てきた場合,次の行へ移動
                if( c == '\n' ) {
                        scriptInfo->script[ scriptInfo->currentLine ][ pos ] = '\0';  //\0を文字列の最後に付ける             ←疑問4 なんで0をつける必要があるのか
                        scriptInfo->currentLine++;  //次の行に移動  
                        pos = 0;					//書き込み位置を0にする ←疑問5 書き込み位置を0にする理由は?
                }else {
                        scriptInfo->script[ scriptInfo->currentLine ][ pos ] = c; //書き込み //疑問6 この一行の構文の意味がわかりません
                        pos++;													  //文字書き込み位置をずらす
                }
        }
        scriptInfo->maxLineNumber = scriptInfo->currentLine; //最大行数
        scriptInfo->currentLine = 0;						 //読み込み中の行を0にする
        scriptInfo->filename = filename;					 //スクリプトファイル名を設定
        return 0;
}

あるサイトに掲載されてましたサンプルコードを読もうと思いましたが、サッパリ分かりません ;;

勉強していくうちに、
「スクリプトファイル」を読み込んでメモリ上に「バイナリデータ」として書き出し、その「バイナリデータ」を出力していると思われますが、ソースコードの中で、どの構文が「スクリプトファイルに対する操作」でどの構文が「バイナリデータに対する操作」なのかが分からないです。



疑問4や疑問5のところから、「スクリプトファイルではhelloと書かれている」のを、「バッファに0と1だけのバイナリデータを構築している」のかな?と思ってますが・・・
(でないと0を書き込む理由が分からないので・・・ もしくはmemset関数で一度全てのバッファ領域を0で埋めたことが関係している?)

また疑問1や疑問6のようにちょっと変わった書き方があり、解読できません。 特に疑問6の”構造体型ポインタ ->構造体内のメンバ[][]”という構文は似たような構文すら見当たらず、構造体やポインタに慣れてない私には目玉が飛びぬけるほどのコードです。

特にメモリ上の動きがまったく分からないので紙に書いてみたりしましたがサッパリです(コードのどのあたりで、メモリ上ではどうなっているのかetc)

長いコードでありながら最後まで読んでいただき、ありがとうございました

ベル

Re: ノベルゲームのスクリプトエンジンについて

#2

投稿記事 by ベル » 13年前

すいません。 編集の仕方が分からないのでレスで訂正させてください。

疑問1”printf("%d : %s\n", i + 1, script.script )”のところで
×⇒//疑問1"%s\n"って?
○⇒//疑問1"%d : %s\n"って? 例えば"1:a"と表示されるということですか?

の間違いです。

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: ノベルゲームのスクリプトエンジンについて

#3

投稿記事 by h2so5 » 13年前

疑問1:
実際に実行したらいいのでは?
参考:フォーマット指定子
http://www.k-cube.co.jp/wakaba/server/format.html

疑問3:
その行はループの中にあり、ループの中にpos++があります

疑問4:
0は文字列の終わりを示します
http://www9.plala.or.jp/sgwr-t/c/sec02.html
の「文字と文字列」の項目を参照

ベル

Re: ノベルゲームのスクリプトエンジンについて

#4

投稿記事 by ベル » 13年前

h2so5 様>
レスありがとうございます。

疑問3:
その行はループの中にあり、ループの中にpos++があります

あ! そうですね。インクリメントがあるのでいつかは300に行きますね。 初歩の見落としでした。 すいません。

疑問4:
0は文字列の終わりを示します

これも初歩の見落としでした。 すいません。 参考に貼ってくださったURLはいつも見るサイトなのにNULLのことしか頭に入ってませんでした>< 0は文字列の最後に必ずつく決まりですよね

疑問1:
実際に実行したらいいのでは?

すいません。 最初の投稿では間違って質問してしまったためチグハグになってしまいました。 フォーマット演算子が分からないのではなく、"%d:%s"の部分が分からないのです。

コード:

printf("%d : %s\n", i + 1, script.script[i] );
上記から、%dというのはi+1ですよね?(配列は0から始まるため+1している)%sというのはバッファの中の一文字目のこと(helloなh)だと思うのですが(自信ないです)

実行してみるとちゃんと
”hello”
と出力されるのです。

あの文法だと
"1:h
2:e
3:l
4:l
5:o"
と改行されながら表示されそうなものですが・・・(もちろんそんなサンプルをホームページにのせるハズないのもわかりますが)

script.script  ←ここの部分がイマイチ理解できていないのが原因でしょうか? 

script変数の中にある”ScriptInformation構造体”の”script配列メンバ”のことだと思っていたのですが・・・
つまり紛らわしいのですが、ドットの前のscriptとドットの後のscriptは別のものを指していますよね?

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: ノベルゲームのスクリプトエンジンについて

#5

投稿記事 by h2so5 » 13年前

ベル さんが書きました: 上記から、%dというのはi+1ですよね?(配列は0から始まるため+1している)%sというのはバッファの中の一文字目のこと(helloなh)だと思うのですが(自信ないです)
確かに、script.script はバッファの中の一文字目を指しています。
しかしフォーマット指定子は %s つまり文字列という指定なので、¥0が出てくるまで続きの文字を表示します。
フォーマット指定子が %c  ならば h 1文字だけが出力されます。

アバター
kimuchi
記事: 163
登録日時: 13年前
住所: 東京

Re: ノベルゲームのスクリプトエンジンについて

#6

投稿記事 by kimuchi » 13年前

疑問2、疑問5、疑問6:

scriptInfo->script[ scriptInfo->currentLine ][ pos ] = c;

「scriptInfo」は構造体、或いはクラスのポインタです。
「script[][]」「currentLine」 はそのメンバですね。

ここで、「script[][]」は二次元配列で、スクリプトファイルの文章を格納しています。
一次元目の配列は文の行数を表し、二次元目はその文の何文字目かを表しています。

仮にscriptファイルには次のような文章が書かれているとします。

コード:

script
read
今、読み込みを行おうとしている位置を「<>」このような記号で表しますと、

コード:

//読み込み開始時
//pos==0;
//scriptInfo->currentLine==0;
//c==s;

<>script
read

コード:

//読み込み途中
//pos==2;
//scriptInfo->currentLine==1;
//c==a;

script
re<>ad

コード:

//読み込み終了時
//pos==4;
//scriptInfo->currentLine==1;
//c==EOF;

script
read<>
上のようになります。
「pos」はスクリプト文章の「scriptInfo->currentLine」行目のの何文字目かを表すもので、
ここでは配列「scriptInfo->script[ scriptInfo->currentLine ][ pos ]」として格納する位置を指定しています。

つまり、スクリプトに書かれた文章と対応する位置である
「scriptInfo->script[ scriptInfo->currentLine ][ pos ]」に読み込んだ文字「c」を格納しているのです。

ですから、
疑問2の処理は格納する配列の書き込み位置を一番初め(scriptInfo->script[0][0])にするためで、
格納配列に、読み込んだ文字「c」を書き込んでいます。

疑問5の処理は、文一行を全て読み終えたので、次の行頭(何行目かの1文字目)に移動するために行っているのです。

疑問6の処理は、スクリプトに書かれた文章と対応する位置である
「scriptInfo->script[ scriptInfo->currentLine ][ pos ]」に読み込んだ文字「c」を格納するものです。


国語力が足りていないので分かりにくい部分が有りましたらすみません。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 13年前
住所: 東海地方
連絡を取る:

Re: ノベルゲームのスクリプトエンジンについて

#7

投稿記事 by softya(ソフト屋) » 13年前

ここは隠さずにはっきり書いてもらったほうが調べ易いのでお願います。
「ゲーム作りで学ぶ! 実践的C言語プログラミング」のコードですね?
http://karetta.jp/book-node/game-programming/235355
h2so5さんとは違ったアプローチで書いておきます。
//メモリ確保3,000,000も確保しており無駄じゃないのか?
思っきり無駄ですが、これをmallocなどのテクニックで処理すると初級者お断りのコードになりかねません。
//script書き込み時に使用 //疑問2 書き込み? ”どこに””なにを”書き込むのか
pos = 0;
char script[SCRIPT_MAX_LINE][SCRIPT_MAX_STRING_LENGTH];
コードを見るとこの2次元配列の2次元目の添字として使われていますね。
//ここから下が特に意味分かりませんorz
一行毎に分解して、scriptInfo->scriptに格納しているだけです。
なので、
scriptInfo->script[何行目かを表す][何文字目かを表す]
って事になります。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ベル

Re: ノベルゲームのスクリプトエンジンについて

#8

投稿記事 by ベル » 13年前

h2so5 様

なるほど! %sの機能が少しわかってなかったです。 勉強になりました!
ありがとうございました

ベル

Re: ノベルゲームのスクリプトエンジンについて

#9

投稿記事 by ベル » 13年前

kimuchi 様
おおおおおお やっと謎だった部分が読めました! 1行単位で処理していたのですねぇ・・・
[][]の時点で二次元配列かなぁ? とは思っておりましたが、[]の中に変数や構造体のメンバを入れることも可能なんですね 初めて知りました。

>>国語力が足りていないので分かりにくい部分が有りましたらすみません。

とんでもないです! 非常にわかりやすい説明で初心者の私でもやっと意味がわかりました。 ありがとうございました

ベル

Re: ノベルゲームのスクリプトエンジンについて

#10

投稿記事 by ベル » 13年前

softya(ソフト屋) 様

>>「ゲーム作りで学ぶ! 実践的C言語プログラミング」のコードですね?

そうです。 隠すつもりではなかったのですが、明記してしまってもよいか分からず伏せてました。 すいません 今後は書くようにします。

>>思っきり無駄ですが、これをmallocなどのテクニックで処理すると初級者お断りのコードになりかねません。
なるほど。 ちなみにこれはcのソースで、個人的にはc++やDXライブラリで書き直したいと思ってますのでいつかはちゃんと勉強しなくちゃなぁ・・・とは思ってます。

>>char script[SCRIPT_MAX_LINE][SCRIPT_MAX_STRING_LENGTH];
>>コードを見るとこの2次元配列の2次元目の添字として使われていますね。

ごめんなさい。 ここの件がわかりません。”2次元目の添字”というのは”SCRIPT_MAX_STRING_LENGTH”ですよね? これと

コード:

//script書き込み時に使用 //疑問2 書き込み? ”どこに””なにを”書き込むのか
pos = 0;
がどう結びつくのでしょうか??

>>一行毎に分解して、scriptInfo->scriptに格納しているだけです。
>>なので、
>>scriptInfo->script[何行目かを表す][何文字目かを表す

すごく分かりやすかったです! 一行ごとに分解しているのが分かるとすぐに処理の流れが分かりました! ありがとうございました!

ベル

Re: ノベルゲームのスクリプトエンジンについて

#11

投稿記事 by ベル » 13年前

皆様返信ありがとうございました! おかげさまでかなりソースを読めるようになりましたが、まだ不明な点が2つあるのでレスを書かせてください。

コード:

 scriptInfo->maxLineNumber = scriptInfo->currentLine; //最大行数
        scriptInfo->currentLine = 0;				   //読み込み中の行を0にする
        scriptInfo->filename = filename;			   //スクリプトファイル名を設定
はなにをしているのでしょうか?
”直近で読み込んだスクリプトファイル”の情報を格納しているのでしょうか?

例えば
1:Japan
2:America
3:china
というCountry.txtというファイルがあった場合、「Country.txtファイルは3行だったよー 読み込み開始の位置はまた0行目にセットしとくから次のテキストファイルも読み込める準備できてるよー」という意味でしょうか?

コード:

 //10行目までを表示
        for( i = 0; i < 10; i++ ) {
                printf("%d : %s\n", i + 1, script.script[i] );
		}
と言う出力命令でどうしてちゃんと”japan”とか表示できるのかが分かりません ”%d:"の部分はどこへ行ってしまったのでしょうか?

アバター
kimuchi
記事: 163
登録日時: 13年前
住所: 東京

Re: ノベルゲームのスクリプトエンジンについて

#12

投稿記事 by kimuchi » 13年前

コード:

        scriptInfo->maxLineNumber = scriptInfo->currentLine; //最大行数
        scriptInfo->currentLine = 0;                      //読み込み中の行を0にする
        scriptInfo->filename = filename;               //スクリプトファイル名を設定
はなにをしているのでしょうか?
”直近で読み込んだスクリプトファイル”の情報を格納しているのでしょうか?
コードを観る限り役割は何か分かりませんが、そのようですね。
”%d:"の部分はどこへ行ってしまったのでしょうか?
「printf()」の仕様なのですが、
「printf("%d(<1>の内容) : %d(<2>の内容)", <1>, <2> )」
このように後続の引数と対応して書式付出力が行われます。
ですから、「printf("%d : %s\n", i + 1, script.script );」の場合は
「%d」には「i+1」が、「%s」には「script.script」の情報が出力されます。

どうしてちゃんと”japan”とか表示できるのかが分かりません 

読み込み済みの「script.script」の中身を配列宣言の要領で表してあげると、

コード:

script.script {
    "1:Japan",
    "2:America",
    "3:china",
<以降は未定義>
}
こんな風になります。(厳密には違いますよ)
そうすれば自ずと

コード:

script[0] = "1:Japan"
script[1] = "2:America"
script[3] = "3:china"
このようなイメージで文字列が格納されているのが分かると思います。
(ちなみに「script[0][0]」は「'1'」のように文字(文字ではない)を表します)

あとは「printf()」で文字列用の書式「%s」を使って出力させているだけですね。

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: ノベルゲームのスクリプトエンジンについて

#13

投稿記事 by h2so5 » 13年前

ベル さんが書きました: 例えば
1:Japan
2:America
3:china
というCountry.txtというファイルがあった場合、「Country.txtファイルは3行だったよー 読み込み開始の位置はまた0行目にセットしとくから次のテキストファイルも読み込める準備できてるよー」という意味でしょうか?

コード:

 //10行目までを表示
        for( i = 0; i < 10; i++ ) {
                printf("%d : %s\n", i + 1, script.script[i] );
		}
と言う出力命令でどうしてちゃんと”japan”とか表示できるのかが分かりません ”%d:"の部分はどこへ行ってしまったのでしょうか?
”japan”とは表示されませんよ。
そもそも読み込むテキストファイルは、

コード:

Japan
America
china
のようなテクストファイルで
これを読み込んで画面出力した時、行番号とコロンが最初に付いて

コード:

1:Japan
2:America
3:china
のような形式で表示されます。

ベル

Re: ノベルゲームのスクリプトエンジンについて

#14

投稿記事 by ベル » 13年前

皆様返信ありがとうございました! おかげさまでかなりソースを読めるようになりましたが、まだ不明な点が2つあるのでレスを書かせてください。

コード:

 scriptInfo->maxLineNumber = scriptInfo->currentLine; //最大行数
        scriptInfo->currentLine = 0;				   //読み込み中の行を0にする
        scriptInfo->filename = filename;			   //スクリプトファイル名を設定
はなにをしているのでしょうか?
”直近で読み込んだスクリプトファイル”の情報を格納しているのでしょうか?

例えば
1:Japan
2:America
3:china
というCountry.txtというファイルがあった場合、「Country.txtファイルは3行だったよー 読み込み開始の位置はまた0行目にセットしとくから次のテキストファイルも読み込める準備できてるよー」という意味でしょうか?

コード:

 //10行目までを表示
        for( i = 0; i < 10; i++ ) {
                printf("%d : %s\n", i + 1, script.script[i] );
		}
と言う出力命令でどうしてちゃんと”japan”とか表示できるのかが分かりません ”%d:"の部分はどこへ行ってしまったのでしょうか?

ベル

Re: ノベルゲームのスクリプトエンジンについて

#15

投稿記事 by ベル » 13年前

あれ? 間違えて変なレスしちゃいました(汗) 一個上のは無視してください;;

kimuchi様
>>コードを観る限り役割は何か分かりませんが、そのようですね。

ありがとうございます。 確認がとれてよかったです。ノベルゲームなので、スクリプトファイルの内容を保持したいのだと思います。 既読メッセージの判断やセーブの箇所判断に利用できるからだと思います。

>>script[0] = "1:Japan"
>>script[1] = "2:America"
>>script[3] = "3:china"

なるほど! 上記の書き方わかりやすかったです。 おかげさまでコードが全て(とりあえず)読めました。 ありがとうございました!

ベル

Re: ノベルゲームのスクリプトエンジンについて

#16

投稿記事 by ベル » 13年前

h2so5様

ありがとうございました! 自分が完全に勘違いしてました><

"%d:"の部分が画面に出力されて正解でした。 もともとノベルゲーム作成の手順を紹介しているサイトでして、
「ノベルゲームなのに行が表示されるのはおかしいなぁ・・・」
と思っていたのですが、どうやらサンプルらしくてあえて行番号を表示させているプログラムだったようです。

深読みしすぎてましたorz

閉鎖

“C言語何でも質問掲示板” へ戻る