スクリプトとは何で、何を使って食べるのか-Lua&C ++

こんにちは、Habrahabr!

スクリプトでこのトピックを書くことにしました



何を知る必要がありますか?





ゲームダイアログを.cppファイルに記述することが大きな間違いだった理由



大規模なプロジェクト(たとえば、大規模なゲーム)を開発した場合、新しい100行のコードごとにコンパイルが遅くなることに気づきましたか?

ゲームはより多くの武器、より多くのダイアログ、より多くのメニューなどを作成します。

イノベーションに関連して発生する最も重要な問題の1つは、無数の武器とバッジというかなり難しいタスクをサポートすることです。

会話を変更したり、新しいタイプの武器を追加したりするために友人/上司/パートナーからの要求に時間がかかりすぎる状況では、いくつかの手段に頼らなければなりません。たとえば、このすべてのゴミを別々のテキストファイルに記録します。

ほとんどすべてのゲーム開発者は、個別のテキストファイルでレベルマップまたはダイアログを作成し、それらを読み取りました。 少なくとも最も単純なオプション-入力ファイルを使用したコンピューターサイエンスのオリンピアードタスク



しかし、上記の方法-スクリプトを使用する方法があります。



問題解決



「わかりました、そのようなことについては、プレーヤーの特性の説明を含む通常のファイルで十分です。 しかし、急速に発展しているプロジェクトでほぼ毎日、メインプレーヤーのロジックをわずかに変更する必要がある場合、どうすればプロジェクトを何度もコンパイルする必要がありますか?」

いい質問です。 この場合、ゲームのすべての特性またはその他の部分でプレイヤーのロジックをサポートするスクリプトが私たちの助けになります。

当然、プレーヤーのロジックを何らかのプログラミング言語のコードの形式で保持するのが最も便利です。

最初の考えは、スクリプト言語の独自のインタープリターを作成して、数秒で脳から排除することです。 プレーヤーのロジックは、このような恐ろしいコストの価値はありません。

幸いなことに、テキストファイルを受け入れて実行するC ++用の特別なスクリプト言語ライブラリがあります。



そのようなスクリプト言語Luaの1つについて説明します。



どのように機能しますか?



始める前に、スクリプト言語の仕組みを理解することが重要です。 実際のところ、for、while、if、およびその他の構造が存在する場合、スクリプト言語にはほとんど機能がありません。

これらは主に、コンソールにテキストを出力するための関数、数学関数、およびファイルを操作するための関数です。

スクリプトを使用してプレーヤーを制御するにはどうすればよいですか?



私たちはC ++で-プログラムはいくつかの機能を実行し、スクリプト内の何らかの名前でそれらを「登録」し、スクリプト内で呼び出します。 つまり、SetPos(x、y)関数を登録してC ++プログラムでのプレーヤーの位置を決定した場合、スクリプトでこの関数に遭遇すると、スクリプト言語ライブラリの「インタープリター」はもちろんC ++プログラムでこの関数を呼び出します。すべてのメソッドを転送します。

すごいね :)



UPD:注意! 1人のユーザーが、コードをアップロードしたときにすべてのエラーを完全に排除できなかったというメールで私に連絡しました-habrahabr.ru/post/196272/#comment_6850016

バグはHabrの許可を得てコードに侵入しました

次のようなコードのセクションを置き換えます

template<class t> T MethodName();
      
      







  template<class T> T MethodName();
      
      





そして、lua_CFunctionの代わりに、lua_cfunctionはスキップします

よろしくお願いします!



準備ができました!



スクリプトプログラミング言語の利点を理解したら、始めましょう!

libとincud Luaをgithub(トピックの下部)のリポジトリからダウンロードするか、公式Webサイトで入手してください。



Visual StudioでコンソールプロジェクトまたはWin32(重要ではありません)を作成します(バージョン2012があります)



