Squirrel その9

前回予告した通り、ちょっと高度なSquirrelの使い方。
高度といってもちょっとです。難しいことはないと思います。

いわゆるスクリプト言語をゲームで使用する場合、主に外に出しておくと便利なものに使用します。
例えば敵キャラクタのAIとか、ゲーム中に発生するイベントシーンとかですね。
ゲーム中に発生する、ちょっとしたカメラの移動を例にとって処理を考えてみましょう。
まず、ゲームプレイ中にはカメラはプレイヤーキャラクタを注視しています。
ある位置に来るとプレイヤーキャラクタは動けなくなり、カメラがそれまで画面外にあったアイテムなどにフォーカスし、その後またプレイヤーに戻るとします。
カメラを移動させ、移動終了したらスクリプトも終了するというようなスクリプト関数を用意したとしましょう。
この関数をただ実行しただけだと上手くいきません。なぜなら、関数が終了して戻ってきた時には既にカメラの移動は終わっているわけですから、画面にそれが反映される事はありません。
どうすればいいか?
まず、スクリプト関数内で描画命令を発行することができますが、描画以外にもやらなければいけないことは多いのでそうはいきません。
毎フレームスクリプト関数を実行し、スクリプト関数内では1フレーム分の処理だけを行う。
この方法は十分機能しますが、初期化やデータの保持が面倒だったりします。
通常使われる方法は、やはりコルーチンだと思います。
言語仕様のほうでも紹介しましたが、コルーチンはメインルーチンから呼ばれ、任意にメインルーチンに戻り、戻った位置からコルーチンの再開が可能という代物です。
スクリプト関数の最初で初期化をし、その後移動終了までループ。
1フレーム分移動したところでメインルーチンに戻り、描画等を終えてコルーチンの再開。
コルーチンが終了するまでこれを繰り返せば初期化から実行までスクリプト関数内で全てまかなえるようになります。

では、実際のプログラムを見ていきましょう。と言っても、カメラを動かすのではなく適当な文字列を出すだけですが。
まず、test.nut などの適当なSquirrelファイルに以下の関数を書いてください。

function foo_thread(num)
{
    for(local i=0; i<num; i++){
        print("Round " + (i + 1) + " : Fight!\n");
        local ret = suspend("You ");
        print(ret + "\n");
    }
}

"Round n : Fight!"と表示し、その後にsuspend() でメインルーチンに戻り、"You win!"か"You lose!"と表示します。
特殊なところとして、"You "という文字列だけC++プログラム内で出力していることと、"win!"か"lose!"はC++プログラム内でランダムを用いて決定している点です。
Squirrelファイルを実行するところまでは同じです。実行に成功したら、スレッドを新規作成する必要があります。

HSQUIRRELVM  new_v = sq_newthread(v, 1024);

第1引数が元のVM、第2引数が新しいVMのスタックサイズです。
これで new_v に新しいスレッドが作成されました。今後、この new_v に対して処理を行います。
Squirrel関数の実行は new_v に対して通常の関数発行方法を用います。引数はルートテーブルとループ数を指定してください。
sq_call() 関数でSquirrel関数が実行されると suspend() でメインルーチンに戻ってきます。
suspend されたかどうかは以下の方法でチェックできます。

while(sq_getvmstate(new_v) == SQ_VMSTATE_SUSPENDED){
}

この式が false になると Squirrel関数は終了しています。
このループ内で以下の処理を行います。

char* str;
sq_getstring(new_v, -1, (const char**)&str);
sq_pop(new_v, 1);
printf("%s", str);
if(rand() & 0x01){
    sq_pushstring(new_v, "win!", -1);
}
else{
    sq_pushstring(new_v, "lose!", -1);
}
sq_wakeupvm(new_v, SQTrue, SQTrue, SQFalse);

sq_getstring() 関数でトップスタックの文字列を取得します。ここに"You "の文字列が入っています。
文字列を取得した後にはポップすることをお忘れなく。
次にランダムで"win!"か"lose!"の文字列をプッシュします。この文字列が Squirrel関数の ret に入ってきます。
それから sq_wakeupvm() 関数でコルーチンを再開します。
第2引数を true にすると先にプッシュした値を suspend() の戻り値として使用します。
第3引数はsuspend() もしくは関数終了時に戻り値を受け付けます。
第4引数はエラーが出た時にハンドラに送るかどうかです。
これで、次に suspend() されるか関数が終了するまでメインルーチンには戻ってきません。
今回はループ回数をC++側から指定していますが、もちろんSquirrel関数内で指定しても構いません。
new_v がサスペンド中である限り、sq_wakeupvm() で再開しつづければいいわけです。

今回はこれで終了。次はクラスでもやってみましょう。