📜 ⬆️ ⬇️

What are scripts and what they eat - Lua & C ++

Good day, Habrahabr!
I decided to write this topic on the topic of scripts

What you need to know?



Why writing game dialogs in a .cpp file was a big mistake.


If you have developed large projects (for example, large-scale games), have you noticed that with each new hundred lines of code, the compilation is slower?
The game creates more weapons, more dialogues, more menus, more etc.
One of the main problems arising in connection with innovations is to maintain the innumerable number of weapons and badges rather difficult task.
In a situation where a friend / boss / teammate request to change the dialogue or add a new type of weapon takes too much time, you have to resort to some measures - for example, recording all this garbage in separate text files.
Almost every game developer has ever made a level map or dialog in a separate text file and then read them. Take at least the simplest option - Olympiad tasks in computer science with an input file

But there is a way, head down - using scripts.
')

Solution to the problem


“Okay, for such cases a regular file with a description of player characteristics is enough. But what to do if in a rapidly developing project almost every day you have to slightly change the logic of the main player, and, therefore, compile the project many times? ”
Good question. In this case, scripts come to the rescue, holding exactly the logic of the player with all the characteristics or some other part of the game.
Naturally, it’s best to keep the player’s logic in the form of a programming language code.
The first thought - to write your own interpreter of your scripting language, is thrown out of the brain in a few seconds. The logic of the player is definitely not worth such terrible costs.
Fortunately, there are special libraries of scripting languages ​​for C ++, which take a text file as input and execute it.

One such scripting language Lua will be discussed.

How it works?


Before you begin, it is important to understand how a scripting language works. The fact is that there are very few functions in scripting languages, in the presence of for, while, if, and other constructs.
These are mainly text output functions in the console, math functions and functions for working with files.
How, then, can a player be controlled through scripts?

We in the C ++ program do any functions, “register” them under some name in the script and call it in the script. That is, if we registered the SetPos (x, y) function to determine the player's position in the C ++ program, then, having encountered this function in the script, the “interpreter” from the script language library calls this function in the C ++ program, of course with the transfer of all methods.
Amazing right? :)

UPD: Attention! One user sent me a mail that when I filled in the code, I didn’t completely eliminate all errors - habrahabr.ru/post/196272/#comment_6850016
In the code with the permission of Habr penetrated bugs
Replace sections of code like
template<class t> T MethodName(); 

On
  template<class T> T MethodName(); 

And instead of lua_CFunction skips lua_cfunction
Thank!

I'm ready!


When you understand the benefits of scripting programming languages, it's time to start working!
Download from the repository on the githab (bottom of the topic) lib'u and includ'y Lua, or take them on the official website.

Create a console project or Win32 (it does not matter) in Visual Studio (I have version 2012)

Go to Project-> Properties-> Configuration Properties-> VC ++ directories and in the “include directories” and “library catalogs” add the folder Include and Lib from the repository, respectively.

Now create the main.cpp file, write in it:
 int main() { return 0; } 

As you guessed, I have a console application.

Now go to coding

I promise that I will carefully explain every moment.

We will be responsible for the scripts class Script. I will declare and simultaneously implement functions in Script.h / .cpp
Create Script.cpp and write in it.
 #include "Script.h" 

Create Script.h and write in it.
 #ifndef _SCRIPT_H_ #define _SCRIPT_H_ #endif 

After 2 lines and before #endif we define the class of scripts
This code is written to prevent mutual inclusion of files. Suppose that the Game.h file connects Script.h, and Script.h connects Game.h - disorder! And with this code inclusion is performed only 1 time.

Now we write inside this code
 #pragma comment(lib,"lua.lib") extern "C" { #include <lua.h> #include <lualib.h> #include <lauxlib.h> } 

The first line connects lua.lib from the archive itself.
What is extern "C" for? The fact is that lua is written in C and therefore such code is needed to connect the libraries.

Next comes the connection of well-known files for working with the console.
 #include <stdio.h> #include <iostream> #include <sstream> using namespace std; 