[プロジェクト]-> [プロパティ]-> [構成プロパティ]-> [VC ++ディレクトリ]に移動し、「インクルードディレクトリ」および「ライブラリディレクトリ」に、それぞれリポジトリからIncludeおよびLibフォルダーを追加します。



次に、main.cppファイルを作成して書き込みます。

 int main() { return 0; }
      
      





ご想像のとおり、コンソールアプリケーションがあります。



今、コーディングに行きます



すべての瞬間を注意深く説明することを約束します



スクリプトは、スクリプトクラスが担当します。 Script.h / .cppで関数を同時に宣言および実装します

Script.cppを作成して書き込みます

 #include "Script.h"
      
      





Script.hを作成して書き込みます

 #ifndef _SCRIPT_H_ #define _SCRIPT_H_ #endif
      
      





2行後、#endifの前にスクリプトのクラスを定義します

このコードは、ファイルの相互インクルードを防ぐために書かれています。 Game.hファイルにはScript.hが含まれており、Script.hファイルにはGame.hが含まれているとしましょう。 このコードでは、インクルードは1回のみ実行されます



このコード内に次のように記述します

 #pragma comment(lib,"lua.lib") extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> }
      
      





最初の行は、lua.lib自体をアーカイブから接続します。

extern“ C”とは何ですか? 実際のところ、luaはCで記述されているため、ライブラリを接続するにはこのようなコードが必要です。



次は、コンソールを操作するための多くのファイルへの既知の接続です

 #include <stdio.h> #include <iostream> #include <sstream> using namespace std;
      
      







それではクラス定義に取りかかりましょう

 class Script {
      
      





C ++のLuaライブラリの最も重要なオブジェクトはlua_Stateで、スクリプトの実行に必要です

 private: lua_State *lua_state;
      
      





次はパブリック関数です

 public: void Create();
      
      





この関数はlua_Stateを初期化します



作成()
Script.cppでのその定義



 void Script::Create() { lua_state = luaL_newstate(); static const luaL_Reg lualibs[] = { {"base", luaopen_base}, {"io", luaopen_io}, {NULL, NULL} }; for(const luaL_Reg *lib = lualibs; lib->func != NULL; lib++) { luaL_requiref(lua_state, lib->name, lib->func, 1); lua_settop(lua_state, 0); } }
      
      





lua_Stateを初期化する最初の行。

次に、「接続ライブラリ」のリストを宣言します。 実際のところ、luaの「純粋な」形式にはprint()関数しかありません。 数学およびその他の関数の場合、特別なライブラリを接続してから、それらをmath.foo、base.foo、io.fooとして呼び出す必要があります。 他のライブラリを接続するには、たとえば「 "math"、luaopen_math}」などのlualibsを追加します。 すべてのライブラリ名はluaopen _...で始まり、lialibsの最後は{NULL、NULL}でなければなりません

  void Close();
      
      





この機能により、Luaリソースが解放されます。

閉じる()
彼女の定義

 void Script::Close() { lua_close(lua_state); }
      
      





lua_close()を使用するだけです

 int DoFile(char* ScriptFileName);
      
      





そして、この関数はファイルを実行します。 入力では、ファイルの名前(たとえば、「C:\\ script.lua」)を使用します。

なぜintを返すのですか? 一部のスクリプトにはreturnが含まれている場合があり、スクリプトを中断して値を返します。



Dofile()
彼女の定義

 int Script::DoFile(char* ScriptFileName) { luaL_dofile(lua_state,ScriptFileName); return lua_tointeger(lua_state, lua_gettop(lua_state)); }
      
      





ご覧のとおり、スクリプトを実行してintを返します。 しかし、関数はintだけでなくboolとchar *も返すことができます、私は常に数字を返すだけです(lua_toboolean、lua_tostring)





次に、定数(数値、文字列、関数)を登録する関数を作成します

  template<class t> void RegisterConstant(T value, char* constantname);
      
      



RegisterConstant()
パターンを通して行動します。 関数呼び出しの例:
 RegisterConstant<int>(13,"goodvalue");
      
      





