Luaレッスン25「(3.2)物理エンジンへようこそ(その3)」和訳

出典:http://compasstech.com.au/TNS_Authoring/Scripting/script_tut25.html
レッスン25「(3.2)物理エンジンへようこそ(その3)」:複数のボールをジャグリングする
前回レッスンで今回のための舞台が整いました。今回のレッスンでは、スタート、ストップ、リセットといった、シミュレーションの一連の流れをコントロールする方法を学びます。特に大切なこととして、複数のオブジェクトを扱う方法を学びます。
local pause
function init(gc)
     ...
     pause = 1
     ...
end
function on.timer()
     space:step(0.01*pause)
     platform.window:invalidate()
end
function on.enterKey()
     pause = pause == 1 and 0 or 1
     platform.window:invalidate()
end
function on.escapeKey()
     on.resize()
     platform.window:invalidate()
end
function on.arrowUp()
     totalBodies = (var.recall("n") or 5)
     totalBodies = totalBodies + 1
     var.store("n", totalBodies)
     on.resize()
end
function on.arrowDown()
     if totalBodies > 1 then
          totalBodies = (var.recall("n") or 5)
          totalBodies = totalBodies - 1
          var.store("n", totalBodies)
          on.resize()
     end
end
local totalBodies
local bodies
local shapes
local initX
local initY
local Colors
local colorNum
function init(gc)
     ...
     require "color"
     Colors = {color.gray, color.red, color.orange,color.yellow, color.green, color.blue, color.black}
     colorNum = 1
     bodies = {}
     shapes = {}
     totalBodies = (var.recall("n") or 5)
     for i=1,totalBodies do
          newBody = physics.Body(mass, inertia)
          newShape = physics.CircleShape(newBody, width, physics.Vect(0,width/2))
          newShape:setRestitution(elasticity)
          newShape:setFriction(friction)
          newBody:setVel(physics.Vect(1000,1000))
          initX = math.random(W)
          initY = math.random(H)
          newBody:setPos(physics.Vect(initX, initY))
          table.insert(bodies, newBody)
          table.insert(shapes, newShape)
          space:addBody(newBody)
          space:addShape(newShape)
     end
