【小ネタ】(こねた)


Luaのテクニックを紹介

Valの変数を配列のように使う

_G["変数名" .. インデックス]

_G[]を使うとValの変数を配列のように使うことが出来ます。
(Luaで配列を宣言して最後に全部代入という手もありますが。)
これを利用してグラデーションを作ってみましょう

Val
{
    COLOR_1(default=10)
    COLOR_2(default=10)
    COLOR_3(default=10)
    COLOR_4(default=10)
}
Key
{
}
Body {
    Core(color=COLOR_1){
        N:Chip(color=COLOR_2){
            N:Chip(color=COLOR_3) {
                N:Chip(color=COLOR_4) {
                }
            }
        }
    }
}
Lua
{
    function main()
        local c1 = 0
        local c2 = 255

        local d = (c2 - c1) / 4

        for i = 1, 4 do
            _G["COLOR_" .. i] = c1 + d * i --ここが味噌
        end
    end
}

黒から青へのグラデーションが出来たと思います。
今回はcolorに対して使いましたが、angleに対して使うといろいろ面白いことが出来ると思います

シナリオ実行のたびに違う乱数を使う

function OnInit()
    seed = _STICKS()
    math.randomseed(seed)
    math.random()--任意。初めの乱数はあんまりばらけないため
end

math.random()は何もしなければ毎回同じ乱数を返します。
そこでRigidChipsを実行してからの時間を返す_STICKS()を使い、
乱数を初期化します。
ただし、System.rcsに設定されると初めの1回が必ずseed=0になってしまうので、
気になる場合は対策しましょう。

Luaでのオブジェクト指向

setmetatable ( インスタンス, { __index=クラス } )

setmetatableを使うと、テーブルをオブジェクトのように扱うことが出来ます。
上の例ではインスタンスとクラスはそれぞれテーブルです。

基本

インスタンスのfunctionを呼び出すと、クラスのfunctionが呼び出されるようになります。
例 (もっといい例求む)

Point = {}
Point_mt = { __index=Point }
function Point.new(x, y)
    tmp = {}
    tmp.x = x
    tmp.y = y
    return setmetatable (tmp, Point_mt)
end

function Point:set(x, y)
    self.x = x
    self.y = y
end

function Point:get()
    return self.x, self.y
end

function test()
    o = Point.new(0, 0)
    a = Point.new(10, 10)    --いくつも作れる

    o:set(-10, -10)    --oからもPointの関数setが呼び出せる。
    --この場合変更されるのはo.xとo.y
end

functionの後ろの:は自分自身をあらわす引数selfを使うという意味です。
selfは:の前を表します。o:get()ならoを、a:get()ならaです。

継承

オブジェクト指向をするのであれば、やはり継承をしたくなります。
メタテーブルによって結構深いところまで探してくれるので
継承は次のような方法で行います。

Parent = {}
--親クラス
function Parent.new(a)
    tmp = {
        val = a
    }
    return setmetatable(tmp, { __index = Parent })
end

function Parent:talk()
    return "I am parent" .. self.val
end

function Parent:getVal()
    return self.val
end

----------------------------------------------
--子クラス
Child = {}
--親クラスをメタテーブルに設定しておく
setmetatable(Child, { __index = Parent })

function Child.new(a)
    tmp = {
        val = a
    }
    --あらためて子クラスをメタテーブルに設定
    return setmetatable(tmp, { __index = Child })
end

--talkをオーバーライド
function Child:talk()
    return "I am Child" .. (self.val * 2)
end

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

child = nil
parent = nil

function OnInit()
    parent = Parent.new(3)
    child = Child.new(4)
end

function OnFrame()
    out(3, parent:talk())
    out(4, parent:getVal())

    out(0, child:talk())
    out(1, child:getVal())
end

ちゃんと継承が行われていることがわかるはずです。

オブジェクト指向にするとプログラムの見通しはよくなりますが、実行速度は遅くなります。
あまり複雑なことをしないのであればオブジェクト指向の必要はないでしょう。

(追記)遅くなるといってもほとんど変わりません。
関数代入で似たようなことをして場合と比べてみた結果、下手すればこっちのほうが早いぐらいでした。
実行速度は気にしなくてもよいかもしれません。

テーブルの要素の巡回

テーブルの要素すべてにアクセスしたいときにはnext関数を使います。

index, val = next(table[, i])

next関数は引数としてテーブルとそのキーをもらい、返り値として次の要素とそのキーを返します。 よって、すべてのテーブルを循環したいときには次のようにします。

