LuaをC ++で使用するのは思ったより簡単です。 LuaBridgeによるチュートリアル

この記事は、もともと英語で書いたチュートリアルの翻訳です。 ただし、この翻訳には、オリジナルに対する追加と改善が含まれています。

チュートリアルではLuaの知識は必要ありませんが、C ++はベースよりもわずかに高いレベルで認識される必要がありますが、ここには複雑なコードはありません。



私はかつてLua C APIを使用してC ++でLuaを使用することに関する記事を書きまし 。 単純な変数と関数をサポートするLuaの単純なラッパーの作成は簡単ですが、より複雑なもの(関数、クラス、例外、名前空間)をサポートするラッパーの作成はすでに困難です。

LuaとC ++の使用については、かなり多くのことが書かれています。 それらの多くはここで見つけることができます

私はそれらの多くをテストしましたが、ほとんどすべてがLuaBridgeが好きでした。 LuaBridgeには多くの機能があります。ユーザーフレンドリーなインターフェイス、例外、名前空間などです。

順番に始めましょう。なぜC ++でLuaを使用するのですか?





Luaを使用する理由





構成ファイル。 定数、マジックナンバー、およびいくつかのdefine'ovを取り除く




これらのことは、単純なテキストファイルを使用して行うことができますが、使用するのはそれほど便利ではありません。 Luaでは、テーブル、数式、コメント、条件、システム関数などを使用できます。構成ファイルの場合、これは非常に便利です。

たとえば、次の形式でデータを保存できます。



window = { title = "Test project", width = 800, height = 600 }
      
      







システム変数を取得できます:

 homeDir = os.getenv("HOME")
      
      







数式を使用してパラメーターを設定できます。

 someVariable = 2 * math.pi
      
      







スクリプト、プラグイン、プログラム機能の拡張




C ++はLua関数を呼び出すことができ、LuaはC ++関数を呼び出すことができます。 これは非常に強力な機能であり、コードの一部をスクリプトに入れたり、ユーザーがプログラムの機能を拡張する独自の機能を記述したりすることができます。 開発中のゲームのさまざまなトリガーにLua関数を使用します。 これにより、C ++で新しい関数とクラスを再コンパイルおよび作成せずに、新しいトリガーを追加できます。 とても快適です。



Luaについて少し。 Luaは、非商用および商用アプリケーションの両方で使用できるようにするMITライセンスの言語です。 LuaはCで書かれているため、LuaはほとんどのOSで動作するため、クロスプラットフォームアプリケーションで問題なくLuaを使用できます。



LuaとLuaBridgeをインストールする





それでは始めましょう。 始めるには、 LuaLuaBridgeをダウンロードしてください。

インクルードLuaフォルダーとLuaBridge自体をプロジェクトのインクルードディレクトリに追加します。

また、リンク用のライブラリのリストにlua52.libを追加します。



次の内容でscript.luaファイルを作成します。

 -- script.lua testString = "LuaBridge works!" number = 42
      
      







main.cppを追加します(このコードは、すべてが機能することを確認するためのものです。説明は少し下になります)。

 // main.cpp #include <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } using namespace luabridge; int main() { lua_State* L = luaL_newstate(); luaL_dofile(L, "script.lua"); luaL_openlibs(L); lua_pcall(L, 0, 0, 0); LuaRef s = getGlobal(L, "testString"); LuaRef n = getGlobal(L, "number"); std::string luaString = s.cast<std::string>(); int answer = n.cast<int>(); std::cout << luaString << std::endl; std::cout << "And here's our number:" << answer << std::endl; }
      
      







プログラムをコンパイルして実行します。 以下が表示されるはずです。



LuaBridgeは動作します!

そしてここに私たちの番号があります:42



:プログラムがコンパイルされず、コンパイラがLuaHelpers.hファイルのエラー「エラーC2065: 'lua_State':undeclared identifier」について文句を言う場合、以下を実行する必要があります。