Now we proceed to class definition.
 class Script { 

The main object of the Lua library for C ++ is lua_State, it is required for script execution.
 private: lua_State *lua_state; 

Next come the public functions.
 public: void Create(); 

This function initializes lua_State

Create ()
Its definition in 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); } } 

The first line we initialize our lua_State.
Then we declare a list of "connected libraries." The fact is that in the “pure” form in Lua there is only a function print (). For mathematical and other functions, you need to connect special libraries and then call them as math.foo, base.foo, io.foo. To connect other libraries, add to lualibs, for example, {"math", luaopen_math}. All library names begin with luaopen _..., at the end of lialibs must be {NULL, NULL}
  void Close(); 

This feature frees Lua resources.
Close ()
Her definition
 void Script::Close() { lua_close(lua_state); } 

Just use lua_close ()
 int DoFile(char* ScriptFileName); 

And this function executes the file. It accepts the file name as input, for example, “C: \\ script.lua”.
Why does it return an int? Just some scripts may contain return, interrupting the script and returning some value.

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

As you can see, I execute the script and return the int. But the function can return not only int, but also bool and char *, I just always return numbers (lua_toboolean, lua_tostring)


Now we will make a function registering constants (numbers, strings, functions)
  template<class t> void RegisterConstant(T value, char* constantname); 
RegisterConstant ()
We operate through templates. Example of function call:
 RegisterConstant<int>(13,"goodvalue"); 

Her definition
 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); } 

For each possible value of class T, we define our actions.
* Captain * Last Definition - Registration Function
Functions suitable for registration, look like this:
 int Foo(lua_State*) { // ... return n; } 

Where n is the number of returned values. If n = 2, then in Lua you can do this:
 a, b = Foo() 

Read Lua manuals if you were surprised that one function returns multiple values ​​:)


The following function creates a table for Lua. If it is not clear what this means, then the table there is just like an array
  void Array(); 
Array ()
Her description
 void Script::Array() { lua_createtable(lua_state, 2, 0); } 


The following function registers an item in the table.
  template<class t> void RegisterConstantArray(T value, int index); 
RegisterConstantArray ()
Her description
 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); } 

If you don't know Lua, you are probably surprised that so many types fit into one array? :)
In fact, a table element may also contain a table, I never do that.


Finally, the completed table needs to be registered.
  void RegisterArray(char* arrayname); 
RegisterArray ()
Her description
 void Script::RegisterArray(char* arrayname) { lua_setglobal(lua_state, arrayname); } 

Nothing special


The following functions are mainly intended only for functions of type int foo (lua_State *) that are needed for registration in Lua.

The first one gets the number of arguments.
  int GetArgumentCount(); 
Create ()
Her description
 int Script::GetArgumentCount() { return lua_gettop(lua_state); } 

This function is needed, for example, for the Write () function, where you can cram as many arguments as you like, or you can push
We will implement a similar function later.


The following function receives the argument passed to the function in the script.
  template<class t> T GetArgument(int index); 
GetArgument ()
Her description
 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); } 

You can get all the types described earlier, except for tables and functions
The index is the number of the argument. And the first argument starts with 1.


Finally, the last function that returns the value in the script
  template<class t> void Return(T value); 
Return ()
Her description
 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); } 


Combat code



Time to do something!
Change main.cpp

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

Compile. Now you can start testing our class.

Remember, I promised to do the Write function? :)
Modify 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(); } 

And in the folder with the project create a file script.lua
 Write(1,2,3,4) 


image

Compile and run the project.

image

Now we change script.lua
 for i = 1, 4 do Write(i, "\n", "Hier kommt die Sonne", "\n") end 

Now the program will display 2 lines of each ("\ n" - creating a new line), wait for Enter and press the lines again.

image

Experiment with scripts!

Here is an example main.cpp with functions and an example 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 


Useful tips


Questions and answers



Full listing Script.h and 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 

I deleted the console to work
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); } 


Repository with lib and includami: https://github.com/Izaron/LuaForHabr

Send all questions to me in the LAN, or in this topic, or, if you are not lucky enough to be registered on Habré, email me izarizar@mail.ru

Source: https://habr.com/ru/post/196272/


All Articles