table = {
    [3] = 20,
    40,
    z = 60,
    80
}

function OnFrame()
    sum = 0

    local index = nil --キーが代入される
    local val         --要素が代入される

    index, val = next(table)--indexを省くと初めの要素を返す

    while val ~= nil do
        --ここに処理を書く
        sum = sum + val --今回は要素の合計を出してみましょう。
        --
        
        index, val = next(table, index)--最後まで行くとnilを返す。
    end

    out(1, sum)--答えは200
end

下のやり方のほうがcoolです。
この方法は忘れてください。orz

真・テーブルの要素の巡回

汎用for文を使うと上のプログラムはかなりすっきりします。 以下がサンプルです。動作は上のプログラムと同じです。

table = {
    [3] = 20,
    40,
    z = 60,
    80
}

function OnFrame()
    sum = 0

    for index, val in next, table do
        --ここに処理を書く
        sum = sum + val --今回は要素の合計を出してみましょう。
        --
    end
    
    out(1, sum)--答えは200
end

かなりすっきりしましたね。

変数に関数を代入!?

変数名 = 関数名 --代入するとき、関数名には()をつけない

変数名(引数) --実行するときには変数名に()とそのなかに引数を書く。

なんとLuaでは変数に関数を代入することが出来てしまいます。
これを利用すると自在に実行する関数を変更することが出来ます。

とりあえずJetを寝かせたり立たせたりしてみました。
少し変えれば飛行機のギアなどに応用できるかもしれません

Val
{
    ANG(default=0,min=-90,max=0)
}
Key
{
}
Body
{
    Core(){
        N:Jet(angle=ANG){
        }
        S:Weight(){
        }
    }
}
Lua
{
--[[
ここにfunc = downなどを書いてもうまくいきません
初期化で関数を代入させたい場合はその関数の後に書きましょう。
]]

--代入する関数を書く。
--立たせる
function stand(d)
    ANG = ANG - d
end

--寝かせる
function down(d)
    ANG = ANG + d
end
--代入する関数ここまで。

--関数を変数funcに代入する
--かならず代入したい関数のあとに書くこと。あるいはOnInit()の中とか
func = down --はじめはとりあえずdown()を入れておく

function OnInit()
end
function OnReset()
end

function OnFrame()
    --Zが押されたとき
    if _KEYDOWN(4) > 0 then
        --ここが味噌。funcの関数を変更
        func = down
    end

    --Xが押されたとき
    if _KEYDOWN(5) > 0 then
        --ここが味噌。funcの関数を変更
        func = stand
    end

    --ここで代入した関数を使用
    func(2)
end
}

ZキーやXキーを押すとfunc()の中身(働き)が変わります。
funcに代入する位置に気をつけてください。
代入する前に関数が書かれている(定義されている)必要があります

飛行機のギアに使えると書きましたが、、適当な変数使ってif文で分岐させたほうが簡単でわかりやすいかもしれません。

テーブルを使った例

テーブルと組み合わせると次のようなことも出来ます。
この場合、テーブルを配列のように使います。
実行するときには、適当なモデルのLuaブロックに以下をコピーしてください。

--代入する関数。
--マウス座標を返す
function mouse()
    return "( " .. _MX() .. " , " .. _MY() .. " )"
end

--座標を返す
function point()
    return string.format("( %d, %d, %d)", _X(0), _Y(0), _Z(0))
end

--関数の数
MAX = 4
--現在使用する関数。テーブルの添え字として利用
num = 0

--この配列に代入する
funcs = {
    [0] = mouse,

    --こんな書き方もあり。というかこの場合はこっちのほうがいいかも
    [1] = 
        function()
            return _WIDTH() * _HEIGHT()--画面の面積
        end,

    [2] = point,
    [3] = _FPS	--元からある関数も代入可能
}

function OnInit()
end
function OnReset()
end

function OnFrame()
    --Zが押されたとき
    if _KEYDOWN(4) > 0 then
        num = num + 1
    end
    --余りを取ることで0から3の間に制限する
    num = math.mod(num, MAX)
    
    out(0, num)
    
    --ここで代入した関数を使用
    out(1, funcs[num]() )
end

Zを押すたびに画面に表示される情報が変わると思います。