1)これらの行をLuaHelpers.hファイルの先頭に追加します

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





2) これからStack.hの460行目を変更します。

 lua_pushstring (L, str.c_str(), str.size());
      
      





これに:

 lua_pushlstring (L, str.c_str(), str.size());
      
      







できた!



そして、コードの仕組みについて詳しく説明します。



必要なすべてのヘッダーを含めます。

 #include <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" }
      
      







LuaBridgeのすべての関数とクラスは名前空間luabridgeに配置され、「luabridge」を何度も記述しないように、この構造を使用します(ただし、LuaBridge自体が使用される場所に配置することをお勧めします)



 using namespace luabridge;
      
      







lua_Stateを作成する



 lua_State* L = luaL_newstate();
      
      







スクリプトを開きます。 スクリプトごとに、新しいlua_Stateを作成する必要はありません。1つのlua_Stateを多くのスクリプトに使用できます。 この場合、グローバル名前空間内の変数の衝突を考慮する必要があります。 script1.luaとscript2.luaで同じ名前の変数が宣言されている場合、問題が発生する可能性があります



 luaL_dofile(L, "script.lua");
      
      







メインのLuaライブラリ(io、mathなど)を開き、スクリプトのメイン部分を呼び出します(つまり、グローバルネームスペースのスクリプトでアクションが指定されている場合、アクションが実行されます)



 luaL_openlibs(L); lua_pcall(L, 0, 0, 0);
      
      







Lua変数が保存できるすべてのもの(int、float、bool、string、tableなど)を保存できるLuaRefオブジェクトを作成します。

 LuaRef s = getGlobal(L, "testString"); LuaRef n = getGlobal(L, "number");
      
      







LuaRefをC ++型に変換するのは簡単です:

 std::string luaString = s.cast<std::string>(); int answer = n.cast<int>();
      
      







エラーの確認と修正





ただし、問題が発生する可能性があり、エラーをチェックして処理する価値があります。 最も重要でよくある間違いを考慮してください



