Luaからゲームウィンドウを作ってみる前章でLuaからC言語の関数を呼ぶ方法を取り上げました。この章までで、LuaからC言語の関数も、C言語からLuaの関数も呼べるようになり、お互いに情報のやり取りが可能となりました。具体的な事をしたくなります。そこで、この章ではLuaからゲーム用のウィンドウを作り、そこにラインを引いてみたいと思います。ここまではコンソールウィンドウでしたが、ここからはWin32アプリケーションで黒くないウィンドウができます(笑)。Luaファイルで作成に必要なパラメータを作成し、それをC言語側で受けてウィンドウを作成することにします。Luaファイル内にグローバルな値を設定するのも手としてもちろんありなのですが、折角ですからLuaファイル内に初期化関数となるinit関数を設定します。C言語側はそのinit関数から初期化パラメータを受け取ります。init関数はテーブルを作って返すのが簡単かなと思います。480};endinit関数は幅と高さのパラメータをテーブルとして返すとします。Lua側は単純ですね。C言語側でテーブルを解析C言語側では上のInitWindow.luaファイルを読み込み解析し、Luaステートにinit関数を登録してもらいます。次にinit関数を呼び出し、テーブルをスタックに積んでもらいます。ここまでは特に問題ありません。これまでスタックにある数値や文字列の取得は見てきました(lua_tonumber関数、lua_tostring関数)。テーブルにある値を取得するのはちょっと特別で、変数の名前を通す必要があります。変数名(=フィールド名));第1引数はスタックを持っているLuaステートです。第2引数はスタック内のテーブルがあるスタック番号を指定します。第3引数はテーブル内の変数名(フィールド名)を文字列で渡します。テーブルからウィンドウの幅と高さの情報を貰えば、後はそれを設定してウィンドウを作成するだけです。0;}「Lua内の初期化関数を呼ぶ」というコメントの範囲まででLuaファイル内のinit関数を呼んでいます。これが通ればinit関数が初期化値が入ったテーブルを返し、それがスタックに積まれているはずですスタックに積まれているテーブルから、次に欲しいパラメータを貰います。テーブルから値を得るのはlua_getfield関数です。指定のフィールド名な変数があれば、その値がスタックに積まれます。無ければ「nil」が積まれます。これは重要な事で、lua_getfield関数は成功しても失敗しても、スタックにちゃんと何かを積んでくれます。これにより、値があればそれを格納、無ければ無視という判定がスムーズにできるわけです。実際コードではその判定を行って作成するウィンドウサイズを変更しています。使い終わったLuaステートはメモリを圧迫するだけなのでさっさと削除です。後は指定されたウィンドウサイズ(クライアントサイズ)でウィンドウを作成して処理完了となります。結果はこんな感じです:後はLua側のinit関数が返すテーブルにパラメータを追加し、C言語側でそれに対応すれば、Lua側から作るウィンドウの初期状態を柔軟に変更できるようになります。Luaからウィンドウに線を引いてみようでは次に、Luaで「ラインオブジェクト」を作り、そこに座標を与えてウィンドウに線を引いてみる事にします。APIは呼べませんので、最終的なライン引きはC言語側が行うのですが、今回はLua上でオブジェクトを作り、それに指示した通りのラインを引くという点で、ちょっとオブジェクト指向っぽいLuaになります。250);window:setObject(obj1);window:setObject(obj2);endん〜...。上の実装内で呼び出しているgetWindow、createObject、setObject関数は全部C言語側で設定します。getWindow関数はウィンドウオブジェクトをLua側に渡すとします。C++のクラスのオブジェクトは直接渡せませんので、ウィンドウに対して操作する為の関数が入ったテーブルをC言語側で作って渡します。その関数の一つsetObject関数ではウィンドウ上に置くウィジット(丸とか線とかボタンの総称)をウィンドウに登録します。Luaはあらゆる値を関数に渡せるのでLua側は問題ありません。ここでLuaを通さないでラインオブジェクトを登録する事を考えてみます。250);objArray.push_back(obj1);objArray.push_back(obj2);つまり、setObject関数を呼んだ時にこのポインタの登録を行う必要があるんです。setObject関数内で具体的なオブジェクトを作るのは芸がありません。「Lua側から登録すべきポインタを渡してもらう」のがスマートです。Lua側でC言語のポインタを直接は作れません。C言語側からポインタを貰うことはできます。Luaには「LightUserData型」というポインタを保持できる型があるのでした。これです、やりました、いえい(^-^)vLua側コードでのcreateObject関数が呼ばれたら、C言語側でLineオブジェクトを作り、そのポインタを含めたテーブルを作ってLua側に戻します。そのポインタに「pointer」というフィールド名を付けたとして、次に呼ばれるwindow:setObjectメソッドに渡されたラインオブジェクトテーブルからそのpointerを指定し、得られたポインタをC言語側で登録します。後はそれを親クラスのポインタ(Object*)として取り扱えば、オブジェクトの配列(objArray)に登録できるというわけです。筋道は見えましたので、ちょっとずつ作りこんでいきます。1;}このメソッドがLua側から呼ばれたら、新しいテーブルを1つ作り(lua_newtable)、C言語側のsetObject関数をスタックに積んで(lua_pushcfunction)、lua_setfieldでテーブルに登録します。この段階でスタックにはテーブル1つだけになりますので、戻り値1つとして関数を終わります。1;}この関数は要はファクトリ関数なんです。Lua側の第1引数には作成するオブジェクトの種類が文字列で入ってきます(という約束にします)。今は"Line"です。Lineの場合第2〜第5引数に線の視点と終点が入ってきます(という約束にします(^-^;)。y2)にそれぞれ代入し、Lineオブジェクトにさらにその値を引き渡しています。オブジェクトが出来たらもう引数は必要ないのでスタックにある値を全部popしてしまいます。続いて戻り値となるテーブルを作成しています。このテーブルには先ほど作成したLineオブジェクトのポインタを登録します。スタックにポインタを乗せるには「lua_pushlightuserdata関数」を用います。この関数の第2引数に渡したvoid*型がスタックに乗ります。次にlua_setfield関数でこのポインタに"pointer"という名前を付けてテーブルに登録します。これでcreateObject関数をLua側から呼ぶと、Lineオブジェクトのポインタを保持したテーブルが返ります。このポインタに対してLuaから一切何も直接操作は出来ませんが、次のsetObject関数にLineオブジェクトたるテーブルを渡すとC言語に処理が戻ってきますので、そこでテーブルから再びポインタを取り戻し、処理ができるというわけです。0;}Lua側では「window:setObject(obj1)」とコロンでこの関数を呼んでいます(windowテーブルにsetObject関数が登録されていて、クラスとして扱う場合はコロンを使う約束)。コロンで関数を呼ぶと、第1引数に呼んだ本人であるwindowテーブル、第2引数にobj1テーブルが入ってきます。よって上の関数内では第2引数にあるobj1テーブルから先に登録した"pointer"を取得して(lua_topointer関数を使います)、それをObject型にキャストし、配列に登録しています。以上でラインオブジェクトを取得して座標を登録し、それを指定のウィンドウに配置するという処理がLua側で出来ました、もう一度流れを整理してみましょう。下図を御覧下さい:C側のgetWindow関数はウィンドウに対する関数が登録されたテーブルをLua側に渡します。上ではsetObject関数を登録していますが、例えばオブジェクトを削除するremoveObject関数などもここで登録するわけです。createObject関数の最大の仕事はLineオブジェクトのポインタをテーブルに登録してLua側に返すことです。このテーブルにLineに関する他の関数(setStart関数、setEnd関数など)も一緒に登録して渡せば、Lineクラスのメソッドとして使えるわけです。windowテーブルに登録してあったsetObject関数をLua側で呼び、ラインオブジェクトのポインタが入ったテーブル(obj1)を渡せば、C側の関数内でそのポインタを取り出せるので、めでたくobjectArrayにそのポインタを登録できるわけです。このように、Lua側でC++側のオブジェクトは直接扱うことはできませんが、テーブル≒オブジェクトという扱いをしてそのテーブル毎にオブジェクトポインタを保持しておいて、そのポインタ入りテーブルをC側に戻せば、オブジェクト単位で操作する事ができるんです。上の図で「*obj」というポインタ入りテーブルがC++→Lua→C++とぐるっと回ってきているのに注目すればここはOKです。"初期化用Luaファイルがありません。"init関数がありません。"init関数の呼び出しに失敗しました。"初期化テーブルがinit関数で返されていません。"init関数の呼び出しに失敗しました。0;}ちなみに実行結果はこちら:Lua側で指定した座標にラインが引けました(^-^)。Lua側でラインオブジェクトをどんどん取得してwindowに設定すれば、どんどん線が引けます。APIでラインを引きましたが、C側はどうとでも書けるわけで、DirectXに置き換えももちろんできます。Lua側を変えることなくゲームエンジンをごそっと入れ替えるというのは「スクリプトによるゲームロジック」と「エンジン」を分離するという、昨今のゲームプログラムの大切な設計思想です。Luaによるクラスの操作っぽい事が少しずつ見えてきました。楽しいですね〜(^-^)。