この方法の使い道は・・・思いつきません。
luaにはswitch文がないとはいえ、素直にif文つなげたほうがいいかもしれません。
テーブルにうまく使えばCの構造体っぽいオブジェクト指向も出来ますが、ややこしくなるだけで、そのうえスピードもそれほど変わらないようなので、意味はないでしょう
まさに小ネタ
追記:コールバック関数もできるということでまったく意味がないわけではないですね。

ライブラリを作ろう

他の多くのプログラミング言語と同様にLuaでも、プログラムを幾つものファイルに分割して記述することが出来ます。
これによって、いつも使う関数をライブラリに記述して楽が出来ます。
もちろん一つのモデルのプログラムを分割してもかまいませんが・・
さて、他のファイルに書かれたコードを使える様にするには以下の様にします。

require("./library.lua"); --モデルと同じフォルダにあるlibrary.luaファイルを読み込み、呼び出せる様にする

この一行をLuaブロックの中のどこの関数にも属していない部分に書いておくと、
Luaブロックの全ての関数からlibrary.luaに記述されている関数を呼び出すことが出来るようになります。
もし、「could not load package」という、ファイルが見つからないエラーのメッセージが出てしまった時には、
モデルを(Ctrl+Uのリロードではなく)「File → Open Chips...」で開き直すと改善される場合があります。

-----ここからモデルのLuaブロック
require("./library.lua");--モデルと同じフォルダにあるlibrary.luaファイルを読み込む
function hoge()
    PrintMessage(9);
end
function OnFrame()
    hoge();
    PrintMessage(10);
    out(12, callCount);--他のファイルのグローバル変数もアクセスできるよ〜
end
----ここまでモデルのLuaブロック

----ここからlibrary.luaファイル
callCount = 0;--PrintMessage関数が呼ばれた回数を記録する
function PrintMessage(lineNum)--lineNum行目におなじみのメッセージを表示する関数
    out(lineNum, "Hello World from library.lua "..callCount);--メッセージを表示する
    callCount = callCount + 1;--呼び出された回数を増やす
end
----ここまでlibrary.luaファイル

この例では余り意味のある関数を呼び出していませんが、よく使う関数をまとめてライブラリファイルにしておけば、
いちいちコピペする手間がなくて、能率が上がることでしょう。
ただし、Uploaderにアップロードするときには、ライブラリは他の人には使えないので、
モデルのLuaブロックにライブラリの中身を張り付けてからアップロードする必要があるのが問題となるかもしれません。
又、Luaのグローバル変数は読み込んだ全てのファイルからアクセスすることが出来るので思わぬ値の破壊には注意が必要です。

(以下詳しい内容を加筆求む)

シナリオとモデルのイケナイ関係

Luaでモデルの変数にアクセスするには大文字でモデルの変数を書けばよい、というのは周知のことです。

シナリオからも同じことができます。 次のコードをtest.rcsとでも名前をつけて保存してください。 そしてこのシナリオを開けば4WDなどのたいていのモデルが勝手に走り出すはずです。

ENGINE = -1000--初期化しても共有される
function OnFrame()
    ENGINE = -5000
end

このとき、モデルとシナリオで変数を共有する形となります。うまく使えばモデルを選ばない制御システムなどもできるかもしれませんね。

また、モデル側からもシナリオの値の変更が可能です。

いま、「あれ?」とおもったかた、ご想像のとおりこれは何かとマズイです。 シナリオとモデル、双方で予期せぬ値の変更が行われる可能性があります。

定数を全部大文字で宣言するということはよく使われる手法ですが、RigidChipsではやめたほうがいいでしょう。MAXPOWERとか・・・

任意の数の引数に対応した関数

Luaでは、引数の数を自由に変えられる関数を作れます。 例として、「渡された数値の平均値を求める関数」はこんな風に書きます。

function average(...)
    sum = 0

    for i = 1, arg.n do -- arg.n は、引数の個数を表す
        sum = sum + arg[i]
        -- arg[1], arg[2] ... arg[arg.n]で各要素にアクセス
    end

    return sum / arg.n
end

function OnFrame()
    out(1, "average(4) = "          , average(4) )
    out(2, "average(3, 5, 7, 9) = " , average(3, 5, 7, 9) )
end

関数の引数リストの最後に "..."(半角ドット3つ) を付けると、その関数はいくらでも引数を取れるようになります。 "..." 以降にある引数は、argという名前のテーブルに入れられ、関数はそれを読むことで引数にアクセスできます。 *1



*1 この部分は、RigidChipsが使用しているLua5.0と、現在最新のLua5.1とでは仕様が異なりますので、Web等で調べる際には注意してください