end
function paint(gc)
     for k=1,totalBodies do
          ...
          bodies[k]:setPos( physics.Vect(posX, posY) )
          bodies[k]:setVel( physics.Vect(velX, velY) )
          chooseColor = Colors[((colorNum + k) % #Colors) + 1]
          gc:setColorRGB(chooseColor)
          gc:fillArc(posX, posY, width, width, 0, 360)
     end
end
function on.resize()
     on.paint = init
end

1. 一時停止とリセット
前回問いかけましたが、動きを停止させるにはどうすれば良いでしょうか? 
驚くべきことではありませんが、鍵はtimer函数にあります。現在は0.01に設定してあります。要するに、毎秒100回リフレッシュされます。Nspireでは毎秒100回が限度です。この値をゼロに変更すれば、動きが停止します。では、"pause"という名前のローカル変数を作成します。以前と同様、この変数は、他のすべてのローカル変数と一緒にスクリプトの先頭のところで設定します。そのあと、init函数内で初期値をpause = 1と設定します。この値が0に設定されていると、当該ファイルを開いたとき、または当該ファイルをリセットしたときに、動きが一時停止した状態からスタートすることになります。
次に、on.timer函数をspace:step(0.01)からspace:step(0.01*pause)へ変更します。こうすると、pause = 1のときには何も変化しませんが、pause = 0になったときにtimer stepた0になって、すべてが停止します。
リセットをかけるのはもっとずっと簡単です。そうでし、スクリプトの末尾にあるresize函数を使います。on.resize()をコールしても良いし、on.paint = initだけをコールするのでも構いません。これだけですべてがスタート地点へ戻ります。
あと必要なのは、スタートさせるきっかけとなるトリガーだけです。とりあえず、モーションの停止、開始にはenterKeyを使い、リセットにはescapeKeyを使います。
enterKeyやescapeKeyといった函数は、どこに記述しても構いません。これらの函数は、どこに記述されていようとも、トリガーがかかれば見つかるからです。
このスクリプトは、ハンドヘルドでもコンピューター上でも完璧に動作しますが、Playerでは役に立ちません。Playerにはキーボードがないからです。Playerでも実行できるよう、また、単にもっと有効で便利なドキュメントにする手段として、モーションの開始、停止、リセットを行うためのボタンを追加することにします。さらに、画面をクリックしたところからモーションが再開されるようにもしたいと思います。
こうした機能は後回しにして、まずはわたくしも疑問に思いましたが、どうすれば空中に複数のボールを浮かせられるのでしょうか? 
2. 複数のオブジェクトを操作する
複数のオブジェクトを操作するのにテーブルを1つか2つ設定する必要があるとしても驚きはしないでしょう。
上記のコードをよく見てください。init、paintの両方の函数に同じような変更が加えられています。各スクリプトのメイン・パートはループで囲われていますので、必要な数のオブジェクトが順番に参照されます。今回、totalBodiesの初期値は10に設定しました(訳註:サンプル・ファイルではスライダーの初期値が3に設定されている)。さらに、ボディー用に1つ、シェイプ用に1つ、空のテーブルを作成します。
ボディーおよびシェイプの作成、行き先指定を行っているコード部分をforループで囲った以外に、init函数にいくつか行を追加しました。どのオブジェクトも別々の位置からスタートさせたいからです。その手段として、ランダムな初期座標を追加するというのは良いアイディアです。そのあとで、table.insertを使って新しいボディーをbodiesテーブルに追加し、新しいシェイプをshapesテーブルに追加します。
paint函数は、ベロシティーとポジションとを設定するところで少し変更していることに注意してください。newBodyおおびnewShapeという名前のオブジェクト1個ずつにベロシティーとポジションとを設定する代わりに、bodies[k]およびshapes[k]というテーブルのメンバーごとに設定するようにしたのです。これで、10個の青いボールが跳ね回るはずです。
あたりまえですが、複数のボールがみんな同じ色では大して面白くありません。せっかくスクリプトの中にrequire "color"を記述したわけですから、使ってみましょう。
これを行うシンプルな方法の1つは、カラー・テーブルを作成することです。今回は、Colorsというテーブルにします。そのテーブルを参照するための数字colorNumが必要です。そうして選択されたカラーをchooseColorと呼ぶことにします。以上の処理はinit函数内で定義されます。カラフルなボディーが実際に作成されるときにpaint内でこの処理がコールされます。
各ボディーがペイントされるときに、テーブル内で順番にカラーが選択されてゆきます。オブジェクトの数がテーブルの長さを超えたときは、もう一度先頭のカラーへ順番が戻ります。
スクリプトの実行中にオブジェクトの数が変更できれば、さらに良いですね。その手段として今回は上向き矢印キーと下向き矢印キーとを使います。この2つのキーを使って、totalBodiesの値を増減します。そのあと、増減された新しい値でオブジェクトが再描画されるように強制的にリセットをかけます。さらに、TI-Nspireのスライダー変数("n")へもリンクしました。
しかしこの処理を機能させるためには、"n"の現在の値をinit函数に確認させて、その値をtotalBodies値に使用する必要があります。そこで、init函数にtotalBodies = (var.recall("n") or 5)を追加しました。escapeキーおよび矢印キーでリセットをかけたときも、"n"の現在の値が確認され、それに応じて調整されます。
ここで新たな課題を提示します。面白いリセットの仕方に挑戦してみましょう。クリックしたところからボールのモーションが再開されるようにmouseUp函数を追加してみてください。
リセット用および一時停止用のボタンには画像が必要です。マウス・コマンドの仕様も必要になります。こうした処理をシンプルにするため、長方形クラスを定義し、両方のボタン用に長方形を定義しました。そうすれば、いずれかの長方形内でクリックされたかどうかが簡単にチェックできます。
さらにもう1つ、"grabber"として機能する長方形クラスを上に被せました。これで、ボタンの位置が自由に動かせます。
こうした変更点について別のページで詳しく述べるよりも、むしろ、今回のレッスンのサンプル・ファイルを見てほしいと思います。スクリプトをじっくり読み解いて、いろいろいじってみてください。
次のレッスンでは、跳ねるボールの境界線をChipmunkで設定する新たな方法について学びます。画面の端部をセグメントとして定義するという手法を用います。そのあと、おそらく最終レッスンでのことになりますが、円を別のシェイプ、特に多角形に変更する方法について学びます。この方法を使えば、オブジェクトの数、オブジェクトのタイプが変更できるほか、オブジェクトのモーションもコントロールできます。そあとは、あなたの番です。

-------------------------------------------------------------------

(訳註)今回の実行結果。スクリプトはサンプル・ファイルのまま。

f:id:ti-nspire:20141225103446g:plain