Luaの現状

Luaが出てきてから久しいがそういえばここ3~4年くらい触っていない。最新のLua環境ってどうなってるのか調べてみた。1994年に初版がリリースされてからもう20年近く経ってることになる。触りだしたのが2006年以降なので、ちょうどver5.1くらいか。で、そこから10年経つけどメジャーバージョンアップなしに、ちょうど昨年5.3にバージョンアップしたらしい。

Lua 1.119941.0はリリースしていない
Lua 5.12006ガベージコレクションができたよ
Lua 5.22011gotoのサポート
Lua 5.32015intが64bitになったよ / UTF-8をサポート

大きな変更はないにしろ4年ぶりの更新なのでCライブラリ側でも最新環境にしておくことにする。

Luaのダウンロード
https://www.lua.org/download.html

しかし、Luaの使いみちがよくわからない

便利そうでバグもそれなりに取れて安定してそうなLua。長年使いドコロを考えてきたけど今だに「コレだ!」っていう使い方が見つからない。
「ゲームのステージ構成を作るのに便利ですよ!」っていうのを聞いたり、あの有名タイトルでもこのタイトルでも使われてるよ!っていうのはよく聞くけど、正直言ってどこが便利かわからないところがある。プランナーがリコンパイルなしに、ステージをプログラミングできるところがいいところだという。しかし、プランナーはプログラミングしないじゃん。

結局プログラムが必要なところはプログラマが触るんだったら、きちんとしたデバッガーがあるC言語環境の方がいい。ブレークポイントに変数ウォッチ、ステップ実行、エディット&コンティニューがあればこそ便利なことは多々あるが、それらがないLua環境で色々複雑な仕組みを作った結果「何かわからないけどバグった、プログラマーさん調べて」ってなるよね。

コンパイルしなくていいのは同意。高いコンパイラを買ったりしなくていいし、小難しい環境設定をしなくていいのも魅力。結局環境をLuaで代用できるから安く済む、っていう経営側のメリットはあれど、開発に有効なことってなんだろうな。

なんだかんだ言ってるけどLuaの便利さの恩恵に預かりたいのでずっと使い方を考えてるんだけど、使い古したノートPCの使いみちみたいに「何かありそうで何も思いつかない」のを歯がゆく思っている。

自分でADVゲームのスクリプトエンジンをかくことを考えればLuaでかけるのは便利?

C言語のソースに直接シナリオデータをいれこむといった最悪の事態を考えれば、Luaを使ってLuaスクリプトでできる範囲のことでシナリオを構築するのも悪く無い。しかし、ADV専用のスクリプトエンジンはLuaよりもずっと汎用性が低い分、可読性にすぐれた書式になっていてADVに向いている気もする。スクリプトに入れ込む変数の管理や制御でバグらせてしまうのもスクリプトで複雑なことができすぎるからに他ならない。そういう意味でLuaはオーバースペックだと思う。

メニューを作るのはどうか?

これは悪くなさそうである。プログラムで座標をエディット&コンティニューを駆使してパーツを並べるよりは、Luaで誰かがちまちまパーツを並べながらウインドウの位置調整や文言の中身をいじるほうが効率がいい。実際仕事で使われているのを初めてみたのもメニューだった。これはメニューのプログラムを実行ファイルに含めると実行ファイルサイズが膨れ上がってメモリを圧迫するから、メニュー一般のプログラムを「データとして」外にだしとくことが目的だったが。

ただメニューっていうのは、押されたボタンや表示する内容、また遷移先がプログラムと密接に絡んでいるので、メニュー全般をLuaにすることはオススメできない。あくまでも表示位置の調整のみ、プログラムでいうとウインドウの表示位置のテーブルをCSVじゃなくてLuaで書いてもらう、といったイメージに近い。そうなるとそれはそれで、プランナーに投げられる仕事が少ないので、設計するだけプログラマが面倒な仕事を抱えることになる。
あとLuaのスクリプト中に直接テキストを入れ込むと海外版とかを作るときに、マルチランゲージ対応が難しい。やっぱり現実味がないか・・・?

結局デバッガ

結局できることがたくさんある分、立派なデバッガが必要だ。複雑なことをして効率化するのは悪いことじゃないが、それで出てきたわかりにくいバグも責任持って取ってもらえないと複雑になるのは歓迎できないし、バグらせにくくするかわりにシンプルな設計にするならLuaじゃなくて専用ADVエンジンとかCSVの方がいい。

プログラミングすると必ずバグる、それをどうやって抑えこむか、っていうところにフォーカスしてしまうと、やっぱり使いドコロに悩むなあ。

構造体が使えない

Luaの最大の特徴がテーブルらしいんだけど、C言語でいうところの構造体のようなテーブル定義ができない、インデックスが1始まりなのも、移植するのに不便。もうここまで自分とLuaと相性が悪いとどうしてこんな言語仕様にしたのか全く理解に苦しむので、これを読んでみてる。

[[Programming in Lua プログラミング言語Lua公式解説書]>https://www.amazon.co.jp/dp/4048677977]

作ったヒトの本なら何か気持ちがわかるかもしれない。

とりあえず、いつでも使えるようにしとく

で、Luaスクリプトをプログラムで読めるようにしておくわけだけど、特に難しいことはない。ダウンロードしてきたソースをVCに突っ込めば、簡単にコンパイルできる。1つ注意が必要なのはLuaはそれ自信がコマンドラインで動くアプリなのでライブラリではない。
なので、main関数が存在する。だからLua.cのmain()関数の名前を別のmain2とか適当に変えてあげないと、自分のmain関数とバッティングするのでそこだけ注意。

main()
↓
main2()

includeファイルは?

そして自前のC言語プログラムから使うために「lua.hpp」をincludeすること

#include "lua.hpp"

個別にincludeする場合はこっち。上と同じ。

extern "C" {
 #include "lua.h"
 #include "lualib.h"
 #include "lauxlib.h"
}

Luaの関数を呼び出す

lua_getglobal( LuaState , "Luaの関数名");
lua_pcall(LuaState, 引数の数, 戻り値の数, 0);

Luaから関数を呼び出される

Luaの関数"dqDraw"とCの関数「dqDraw」の関連付け

lua_register( LuaState , "dqDraw" , &dqDraw );

呼び出されるCの関数のカタチはコレ

int dqDraw( lua_State *L );

Luaファイル同士でのinclude

include"dqLib.h"に相当するLuaのコードは「拡張子を書かない」ことに注意

require "dqLib"

関数形式でも使える

require("dqLib")

CとLuaで配列をやり取りする

仮想の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 < 160; y++)
{
 for (Sint32 x = 0; x < 160; x++)
 {
  n = x + 1 +y * 160;
  lua_rawgeti(LuaState, -1 , n );
  argb = lua_tonumber(LuaState, -1);
  lua_pop(LuaState , 1 );
 }
}

久しぶりにやったけどはっきり言って色々面倒くさい。ここまでたどり着くのにだいぶ試行錯誤した。ポイントは

  • 256x256の空間を想定しても、空の配列は作れない
  • 連想配列なので定義したデータが3つしかなければ3つしか返ってこない
  • lua_rawgetiで配列は取得できるが、定義した順に取得してしまう
  • lua_rawgetiした後は、スタックが1つ増えてるので参照後にpopしないとテーブルのスタック番号を-1で参照できなくなる
    などなどあって、結局「空」にならないように順番だてて配列の中身を初期化してやる必要がある。なので、最初に全部for文で初期化するか、ベタなテーブルをつくっておいてやることにした。