Luaが出てきてから久しいがそういえばここ3~4年くらい触っていない。最新のLua環境ってどうなってるのか調べてみた。1994年に初版がリリースされてからもう20年近く経ってることになる。触りだしたのが2006年以降なので、ちょうどver5.1くらいか。で、そこから10年経つけどメジャーバージョンアップなしに、ちょうど昨年5.3にバージョンアップしたらしい。
Lua 1.1 | 1994 | 1.0はリリースしていない |
Lua 5.1 | 2006 | ガベージコレクションができたよ |
Lua 5.2 | 2011 | gotoのサポート |
Lua 5.3 | 2015 | intが64bitになったよ / UTF-8をサポート |
大きな変更はないにしろ4年ぶりの更新なのでCライブラリ側でも最新環境にしておくことにする。
Luaのダウンロード
https://www.lua.org/download.html
便利そうでバグもそれなりに取れて安定してそうなLua。長年使いドコロを考えてきたけど今だに「コレだ!」っていう使い方が見つからない。
「ゲームのステージ構成を作るのに便利ですよ!」っていうのを聞いたり、あの有名タイトルでもこのタイトルでも使われてるよ!っていうのはよく聞くけど、正直言ってどこが便利かわからないところがある。プランナーがリコンパイルなしに、ステージをプログラミングできるところがいいところだという。しかし、プランナーはプログラミングしないじゃん。
結局プログラムが必要なところはプログラマが触るんだったら、きちんとしたデバッガーがあるC言語環境の方がいい。ブレークポイントに変数ウォッチ、ステップ実行、エディット&コンティニューがあればこそ便利なことは多々あるが、それらがないLua環境で色々複雑な仕組みを作った結果「何かわからないけどバグった、プログラマーさん調べて」ってなるよね。
コンパイルしなくていいのは同意。高いコンパイラを買ったりしなくていいし、小難しい環境設定をしなくていいのも魅力。結局環境をLuaで代用できるから安く済む、っていう経営側のメリットはあれど、開発に有効なことってなんだろうな。
なんだかんだ言ってるけどLuaの便利さの恩恵に預かりたいのでずっと使い方を考えてるんだけど、使い古したノートPCの使いみちみたいに「何かありそうで何も思いつかない」のを歯がゆく思っている。
Luaの最大の特徴がテーブルらしいんだけど、C言語でいうところの構造体のようなテーブルを作って配列にして管理っていうのができない?弾幕とかどうやって管理したらいいのか。インデックスが1始まりなのも、移植するのに不便。for文のループをスキップするためのcontinueがないとか、もうここまで自分とLuaと相性が悪いとどうしてこんな言語仕様にしたのか全く理解に苦しむので、これを読んでみてる。
[[Programming in Lua プログラミング言語Lua公式解説書]>https://www.amazon.co.jp/dp/4048677977]
Luaを作った博士の本なら博士の気持ちが何かわかるかもしれない。
C言語のソースに直接シナリオデータをいれこむといった最悪の事態を考えれば、Luaを使ってLuaスクリプトでできる範囲のことでシナリオを構築するのも悪く無い。しかし、ADV専用のスクリプトエンジンはLuaよりもずっと汎用性が低い分、可読性にすぐれた書式になっていてADVに向いている気もする。スクリプトに入れ込む変数の管理や制御でバグらせてしまうのもスクリプトで複雑なことができすぎるからに他ならない。そういう意味でLuaはオーバースペックだと思う。
これは悪くなさそうである。プログラムで座標をエディット&コンティニューを駆使してパーツを並べるよりは、Luaで誰かがちまちまパーツを並べながらウインドウの位置調整や文言の中身をいじるほうが効率がいい。実際仕事で使われているのを初めてみたのもメニューだった。これはメニューのプログラムを実行ファイルに含めると実行ファイルサイズが膨れ上がってメモリを圧迫するから、メニュー一般のプログラムを「データとして」外にだしとくことが目的だったが。
ただメニューっていうのは、押されたボタンや表示する内容、また遷移先がプログラムと密接に絡んでいるので、メニュー全般をLuaにすることはオススメできない。あくまでも表示位置の調整のみ、プログラムでいうとウインドウの表示位置のテーブルをCSVじゃなくてLuaで書いてもらう、といったイメージに近い。そうなるとそれはそれで、プランナーに投げられる仕事が少ないので、設計するだけプログラマが面倒な仕事を抱えることになる。
あとLuaのスクリプト中に直接テキストを入れ込むと海外版とかを作るときに、マルチランゲージ対応が難しい。やっぱり現実味がないか・・・?
結局できることがたくさんある分、立派なデバッガが必要だ。複雑なことをして効率化するのは悪いことじゃないが、それで出てきたわかりにくいバグも責任持って取ってもらえないと複雑になるのは歓迎できないし、バグらせにくくするかわりにシンプルな設計にするならLuaじゃなくて専用ADVエンジンとかCSVの方がいい。
プログラミングすると必ずバグる、それをどうやって抑えこむか、っていうところにフォーカスしてしまうと、やっぱり使いドコロに悩むなあ。
で、Luaスクリプトをプログラムで読めるようにしておくわけだけど、特に難しいことはない。ダウンロードしてきたソースをVCに突っ込めば、マルチプラットフォームで簡単にコンパイルできる。1つ注意が必要なのはLuaはそれ自信がコマンドラインで動くアプリなのでライブラリではない。
なので、main関数が存在する。だからLua.cのmain()関数の名前を別のmain2とか適当に変えてあげないと、自分のmain関数とバッティングするのでそこだけ注意。
main() ↓ main2()
そして自前のC言語プログラムから使うために「lua.hpp」をincludeすること
#include "lua.hpp"
個別にincludeする場合はこっち。上と同じ。
extern "C" { #include "lua.h" #include "lualib.h" #include "lauxlib.h" }
lua_getglobal( LuaState , "Luaの関数名"); lua_pcall(LuaState, 引数の数, 戻り値の数, 0);
最初になんらかのLuaの関数を呼び出さないと何も始まらないのでコレは必須。
Luaの関数"dqDraw"とCの関数「dqDraw」を関連付ける
lua_register( LuaState , "dqDraw" , &dqDraw );
Cのプログラム的には指定した関連付けられた関数がコールバックされるイメージ。
ただしコールバックされる関数のカタチは決まっているので、引数でやり取りするときにはLua側で配列などにデータをのっけて、C側ではスタックから、データを引っ張ってくる。Luaの面倒なところではある。ただスタックに慣れてくるとコレしかやり取りできない分、それなりに安心感はある。
呼び出されるCの関数のカタチはコレ
int dqDraw( lua_State *L );
include"dqLib.h"に相当するLuaのコードは「拡張子を書かない」ことに注意
require "dqLib"
関数形式でも使える
require("dqLib")
仮想のVRAM(256x256)の画面を想定した配列を作ってアクセスする
main.cpp
main() { LuaState = luaL_newstate(); luaL_dofile(LuaState, "sample.lua"); luaL_openlibs( LuaState ); lua_getglobal(LuaState, "gameInit"); lua_pcall(LuaState, 0, 0, 0); }
sample.lua
function gameInit() for y=0,159,1 do for x=0,159,1 do VideoRAM[ y*256 + x + 1 ] = 0xff00ff00; end end end
これでもいい
VideoRAM = { 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, ~ 0,0,0,0,0,0,0,0,0,0, --256個書く . . 256段書く }:
読み取り(main.cpp)
read() { lua_getglobal( LuaState, "VideoRAM" ); for (Sint32 y = 0; y < 256; y++) { for (Sint32 x = 0; x < 256; x++) { n = x + 1 +y * 160; lua_rawgeti(LuaState, -1 , n ); argb = lua_tonumber(LuaState, -1); lua_pop(LuaState , 1 ); } }
久しぶりにやったけどはっきり言って色々面倒くさい。ここまでたどり着くのにだいぶ試行錯誤した。ポイントは
luaではビット演算ができない。それが通説。しかし、5.2でbit32っていうライブラリが追加されてANDやORが取れるようになった。
C言語でいうところのAND演算をbit32で書く
a = b&0x01; → a = 32bit.band(b,0x01)
で、さっそくやってみたけど、「STRING sample.lua:15: attempt to index a nil value (global 'bit32')」がでてきて、どうにもうまく使えない。Luaのバージョン5.2で実装されて、今使ってるのが5.3、同じ症状で悩んでいるヒトは5.1だから使えない、という情報があった。5.2で実装されてるはずなのになぁ、と思いつつ、ソースを見ると「LUA_COMPAT_5_2」という定義で、bit32ライブラリがコンパイル対象に「入らない」ようになってた。
え!?5.2互換にする定義があるってことは、さっそく5.3で無くなったってこと???
と思って5.3のリファレンスを眺めてみたら、ビット演算子がサポートされてる・・・。
ただしif( )の中の真偽判定がCと異なるため以下は意図した動きをしない。
if( 0x03&0x01 ) Cだとtrue → if( 0x03&0x01) Luaだとfalse
この場合明示的に「if((0x03&0x01) == 0x01)」というふうに判定してあげなくてはならない。なぜかというとif(0) がtrueになるからである。全く考え方がわからなかったので「Programming in Lua」を参照すると「falseかnil以外はtrue」となる仕様らしい。ゼロは数値(0)も文字("0")と同じくtrueという扱いになる、という考え方。zero == falseと決め込んでいたC頭ではなかなか直感でココにきづけなかった。
こっちは同じand(&)でも関係ない
if( a && b ) c = 0; → if( a and b ) then c = 0 end
あとで定義された関数で上書きされる。連想配列的に以降もオーバーロードは実装されないかもしれない
ない。しかし http://qiita.com/MOKYN/items/feec4678ee57a0e2c7d9ココが分かりやすかった
A and B : A が false か nil ならAを返し、そうでなければBを返す。 A or B : A が false か nil 以外ならAを返し、そうでなければBを返す。
「Programming Lua」に書いてあった仕様をうまく使うと三項演算子っぽく使えるらしい。正直仕様だけ読んだ時にはまったくもって「?」だったが使う人が使うと便利に使えるんですね・
function GetAbs( n ) -- //----------------------------- -- //絶対値を求める -- //----------------------------- -- if ( n < 0 ) then -- return n*-1; -- else -- return n; -- end -- 三項演算子っぽく書こうと思うとこうなるらしい n = ( n < 0 ) and n*1 or n*1 end
!= を使いたいときは「~=」をつかうといいニョロ
テーブルの値を複製して加工するとき以下のように書くと参照元のテーブルの中身も書き換えてしまう
tbl = { a,b,c = 0,0,0; } test = tbl; test.a = 1; 結果 tbl.a => 1 test.a => 1
これを、以下のように記述するとテーブルの中身をコピーして使うことができる。
tbl = { a,b,c = 0,0,0; } test = {tbl}; test.a = 1; 結果 tbl.a => 0 test.a => 1
Cの構造体の配列を作って初期化したい時と同じような局面では、これを応用するといいっぽい。弾幕作ろうと思うとどうしても構造体の配列が必要になるのでコレは助かる。なぜこれでいけるかは、実はまだよくわかっていない。
else if は 「elseif」
条件
( a == b) --> ( a == b ) ( a != b) --> ( a ~= b ) ( a && b) --> ( a and b ) ( a || b) --> ( a or b )
気をつけ無くてはならないのは、aがゼロだった時にif( a ) がfalseになりそうにみえること。if( 0 ) はLuaではtrueである。falseになるのはif( false )か、if( nil )の時だけと「Programming Lua」に書いてあった。
a ++; b --; は使えないので a = a + 1; b = b - 1;に書き換える
そもそも、Luaにはcontinueがない。しかし、なんと5.2からgotoが実装されたらしい、助かった!以下のようにすればfor文をスルーできた
for ii=0, 10 do if( ii< 5 ) goto CONTINUE; x = x + 1; CONTINUE: end
ちなみにforは上記の例だと11回まわるので注意。Cで書くとこうなるのと同じらしい、オリジンが1(配列の添字が0スタートじゃなくて1スタート)なので、iiの初期値を1にしてあげればいつものC言語の間隔に近くなるはず。
for( int ii=0; ii<=10; ii++ )
Luaにはswitchに該当するものがない。え!そうなんだ。しかたがないのでif ~ elseで代用しよう
こういう書き方はできない。しかし
a,b,c = 0,0,0;
こうかける。
複数の戻り値をもどせる便利な機能はCのポインタ渡しで値を書き換えてほしい時に有効
dqBool GetStylus( int *px , int *py ) { *x = 1; *y = 2; retun dqTrue; } ret = GetStylus( &x , &y );
こう書き換えられる
function GetStylus( x , y ) return dqTrue , 1,2; end ret,px,py = GetStylus( px , py );
attempt to index 変数名, a nil value(変数がnilかもよ)って言うエラーをおいかけて、さんざん試行錯誤したけど、結果的にいうと全く関係なかった。どうもエラーメッセージについては、概ね問題なくて、ほとんどあってるけど、場合によりバグることもある。まあLuaに限らないので他のバグが引き起こした勘違いかもしれないので、他をあたってみる。
今回の場合はコレを直すとこの問題が出なくなった
for i=1,i<36-1 do end
Cのfor文を移植してきた時の名残でコレが正しい。
for i=1,36-1 do
コードがどう考えてもおかしくない時は、その他の場所も疑ってみよう。