彼女の定義

 template<> void Script::RegisterConstant<int>(int value, char* constantname) { lua_pushinteger(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<double>(double value, char* constantname) { lua_pushnumber(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<char>(char* value, char* constantname) { lua_pushstring(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<bool>(bool value, char* constantname) { lua_pushboolean(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<lua_cfunction>(lua_CFunction value, char* constantname) { lua_pushcfunction(lua_state, value); lua_setglobal(lua_state,constantname); }
      
      





クラスTの可能な値ごとに、アクションを定義します。

*キャプテン*最終定義-関数登録

登録に適した機能は次のとおりです。

 int Foo(lua_State*) { // ... return n; }
      
      





ここで、nは返される値の数です。 n = 2の場合、Luaでは次のことができます。

 a, b = Foo()
      
      





1つの関数が複数の値を返すことに驚いた場合は、Luaのマニュアルを読んでください





次の関数は、Luaのテーブルを作成します 。 これが何を意味するのか明確でない場合、そこにあるテーブルは配列のようなもの

  void Array();
      
      



配列()
彼女の説明

 void Script::Array() { lua_createtable(lua_state, 2, 0); }
      
      







次の関数は、アイテムをテーブルに登録します。

  template<class t> void RegisterConstantArray(T value, int index);
      
      



RegisterConstantArray()
彼女の説明

 template void Script::RegisterConstantArray<int>(int value, int index) { lua_pushnumber(lua_state, index); lua_pushinteger(lua_state, value); lua_settable(lua_state, -3); } template void Script::RegisterConstantArray<double>(double value, int index) { lua_pushnumber(lua_state, index); lua_pushnumber(lua_state, value); lua_settable(lua_state, -3); } template void Script::RegisterConstantArray<char>(char* value, int index) { lua_pushnumber(lua_state, index); lua_pushstring(lua_state, value); lua_settable(lua_state, -3); } template void Script::RegisterConstantArray<bool>(bool value, int index) { lua_pushnumber(lua_state, index); lua_pushboolean(lua_state, value); lua_settable(lua_state, -3); } template void Script::RegisterConstantArray<lua_cfunction>(lua_CFunction value, int index) { lua_pushnumber(lua_state, index); lua_pushcfunction(lua_state, value); lua_settable(lua_state, -3); }
      
      





Luaがわからない場合、非常に多くの型が1つの配列に収まることに驚くでしょう。 :)

実際、テーブル要素にはテーブルも含まれる場合がありますが、私はそれを行いません。





最後に、完成したテーブルを登録する必要があります

  void RegisterArray(char* arrayname);
      
      



RegisterArray()
彼女の説明

 void Script::RegisterArray(char* arrayname) { lua_setglobal(lua_state, arrayname); }
      
      





特別なことは何もない





以下の関数は主に、Luaでの登録に必要なint foo(lua_State *)などの関数のみを対象としています。



それらの最初-引数の数を取得します

  int GetArgumentCount();
      
      



作成()
彼女の説明

 int Script::GetArgumentCount() { return lua_gettop(lua_state); }
      
      





この関数は、たとえば、Write()関数の場合に必要です。この関数では、1つの引数ではなく、好きなだけ引数を詰めることができます

同様の機能を後で実装します。





次の関数は、スクリプト内の関数に渡された引数を受け取ります

  template<class t> T GetArgument(int index);
      
      



GetArgument()
彼女の説明

 template int Script::GetArgument<int>(int index) { return lua_tointeger(lua_state,index); } template double Script::GetArgument<double>(int index) { return lua_tonumber(lua_state,index); } template char* Script::GetArgument<char>(int index) { return (char*)lua_tostring(lua_state,index); } template bool Script::GetArgument<bool>(int index) { return lua_toboolean(lua_state,index); }
      
      





テーブルと関数を除く、前述のすべてのタイプを取得できます

indexは引数の番号です。 そして、最初の引数は1から始まります。





最後に、スクリプトに値を返す最後の関数

  template<class t> void Return(T value);
      
      



戻る()
彼女の説明

 template<> void Script::Return<int>(int value) { lua_pushinteger(lua_state,value); } template<> void Script::Return<double>(double value) { lua_pushnumber(lua_state,value); } template<> void Script::Return<char>(char* value) { lua_pushstring(lua_state,value); } template<> void Script::Return<bool>(bool value) { lua_pushboolean(lua_state,value); }
      
      







バトルコード





それは何かをする時間です!

main.cppを変更する



 #include "Script.h" int main() { return 0; }
      
      





コンパイルします。 これでクラスのテストを開始できます



覚えておいて、私は書き込み機能を作成すると約束しましたか? :)

main.cppを変更します

 #include "Script.h" //   _getch() #include <conio.h> //   Script script; //  Write   int Write(lua_State*) { //          for(int i = 1; i < script.GetArgumentCount()+1; i++) cout << script.GetArgument<char*>(i); //       _getch(); return 0; } int main() { script.Create(); //      ,    script.RegisterConstant<lua_cfunction>(Write,"Write"); script.DoFile("script.lua"); script.Close(); }
      
      





そして、プロジェクトのあるフォルダーで、script.luaファイルを作成します

 Write(1,2,3,4)
      
      







画像



プロジェクトをコンパイルして実行します。



画像



次に、script.luaを変更します

 for i = 1, 4 do Write(i, "\n", "Hier kommt die Sonne", "\n") end
      
      





これで、プログラムはそれぞれ2行を出力し( "\ n"-新しい行を作成)、Enterが押されるのを待って、再び行を出力します。



画像



スクリプトを試してください!



次に、関数を含むmain.cppの例とscript.luaの例を示します。



 #include "Script.h" #include <conio.h> #include <Windows.h> #include <time.h> Script script; int Write(lua_State*) { //          for(int i = 1; i < script.GetArgumentCount()+1; i++) cout << script.GetArgument<char*>(i); cout << "\n"; return 0; } int GetString(lua_State*) { //     cin   ,   Script char* str = ""; cin >> str; script.Return<char*>(str); //  !    1  -> return 1 return 1; } int Message(lua_State*) { //    MessageBox  Windows.h // ,    -        :) char* msg = script.GetArgument<char*>(1); MessageBox(0,msg,"",MB_OK); return 0; } int GetTwoRandomNumbers(lua_State*) { //      1000 srand(time(NULL)); for(int i = 0; i < 2; i++) script.Return<int>(rand()%1000); //  2  return 2; } int GetLotOfRandomNumbers(lua_State*) { //      1000 srand(time(NULL)); for(int i = 0; i < script.GetArgument<int>(1); i++) script.Return<int>(rand()%1000); //   ,     return script.GetArgument<int>(1); } int main() { script.Create(); script.RegisterConstant<lua_CFunction>(Write,"Write"); script.RegisterConstant<lua_CFunction>(GetString,"GetString"); script.RegisterConstant<lua_CFunction>(Message,"Message"); script.RegisterConstant<lua_CFunction>(GetTwoRandomNumbers,"Rand1"); script.RegisterConstant<lua_CFunction>(GetLotOfRandomNumbers,"Rand2"); script.Array(); script.RegisterConstantArray<int>(1,1); script.RegisterConstantArray<int>(2,2); script.RegisterConstantArray<int>(3,3); script.RegisterConstantArray<int>(4,4); script.RegisterArray("mass"); script.DoFile("script.lua"); script.Close(); //    _getch(); }
      
      





 for i = 1, 4 do Write(i, "\n", "Hier kommt die Sonne", "\n") end Write(2*100-1) Message("!") a, b = Rand1() Write(a, "\n", b, "\n") Write(Rand1(), "\n") a, b, c, d = Rand2(4) Write(a, "\n", b, "\n", c, "\n", d, "\n") return 1
      
      







役に立つヒント




質疑応答






Script.hおよびScript.cppの完全なリスト

Script.h
 #ifndef _SCRIPT_H_ #define _SCRIPT_H_ #pragma comment(lib,"lua.lib") extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> } class Script { private: lua_State *lua_state; public: void Create(); void Close(); int DoFile(char* ScriptFileName); template<class t> void RegisterConstant(T value, char* constantname); void Array(); template<class t> void RegisterConstantArray(T value, int index); void RegisterArray(char* arrayname); int GetArgumentCount(); template<class t> T GetArgument(int index); template<class t> void Return(T value); }; #endif
      
      





コンソールを操作するためのインクルードを削除しました

Script.cpp
 #include "Script.h" void Script::Create() { lua_state = luaL_newstate(); static const luaL_Reg lualibs[] = { {"base", luaopen_base}, {"io", luaopen_io}, {NULL, NULL} }; for(const luaL_Reg *lib = lualibs; lib->func != NULL; lib++) { luaL_requiref(lua_state, lib->name, lib->func, 1); lua_settop(lua_state, 0); } } void Script::Close() { lua_close(lua_state); } int Script::DoFile(char* ScriptFileName) { luaL_dofile(lua_state,ScriptFileName); return lua_tointeger(lua_state, lua_gettop(lua_state)); } template<> void Script::RegisterConstant<int>(int value, char* constantname) { lua_pushinteger(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<double>(double value, char* constantname) { lua_pushnumber(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<char>(char* value, char* constantname) { lua_pushstring(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<bool>(bool value, char* constantname) { lua_pushboolean(lua_state, value); lua_setglobal(lua_state,constantname); } template<> void Script::RegisterConstant<lua_cfunction>(int(*value)(lua_State*), char* constantname) { lua_pushcfunction(lua_state, value); lua_setglobal(lua_state,constantname); } void Script::Array() { lua_createtable(lua_state, 2, 0); } template<> void Script::RegisterConstantArray<int>(int value, int index) { lua_pushnumber(lua_state, index); lua_pushinteger(lua_state, value); lua_settable(lua_state, -3); } template<> void Script::RegisterConstantArray<double>(double value, int index) { lua_pushnumber(lua_state, index); lua_pushnumber(lua_state, value); lua_settable(lua_state, -3); } template<> void Script::RegisterConstantArray<char>(char* value, int index) { lua_pushnumber(lua_state, index); lua_pushstring(lua_state, value); lua_settable(lua_state, -3); } template<> void Script::RegisterConstantArray<bool>(bool value, int index) { lua_pushnumber(lua_state, index); lua_pushboolean(lua_state, value); lua_settable(lua_state, -3); } template<> void Script::RegisterConstantArray<lua_cfunction>(lua_CFunction value, int index) { lua_pushnumber(lua_state, index); lua_pushcfunction(lua_state, value); lua_settable(lua_state, -3); } void Script::RegisterArray(char* arrayname) { lua_setglobal(lua_state, arrayname); } int Script::GetArgumentCount() { return lua_gettop(lua_state); } template<> int Script::GetArgument<int>(int index) { return lua_tointeger(lua_state,index); } template<> double Script::GetArgument<double>(int index) { return lua_tonumber(lua_state,index); } template<> char* Script::GetArgument<char>(int index) { return (char*)lua_tostring(lua_state,index); } template<> bool Script::GetArgument<bool>(int index) { return lua_toboolean(lua_state,index); } template<> void Script::Return<int>(int value) { lua_pushinteger(lua_state,value); } template<> void Script::Return<double>(double value) { lua_pushnumber(lua_state,value); } template<> void Script::Return<char>(char* value) { lua_pushstring(lua_state,value); } template<> void Script::Return<bool>(bool value) { lua_pushboolean(lua_state,value); }
      
      







libとincludを含むリポジトリ: https : //github.com/Izaron/LuaForHabr



すべての質問をPMに送信するか、このトピックに送信するか、Habréに登録できない場合は、メールizarizar@mail.ruに送信してください。






All Articles