第0章はLuaを使用してモデルを動かすために何をどうすればいいか大雑把に示すことを目的としています。つまり、詳細だったり正確だったりする説明は、Luaの文法説明を担当する第1章に丸投げです。適宜リンクを張りますので、不明な点やより詳しい情報が欲しい場合はそちらを参照してください。
本章は、「Keyブロックを使った『無制御モデル』は作れるけど、Luaは使えない」という人を想定して記述しています。逆に言うと、「モデルのつくりかたがわかりません!」という人は残念ながら対象外です。RigidChipsWiki(http://f42.aaa.livedoor.jp/~nemucat/pukiwiki14/pukiwiki.php http://www4.atwiki.jp/rigidchips/pages/16.html )にチュートリアル/きほんけいがあるので、そちらの方を読んでください。ドキュメント作成側の事情を正直に語れば、「変数って何ですか!わかりません!」といった問いはあまりにも根源的であるが故に逆に答え辛く、ドキュメント製作者の能力を超えるものがあるので、ここでは既存のドキュメントを参照してくれ!という態度をとります。
また、本章ではRigidChipsのモデルファイル(*.rcdまたは*.txt)をテキストエディタを用いて編集することを前提とします。プログラミング用の高機能なテキストエディタが望ましいですが、ぶっちゃけWindows付属のメモ帳で十分です。RigidChipsDesigner(RCD)やRigidChipsModeler(RCM)といったモデルエディタは便利なのですが、Val変数を所掌するValブロックやモデルの構造を記述するBodyブロックとの対応関係を考える視座を導入したいので、今回は生のテキストを扱うことにします。ついでに、モデルエディタ個別の操作説明とか書くのが面倒です。こっちが本音ですね。
以上の内容をまとめます。
以下に実際のrcdファイルの全文を示します。 長いですが、とりあえずここで見るべきものはさほど多くありません。大雑把に読み飛ばしましょう。
//BasicCar Val { Brake(default=0,min=0,max=80) HBrake(default=0,min=0,max=100) Handle(default=0,min=-20,max=20) Engine(default=0,min=-2500,max=2500) } Key { // 0:Engine(step=-500) // 1:Engine(step=500) // 2:Handle(step=-0.5) // 3:Handle(step=0.5) // 7:Brake(step=30),HBrake(step=20) // 8:HBrake(step=20) } Body { Core(){ N:Chip(){ N:Rudder(angle=Handle){ W:Frame(){ W:Wheel(angle=90,brake=Brake){ } } E:Frame(){ E:Wheel(angle=90,brake=Brake){ } } } } S:Chip(){ W:Frame(){ W:Wheel(angle=90,power=Engine,brake=HBrake){ } } E:Frame(){ E:Wheel(angle=90,power=-Engine,brake=HBrake){ } } } } } Script { print 0,"Welcome to Rigid-Chips World." print 1," FPS=",_FPS()," Chips=",_CHIPS()," Weight=",_WEIGHT() print 2," Width=",_WIDTH()," Height=",_HEIGHT() print 3," Faces=",_FACE() print 4," Vel=",_VEL() print 5," R=",_RED(32,32) print 6," G=",_GREEN(32,32) print 7," B=",_BLUE(32,32) } Lua { function main () out( 0,"Welcome to Rigid-Chips World." ) out( 1," FPS=",_FPS()," Chips=",_CHIPS()," Weight=",_WEIGHT() ) out( 2," Width=",_WIDTH()," Height=",_HEIGHT() ) out( 3," Faces=",_FACE() ) out( 4," Vel=",math.sqrt(_VX()^2+_VY()^2+_VZ()^2) ) --_VEL()はそういえばLuaにないので、めどいよぬるぽ out( 5," R=",_RED(32,32) ) out( 6," G=",_GREEN(32,32) ) out( 7," B=",_BLUE(32,32) ) if _KEY(0)>0 then ENGINE = ENGINE - 500 elseif _KEY(1)>0 then ENGINE = ENGINE + 500 else ENGINE = 0 end if _KEY(2)>0 then HANDLE = HANDLE - 0.5 elseif _KEY(3)>0 then HANDLE = HANDLE + 0.5 else if HANDLE > 0 then HANDLE = math.max(0,HANDLE-5) else HANDLE = math.min(0,HANDLE+5) end end if _KEY(8) then HBRAKE = HBRAKE + 20 end if _KEY(7)>0 then BRAKE = BRAKE + 30 HBRAKE = HBRAKE + 20 else BRAKE = 0 if not (_KEY(8)>0) then HBRAKE = 0 end end end }
まず、最初のほうにある Val{ なんとかかんとか } という塊がいわゆるValブロックと呼ばれるものです。モデルエディタから変数を定義している方はあまり馴染みがないかもしれませんが、この位置にこんな感じで記述されています。
Valブロックの役割はモデルの動作に必要な変数を定義することにあります。逆に言うと、Valブロックで変数を定義しないと、動くモデルは作れません。これは当然のことのように思われるかもしれませんが、Luaを使用する場合には重要なことです。なぜならば、Luaブロックでは、Luaブロック内でのみ通用する変数(Lua変数)を定義することができますが、それらではWheelを回したり、Jetを噴射したりすることはできないからです。Luaスクリプトによってモデルを動かそうとするときは、必ずValブロックで定義した変数(Lua変数との対比からVal変数とも呼ばれる)を使用しなければなりません。
次に目に付くのがKey{ なんとかかんとか }という塊です。これをKeyブロックと呼びます。ScriptやLuaを用いない「無制御モデル」の場合、この部分でキー操作の設定を行います。
上述のサンプルコードはデフォルトモデルであるBasicを改造したものなので、コメントアウトによって無効化された記述が残っていますが、このブロックの機能は例外なく全てLua側で代替可能なので、通常、中には何も書く必要はありません。
その次に目につくのが、Body{ なんとかかんとか }という塊です。これをBodyブロックと呼びます。モデルの構造や形状を定義している部分で、まさにモデルエディタなどで弄り回している部分になります。
ここで注意すべき点は、Chipの名前指定の動作です。
Body { Core(name=CORE) { } }
このように記述すると、名前をつけたChipの番号を示す変数が定義されます。上記の例ではCoreチップの番号である0を示す変数COREが作成されます。Valブロックで定義したものではありませんが、このChip名変数も便宜的にVal変数に含めて取り扱います。
さらに次には、Script{ なんとかかんとか }という塊が存在します。これは、RigidChipsにLuaの実行環境が搭載される前から存在している、RigidChips組み込みScriptを記述するためのスペースで、Scriptブロックと呼ばれます。
Luaブロックと同様にモデルをプログラムによって制御するために利用されますが、Luaの方が高機能であるため、現代においてもはや積極的に利用する実益はありません。上述のサンプルコードのように、ScriptブロックとLuaブロックを併記した場合には、Luaブロックのみが実行されることを把握しておけば足りるでしょう。
最後に存在するLua{ なんとかかんとか }という塊が、これからモデルの制御のために弄り回すLuaブロックです。{ と } の間にLuaのコードを記述します。
以上をまとめるとこんな感じになります。
問い 最速のレースカーに必要なLuaを教えてください! 答え 知らねぇよ
自分の思う通りにモデルを動かすために、どんなLuaプログラムを書けばいいのかというのは、基本的に自分で考えて自分で決めるべき問題です。普遍的一般的な正解が存在するわけではありませんから、他人は何も言えないのが現実です。
しかし、Luaが正常に動作するためには守らなければならないルールがあります。いわゆる、Luaの文法です。このルールを無視すると、およそ貴方の書いたLuaプログラムは正常に動きません。RigidChipsの窓の上の方に赤いエラー文字列が出てきて、モデルは思い通りに動くどころか、全く動かない状態になるでしょう。
そういうわけで、最初の目標はエラーの赤文字が出ないように正しいLuaプログラムを書くということになります。最速の車だか超絶性能の曲芸飛行機だか知りませんが、そんなものは後回しです。幸いなことに、エラーを出さないだけなら、実は簡単に書けてしまうので、以下にエラーの起こらないLuaブロックの例を示します。
Lua{ function main() end }
テキストエディタで、モデルのBodyブロックの次に以上の4行を追加すると、とりあえずLuaプログラムが動きます。もっとも、このサンプルを動かしてみるとちょっと不安な気持ちになるかもしれません。というのも、これは何もしないプログラムなので、4行を追加してみても何も起こらないからです。
そういうことで、最低限動いていることがわかるように、何か書いておきましょう。
Lua{ function main() out(0,"Hello World !") end }
Luaブロックを以上のように書き換えると、画面の左上に Hello World ! という文字が表示されるはずです。表示されない場合は、RigidChipsのメニューにあるHelpからShow Script Messageが有効になっているか、同じくメニューにあるRegulationからUsable Scriptが有効になっているか確認してください。有効であることを確認しても文字が表示されない場合は、貴方のミスなので反省してください(編集しているファイルとRCで読み込んでいるファイルが実は別物だったとか、そういうどうしようもないミスはよくあることなので注意だ!)。
さて、このfunction main() 〜 endというやつが非常に大切です。理屈なんかどうでもいいので、とりあえず書いておいてください。こういう「真面目に説明すると難しすぎるから、とりあえず文句を言わずにこう書いとけ」というものをおまじないと表現しますが、function main() 〜 end というのは、まさしくそのおまじないに相当します。
その正体はRigidChipsによって毎フレーム呼び出される特別な名前の関数なのですが、その辺の理屈を理解していてもいなくても書けば動くので、大人しくそういうものだということで納得してください。
以上の内容をまとめるとこんな感じです。
プログラマの格言に「プログラムは思い通りに動くんじゃない、書いた通りに動くんだ」というものがありますが、実際、どんな風に書こうとも、文法に適合している限り、Luaプログラムは書いた通りに動きます。(思い通りには動きません)
簡単なプログラムを書いているうちはいいのですが、段々Luaに慣れてきて複雑なプログラムを書くようになると、「何処に何のコードが書いてあるのかわからない><」という事態に直面します。自分の書いたコードなら、日の浅いうちは大丈夫だったりしますが、放置してから時間が経つと、「このコードわけわかんねぇ><」という事態が頻発します。
さて、そんなカオス状態に陥ったコードのサンプルを見てみましょう。
divide_path = function (str) local t = {} local count = 0 for s in string.gfind(str,"(%w+)%.?") do count = count+1 t[count] = s end return t,count end assert_path = function (str) local t,c = divide_path(str) if c<=0 then return false, nil, nil end if c==1 then return true, _G, _G[t[1]], t[c] end local tmp local parent = _G for n=1, c-1 do tmp = parent[ t[n] ] if object_type(tmp) ~= "namespace" then return false, parent, nil else parent = tmp end end tmp = parent[ t[c] ] return true, parent, tmp, t[c] end
実に読み辛いです。何故読み辛いかというと、コードの記述に意味のまとまりが反映されていないからです。Luaの構文解析プログラムにとっては何の問題もないのですが、コードを読む人間にとっては深刻な問題です。
そこで、インデントというものをつけてみます。インデントとは、コードの行の左端につける空白のことです。百聞は一見に如かずとかいうので、とりあえずやっちゃってみましょう。
divide_path = function (str) local t = {} local count = 0 for s in string.gfind(str,"(%w+)%.?") do count = count+1 t[count] = s end return t,count end assert_path = function (str) local t,c = divide_path(str) if c<=0 then return false, nil, nil end if c==1 then return true, _G, _G[t[1]], t[c] end local tmp local parent = _G for n=1, c-1 do tmp = parent[ t[n] ] if object_type(tmp) ~= "namespace" then return false, parent, nil else parent = tmp end end tmp = parent[ t[c] ] return true, parent, tmp, t[c] end
インデントをつけると、こんな風に左の空白の量で何となく意味の纏まりが見えるようになります。人間がコードを読んだとき、どれだけ意味がとりやすいかという指標を可読性などと表現しますが、一般的には可読性の高いコードほど不具合が発生した際の対処が容易になります。面倒だからとインデントをつけないと、後々酷い目に遭うので、しっかりとつけておくことをお勧めします。
なお、インデントに使う空白文字についてはちょっとした派閥抗争があります。ここではTab文字を利用しましたが、Tab文字は環境やエディタによって見え方が変わってしまうことから、Tab文字を半角スペース4文字で代用すべきとする主張も存在するようです。ドキュメント製作者としては全角スペースさえ使わなければそれでいいと思うので、暇ならばその種の論争を参照して適宜自分の態度を決してください。
さて、インデントによって幾分意味付けは明瞭になりましたが、それでもこのコードには何だかよくわからない部分が多々残っています。そこで、「この部分では一体何をしているのか」という情報をコード内に記述することにします。
--与えられたパス文字列を分割する divide_path = function (str) local t = {} local count = 0 for s in string.gfind(str,"(%w+)%.?") do count = count+1 t[count] = s end return t,count --分割したパス文字列を含むテーブル, 分割数 end --与えられたパス文字列を検証し、該当する名前空間を返す assert_path = function (str) local t,c = divide_path(str) if c<=0 then return false, nil, nil end --分割数0なら失敗 if c==1 then return true, _G, _G[t[1]], t[c] end --分割数1なら検証の必要なし --分割数2以上の場合、階層構造を検証する local tmp local parent = _G for n=1, c-1 do tmp = parent[ t[n] ] --もし名前空間でないものが含まれていたら失敗 if object_type(tmp) ~= "namespace" then return false, parent, nil else parent = tmp end end tmp = parent[ t[c] ] --該当する名前空間が存在しない場合、nilで構わない return true, parent, tmp, t[c] end
正直、あまりわかり易くなった気がしないけど気にしたら負けだ!
このような、コード中に書き加える説明文をコメントといいます。上述の例では、--から改行までが全てコメントになります。コメントはLuaプログラムとして実行されることはありません。そのおかげで色々と自由に書けます。コメントがあるとコードを読み解く助けになるので、できる範囲で簡潔にコメントを残しておくことをお勧めします。
コメントはプログラムとして実行されないことを以下の例で確認してみましょう。Luaブロックを丸ごと示します。
Lua{ function main() --out(0,"Hello World !") out(1,"Hello alternative World !") end }
実行すると、Hello World ! ではなく、Hello alternative World ! と表示されます。--をつけたり取っ払ったりして、挙動を確かめてみてください。
余談になりますが、このようにコードの一部をコメントにして動作しなくすることを、コメントアウトと言います。簡単に無効化したり、元に戻したりできるので、一時的に一部のコードを無効化したい時に便利です。いちいち書き直したりする手間が省けます。
以上の内容をまとめるとこんな感じです。