Luaスクリプトが見つからない場合はどうなりますか?




 if (luaL_loadfile(L, filename.c_str()) || lua_pcall(L, 0, 0, 0)) { ... //    }
      
      







変数が見つからない場合はどうなりますか?




変数が宣言されていないか、その値がnilです。 これはisNil()関数で簡単に確認できます。

 if (s.isNil()) { std::cout << "Variable not found!" << std::endl; }
      
      







変数は、受け取る予定のタイプではありません



たとえば、変数のタイプは文字列であることが予想される場合、キャストを実行する前にこのチェックを実行できます。



 if(s.isString()) { luaString = s.cast<std::string>(); }
      
      







テーブル





テーブルは単なる配列ではありません。テーブルは、あらゆるタイプのLua変数、他のテーブルを格納し、値と変数に従って異なるタイプのキーを配置できる素晴らしいデータ構造です。 テーブルを使用すると、美しく見やすい形式で構成ファイルを提示および受信できます。



次のコンテンツでscript.luaを作成します。



 window = { title = "Window v.0.1", width = 400, height = 500 }
      
      







このスクリプトからデータを取得できるC ++コード:



 LuaRef t = getGlobal(L, "window"); LuaRef title = t["title"]; LuaRef w = t["width"]; LuaRef h = t["height"]; std::string titleString = title.cast<std::string>(); int width = w.cast<int>(); int height = h.cast<int>(); std::cout << titleString << std::endl; std::cout << "width = " << width << std::endl; std::cout << "height = " << height << std::endl;
      
      







画面に次が表示されます。

ウィンドウv.0.1

幅= 400

高さ= 500



ご覧のとおり、[]演算子を使用してさまざまなテーブル要素を取得できます。 もっと短く書くことができます:

 int width = t["width"].cast<int>();
      
      







テーブルの内容を変更することもできます。

 t["width"] = 300
      
      







これにより、スクリプトの値は変更されず、プログラムの実行中に含まれる値のみが変更されます。 つまり 次のことが起こります。

 int width = t["width"].cast<int>(); // 400 t["width"] = 300 width = t["width"].cast<int>(); // 300
      
      







値を保持するには、テーブルのシリアル化を使用する必要がありますが、このチュートリアルはそれについてではありません。



テーブルは次のようになります。

 window = { title = "Window v.0.1", size = { w = 400, h = 500 } }
      
      







window.size.wの値を取得するにはどうすればよいですか?

このように:

 LuaRef t = getGlobal(L, "window"); LuaRef size = t["size"]; LuaRef w = size["w"]; int width = w.cast<int>();
      
      







機能





C ++で簡単な関数を書きましょう

 void printMessage(const std::string& s) { std::cout << s << std::endl; }
      
      







そして、これをLuaのスクリプトで記述します。

 printMessage("You can call C++ functions from Lua!")
      
      







次に、関数をC ++で登録します

 getGlobalNamespace(L). addFunction("printMessage", printMessage);
      
      







注1 :これは、「luaL_dofile」を呼び出す前に実行する必要があります。そうしないと、Luaは未宣言の関数を呼び出そうとします

注2 :C ++およびLuaの関数は異なる名前を持つ場合があります。



このコードは、関数をグローバル名前空間Luaに登録しました。 たとえば、名前空間「game」に登録するには、次のコードを記述する必要があります。

 getGlobalNamespace(L). beginNamespace("game") .addFunction("printMessage", printMessage) .endNamespace();
      
      







次に、スクリプト内のprintMessage関数を次の方法で呼び出す必要があります。

 game.printMessage("You can call C++ functions from Lua!")
      
      







Lua名前空間は、C ++名前空間とは関係ありません。 むしろ、論理的な統合と利便性のために使用されます。



次に、C ++からLua関数を呼び出します

 -- script.lua sumNumbers = function(a,b) printMessage("You can still call C++ functions from Lua functions!") return a + b end
      
      







 // main.cpp LuaRef sumNumbers = getGlobal(L, "sumNumbers"); int result = sumNumbers(5, 4); std::cout << "Result:" << result << std::endl;
      
      







以下が表示されるはずです。

Lua関数からC ++関数を呼び出すことができます!

結果:9



それは素晴らしいことではありませんか? LuaBridgeに関数が持つ引数の数と引数、および返す値を伝える必要はありません。

ただし、1つの制限があります。1つのLua関数に8つ以上の引数を含めることはできません。 ただし、この制限は、テーブルを引数として渡すことで簡単に回避できます。



必要以上の引数を関数に渡すと、LuaBridgeはそれらを静かに無視します。 ただし、何か問題が発生した場合、LuaBridgeはLuaExceptionをスローします。 それをキャッチすることを忘れないでください! したがって、コードをtry / catchブロックで囲むことをお勧めします。



以下に、関数を含む完全なサンプルコードを示します。



 -- script.lua printMessage("You can call C++ functions from Lua!") sumNumbers = function(a,b) printMessage("You can still call C++ functions from Lua functions!") return a + b end
      
      







 // main.cpp #include <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } using namespace luabridge; void printMessage(const std::string& s) { std::cout << s << std::endl; } int main() { lua_State* L = luaL_newstate(); luaL_openlibs(L); getGlobalNamespace(L).addFunction("printMessage", printMessage); luaL_dofile(L, "script.lua"); lua_pcall(L, 0, 0, 0); LuaRef sumNumbers = getGlobal(L, "sumNumbers"); int result = sumNumbers(5, 4); std::cout << "Result:" << result << std::endl; system("pause"); }
      
      







なに? 他に何かありますか?





はい チュートリアルの次の部分で説明する他の素晴らしいものがいくつかあります:クラス、オブジェクトの作成、オブジェクトの寿命...たくさん!

また、 このdevログを読むことをお勧めします 。ゲームでスクリプトをどのように使用するかについて話しましたが、実用的な例は常に役立ちます。



All Articles