まず、ブラシスクリプトで踏まえることは、Luaという小型の言語で書くということです。ブラシスクリプトの仕様は、FireAlpacaのページに記載されていますが、Luaの基本事項を先に理解している前提のようです。
ひとまず、Luaの基本事項をいくつか挙げてみます。
end
がつくことで、スクリプト処理の区切りになる。i++
、i--
という書き方がなく、i=i+1
、i=i-1
とする。コメントは、以下のように書きます。コメントは、スクリプト処理で無視されます。
--あとは、好きなようにしてください。一行コメントですが。
--[[あとは、好きなようにしてください。
複数行コメントだから、少しは余裕があります。]]
一行ならば、頭に半角ハイフン2つを付けます。複数行ならば、頭に--[[
、後ろに]]
を付けます。
文字列は、アルファベット以外にも日本語でもOKなはずです。以下のように、シングルか、ダブルのクオーテーションで囲みます。
a='Luaの文字列'
a="Luaの文字列"
しかし、このままではシングルとダブルのクオーテーションは、文字列内で使えないので、バックスラッシュを前につけます。バックスラッシュ自体も使えないので、同じようにします。
a='シングルクオーテーションは、\'とします。'
a="ダブルクオーテーションは、\"とします。"
a="バックスラッシュは、回避用途に使われているので、\\とします。"
ブラシスクリプトでは、主にパラメータの名前に使われるだけなので、最低限のことで十分だと思われます。
基本形は、function 関数名(変数,...)~end
という関数を定義する形のようです。必然的に、mainという関数名は使います。こんなコードでも、エラー自体は出ないはず。
function main(x,y,p)
end
ただし、何も起きません。中身がない状態だからです。 とはいえ、関数自体はカーソルの座標が動くたびに実行されています。とりあえず、 以下のように、return 1
を追加すると、関数の結果を返せるようになります。
function main(x,y,p)
return 1
end
このコードでも、目に見える変化はありません。しかし、変数に入っている値を利用して計算、結果を返す、これらの処理をするための下地はできます。
また、関数や変数は、幾らでも定義するので、名前をつけて管理します。命名ルールは、以下のリストのキーワード以外で、なおかつ一文字目を数字にしないこと。使える文字は、半角アルファベットとアンダーバーです。
and break do else elseif end false for function goto if in local nil not or repeat return then true until while _VERSION
そういえば、(x, y, p)
は何なのかというと、変数の一種です。初めから、何かしら値が入っています。xとyは、横と縦のカーソル座標で、pは筆圧です。
さて、以下のブラシスクリプトのコードを例に、これから各項目を述べていきます。
function param1()
return "間隔", 0, 100, 0
end
function param2()
return "筆圧透明度", 0, 1, 0
end
firstDraw=true
lastX=0
lastY=0
function main(x, y, p)
local w=bs_width()
local distance=bs_distance(lastX-x, lastY-y)
if not firstDraw then
if distance < w+w*bs_param1()/100 then
return 0
end
end
local r,g,b=bs_fore()
local a=bs_opaque()*255
if bs_param2() == 1 then
a=a*p
end
local array={
{w, 0},
{w/1.5, 0.5},
{w/3, 1}
}
for i=1, #array do
local w=array[i][1]
local c=array[i][2]
bs_ellipse(x, y, w, w, 0, r*c, g*c, b*c, a)
end
firstDraw=false
lastX=x
lastY=y
return 1
end
上のコード自体は、以下の画像のような線を描画するものです。多少ごちゃついていますが、描画するのに必要なパラメータを先に設定してから、描画するという処理がベースになる、これは恐らく変わらないでしょう。カーソルを動かす限り、これらの処理は繰り返されます。
最初のあたりで、以下のように、変数を初期化しています。
firstDraw=true
lastX=0
lastY=0
変数の初期化は、変数に値を代入するだけです。とにかく、変数=値と書けばいいのです。もし、変数の値を途中で変えたいときでも、同じようにするだけです。
また、Luaでは入れる値のタイプが、整数、少数、文字列など、いくつかありますが、特に気にすることはありません。なぜなら、値のタイプごとに変数を用意しなくても、自動的に変数が適応してくれるからです。
さらに、まとめて代入することができます。lastX, lastY...=x, y...
といった書き方をします。コンマで区切って、ペアで対応するように、順に書いていけばよいです。
ちなみに、変数が初期化されていないときに、変数を参照した場合は、nil
という値が返って、スクリプトがエラーになることがあります。
-- a=0としない。
-- bが初期化されるときに、いきなりaを参照している。
b=a+1
nil
という値は、未定義のままで何もないという意味のようです。とりあえず、変数を空にしたい場合は、a=nil
とすればいいはずです。
そして、初期化する領域の違いがあります。グローバルとローカルです。便宜上、グローバルは、function ()~end
の関数ブロック以外のことを指し、ローカルとは、関数ブロック内を指すこととします。 (do, for, if, while...~end
、repeat~until
というブロック内でも有効ですが...)
最初の初期化は、グローバル領域で行われていることになります。グローバル領域で初期化された変数は、基本的にどこでも参照できて、変更可能です。関数内より先に読み込まれるためです。
ローカルの変数の初期化は、local
をつけることで可能です。大抵、グローバルから参照するのはできません。先に初期化が行われていないからです。この場合、エラーになります。書き方は、以下の通りです。
local a=0
初期化する領域によって変数の有効範囲に違いがあります。グローバルは、標準語のようなもので、全体的に有効です。ローカルは、方言のように一定の領域内のみで有効です。そのため、影響範囲も違うわけで、用途によって使い分けします。
関数内をみると、local w=bs_width()
と初期化されています。これは、APIを呼び出しています。呼び出しは、関数名()
と式中に書きます。APIも関数なのですが、予め用意されているのでわざわざ定義する必要はありません。しかし、パラメータなど使うのであれば、自分で定義する必要があります。
function param1()
return
end
このように定義するのですが、もし定義せずにいきなり呼び出したら、何も起きないか、正しい動作をしないでしょう。なので、呼び出す前に必ず定義するようにしましょう。呼び出す例は、以下の通りです。
-- 先に定義
function call()
return
end
-- 呼び出し
call()
関数を使うのであれば、値を受け渡しすることがあります。function main(x, y, p)
のx, y, pは、引数(ひきすう)といわれるもので、一時的な橋渡しをする変数のようなものでしょうか。
次の行では、引数を参照して計算しています。x, yはカーソル座標が渡されているので、式中にlastX-x, lastY-y
とそのまま書けば取得できるわけです。
local distance=bs_distance(lastX-x, lastY-y)
ちなみに、複数の引数を渡すのであれば、コンマで区切ります。
Luaでは、関数も変数データの値となるようです。なので、このように書いてもエラーになりません。
a=function ()
return
end
しかも、下のような関数定義と同じことになります。
function a()
return
end
もし、一時的に使う関数を定義したいのなら、このように書けばいいのです。
local a=function ()
return
end
-- もしくは、
local function a()
return
end
返しは、returnのあとに式を書きます。何度も同じ計算するときなど、関数定義します。そのときに、このように返します。
function a(b)
return b+1
end
c=a(10) -- 答えは11
c=a(c) -- 答えは12
関数はデータの値なので、このようにも返せます。
function a(b)
return function ()
return b
end
end
私は、パラメータの定義を短く書きたいときにこうしているんですが、どうなんでしょうかね。パラメータの値を予め細工したいときは、普通に定義するべきでしょうね。
条件分岐とは、if 条件 then~end
で終わるブロックを指します。間隔制御したいときは、このようにしていますよね。
if not firstDraw then
if distance < w*bs_param1()/100 then
return 0
end
end
まず、if not firstDraw then
を注目します。firstDrawは、初回描画であるということを表す用途で初期化されています。
この場合は、firstDraw=true
と代入されていて、初回描画であるということです。そのあと、常にfirstDrawにはfalseが代入されて、初回描画であることを否定します。
firstDraw=false
lastX=x
lastY=y
true
とfalse
自体は、状態を表す値のようなものです。true
は肯定、false
は否定します。if not firstDraw then
は、初回描画ではないときを条件としています。(未定義を表すnil
も、否定する値として扱われます。)
次に、if distance < w*bs_param1()/100 then
を注目します。さらに条件分岐したいのか、入れ子になっています。if 条件 then~end
がひとつのブロックとすると、その中にもうひとつ、ブロックを作ったことになります。このように、条件分岐で細かく制御することができますが、入れ子の数は程々にしておいたほうが無難です。把握しにくくなります。
not、and、orは、複数の条件があるときに使います。notは、とにかく条件に合致しないときにture、寧ろ合致するときはflaseとなります。andは、条件と条件が重なったときにtrue、重ならないならfalseとなります。orは、条件がひとつでも合致するならture、どれも引っかからないならfalseとなります。
ちなみに、変数の初期化にa=b or 0.5
と書いても、エラーにはなりません。というより、b or 0.5
も式であるからだと思われます。
値がある数より小さいか、大きいか、あるいは同じなのかと比較して、処理を分けることが多々とあります。
例えば、if distance < w*bs_param1()/100 then
というのは、distance
がw*bs_param1()/100
より小さいときを表します。要するに、不等式で条件を表します。(不等式と言っていいのかわからないけど...)
以下、Luaでの不等式の一例です。
a < b
-- aがbより小さい。bを含まない。a > b
-- aがbより大きい。bを含まない。a <= b
-- aがb以下であること。bを含む。a >= b
-- aがb以上であること。bを含む。a == b
-- aとbは同じ。a ~= b
-- aとbは異なるもの。条件ごとに処理を振り分けたいときは、else
かelseif
を使うと良さげな場合があります。使用例としては、以下のとおりです。
local w=bs_width()
local distance=bs_distance(lastX-x, lastY-y)
local interval=0
if w < 10 then -- wが10より小さいとき
interval=w+w*bs_param1()/100
elseif w < 20 then -- wが20より小さいとき
interval=w/1.5+w*bs_param1()/100
else -- wが10より、20より大きいとき。つまり上の二つの条件以外の場合。
interval=w/2+w*bs_param1()/100
end
if not firstDraw then
if distance < interval then
return 0
end
end
テーブルは、変数の一種です。複数の変数の配列で、主に連続したデータを処理する場合に活用できるでしょう。初期化は、前述した変数のセクションと同様ですが、代入する内容が異なります。テーブルは、{}
をイコールの後に入れて初期化します。つまり、テーブル={}
となります。実際に変数の値を入れるときは、{}
の中に記述します。値は、普通の変数とほぼ変わりません。2つ以上同じく並ぶ場合は、コンマで区切ります。
a={1, 2, 3}
b={"a", "b", "c"}
c, d={}, {} -- まとめて代入できる
また、最初に示したコード内では、以下のようにテーブルを初期化しています。
local array={
{w, 0},
{w/1.5, 0.5},
{w/3, 1}
}
{}
の中に、{}
がありますね。テーブルは、またテーブルそのものを入れることができます。これも、{}
が2つ以上同じく並ぶ場合は、コンマで区切ります。そして、テーブルを入れる階層(次元とも言う)は、恐らくメモリが許す限り制限はないと思われます。
a={ -- 1階層
{1, 2, 3}, -- 2階層
{"a", -- 2階層
{1, 2, 3}, -- 3階層
{4, 5, 6, -- 3階層
{"b", "c", "d"} -- 4階層
},
{7, 8, 9} -- 3階層
}
}
テーブルの値を取り出すには、位置を把握する必要があります。その位置を表すには、番号を使うのが手っ取り早いです。Luaでは、番号が0からではなく、1から始まります。番号は、[]
で囲むので、テーブル[1]
となります。以下のように、値を取り出します。
a={1, 2, 3}
b=a[1] -- aのテーブルの1番目
c=a[2] -- aのテーブルの2番目
d=a[3] -- aのテーブルの3番目
そして、多次元のテーブルの値を取り出すには、目的の位置まで階層を辿るように、順に番号をテーブル[1][1][1]...
と記述します。
a={ -- 1次元
{1, 2} -- 2次元
}
b=array[1][1] -- 2次元の1番目
c=array[1][2] -- 2次元の2番目
また、数値の番号以外に文字列で表すことができます。文字列の場合は、テーブル['Name']
及びテーブル["Name"]
、もしくはテーブル.Name
と記述します。
a.['name']=0
a.["name"]=0
a.Name=0
-- どちらとも同等
b.["Name"]["is"]["none"]=0
b.['Name']['is']['none']=0
b.Name.is.none=0
同じ処理を繰り返したい場合は、for 変数=始まりの数値, 終わりの数値, 増減分(略すと1になる) do ~ end
となります。最初に示したコード内では、以下のとおりにループ処理を記述しています。処理自体は、テーブルの値を順に取り出し、指定した大きさと色の円で描画しています。
for i=1, #array do -- 増減分は略しているので、1ずつ増える
local w=array[i][1]
local c=array[i][2]
bs_ellipse(x, y, w, w, 0, r*c, g*c, b*c, a)
end
まず、for i=1, #array do
を注目します。i=1
は、カウンターの開始数値を指定しています。#array
は、テーブルが持つデータの数(多次元の分は含まない)を表し、カウンターの終了数値としています。この場合、#array
は3となるので、1から3まで、つまり3回分の処理が発生します。
そして、変数i
は1から3まで変化するので、テーブルの値を順に取り出すのに利用できます。
local w=array[i][1] -- 2次元の1番目
local c=array[i][2] -- 2次元の2番目
最終的に、このように描画されています。
-- テーブルはこのように定義している
local array={
{w, 0},
{w/1.5, 0.5},
{w/3, 1}
}
-- 直接的に記述するとこんな感じ
bs_ellipse(x, y, w, w, 0, r*0, g*0, b*0, a) -- 1回目、暗い色の大きな円
bs_ellipse(x, y, w/1.5, w/1.5, 0, r*0.5, g*0.5, b*0, a) -- 2回目、グレー化した中くらいの円
bs_ellipse(x, y, w/3, w/3, 0, r*1, g*1, b*1, a) -- 3回目、明るい色の小さな円
もし、ループを途中で中断したいときは、break
を使います。例えば、2回目で中断したい場合は、以下のとおりにします。
for i=1, #array do
if i == 2 then
break -- iが2のときに中断
end
local w=array[i][1]
local c=array[i][2]
bs_ellipse(x, y, w, w, 0, r*c, g*c, b*c, a)
end
話が脱線しますが、while 条件 do ~ end
、repeat ~ until 条件
でも、似たようなことができることがあります。条件が合うまで繰り返されるのではなく、条件が合わなくなるまで、繰り返されます。以下は、while
の場合です。
local a=nil
while a ~= 5 do
a=math.random(0, 5)
end
-- 以下と同等なはず...
local a=nil
for i=0, 0, 0 do
if a == 5 then
break
end
a=math.random(0, 5)
end
repeat
の場合です。
repeat
local a=math.random(0, 5)
until a ~= 5 -- ここでendとなるので、条件式でローカル変数を使ってもOKっぽい
-- 以下と同等なはず...
for i=0, 0, 0 do
local a=math.random(0, 5)
if a == 5 then
break
end
end
while
とrepeat
でも、途中で中断するときはbreak
を使います。