Apply Lua in your game

Source: Internet
Author: User

This article is transferred from zx9597446's blog,
Welcome to Lua Interested friends exchange, mailto: zx9597446@sina.com

Apply Lua (1) in your game: run the interpreter in your game code

Generally, you want to read some information at the beginning of your game to configure your game. This information is usually stored in a text file. When your game starts, you need to open the file, parse the string, and find the required information.

Yes, maybe you think this is enough. Why should you use Lua?

For the purpose of "configuration", Lua provides you with more powerful and flexible expressions. In the previous method, you cannot configure your game based on certain conditions, lua provides you with a flexible expression. You can configure your game like this:

If PLAYER: is_dead () then
Do_something ()
Else
Do_else ()
End

More importantly, after you make some changes, you do not need to re-compile your game code.

Generally, you do not need a single interpreter in the game. You need to run the interpreter in the game. Next, let's take a look at how to run the interpreter in your code:

// This is the three-headed file required by Lua.
// Of course, You need to link to the correct lib
# Include "Lua. H"
# Include "lauxlib. H"
# Include "lualib. H"

Int main (INT argc, char * argv [])
{
Lua_state * l = lua_open ();
Luaopen_base (L );
Luaopen_io (L );

Const char * Buf = "Print ('hello, world! ')";

Lua_dostring (BUF );

Lua_close (L );

Return 0;
}

Program output: Hello, world!

Sometimes you need to execute a string, and sometimes you may need to execute a file. When you need to execute a file, you can do this:
Lua_dofile (L, "test. Lua ");

Look, it's very easy.

Apply Lua (1): getting value in your game

In the previous article, we were able to execute the Lua interpreter in our game code. Let's take a look at how to obtain the information we need from the script.

First, let me briefly explain the working mechanism of the Lua interpreter. The Lua interpreter maintains a runtime stack. Through this runtime stack, the Lua interpreter transmits parameters to the Host Program, so we can get the value of a script variable as follows:

Lua_pushstring (L, "Var"); // puts the variable name into the stack
Lua_gettatbl (L, lua_globalsindex); the value of the variable is now at the top of the stack.

Assume that you have a variable VAR = 100 in the script.
You can get the variable value as follows:
Int Var = lua_tonumber (L,-1 );

How about it, isn't it easy?

Lua defines a macro that allows you to easily obtain the value of a variable:
Lua_getglobal (L, name)

We can obtain the value of a variable as follows:
Lua_getglobal (L, "Var"); // The value of the variable is now at the top of the stack.
Int Var = lua_tonumber (L,-1 );

The complete test code is as follows:

# Include "Lua. H"
# Inculde "lauxlib. H"
# Include "lualib. H"

Int main (INT argc, char * argv [])
{
Lua_state * l = lua_open ();
Luaopen_base (L );
Luaopen_io (L );

Const char * Buf = "Var = 100 ";

Lua_dostring (L, Buf );

Lua_getglobal (L, "Var ");
Int Var = lua_tonumber (L,-1 );

Assert (Var = 100 );

Lua_close (L );

Return 0;
}

Apply Lua (1) in your game: Call a function

Assume that you define a function in the script:

Function main (number)
Number = Number + 1
Return number
End

In your game code, you want to call this function at a certain point in time to obtain its return value.

In Lua, the function is equivalent to a variable, so you can obtain this function as follows:

Lua_getglobal (L, "Main"); // The function is currently at the top of the stack.

Now, we can call this function and pass it to the correct parameters:

Lua_pushnumber (L, 100); // press the parameter to stack
Lua_pcall (L, 1, 1, 0); // call a function. There is a parameter and a return value.
// The returned value is now at the top of the stack.
Int result = lua_tonumber (L,-1 );

Result is the return value of the function.

The complete test code is as follows:

# Include "Lua. H"
# Include "lauxlib. H"
# Include "lualib. H"

Int main (INT argc, char * argv [])
{
Lua_state * l = lua_open ();
Luaopen_base (L );

Const char * Buf = "function main (number) number = Number + 1 return number end ";

Lua_dostring (BUF );

Lua_getglobal (L, "Main ");
Lua_pushnumber (L, 100 );
Lua_pcall (L, 1, 1, 0 );

Int result = lua_tonumber (L,-1 );

Assert (result = 101 );

Lua_close (L );

Return 0;
}

Apply Lua (2) in your game: Expand Lua

Lua is positioned in a lightweight, flexible, and extensible scripting language, which means you can freely expand Lua and customize a scripting language for your own games.

You can provide your custom API to the script in the Host Program for the script to call.

Lua defines a type: lua_cfunction, which is a function pointer and its prototype is:
Typedef int (* lua_cfunction) (lua_state * l );

This means that only functions of this type can be registered with Lua.

First, we define a function.

Int Foo (lua_state * l)
{
// Obtain the parameter pushed into the stack when the script executes this function.
// Assume that this function provides a parameter with two return values.

// Get the first parameter
Const char * par = lua_tostring (L,-1 );

Printf ("% s/n", par );

// Push the first result
Lua_pushnumber (L, 100 );

// Push the second result
Lua_pushnumber (L, 200 );

// Return 2 result
Return 2;
}

We can call this function in the script as follows:

R1, R2 = Foo ("hello ")

Print (R1.. R2)

The complete test code is as follows:

# Include "Lua. H"
# Include "lauxlib. H"
# Include "lualib. H"

Int Foo (lua_state * l)
{
// Obtain the parameter pushed into the stack when the script executes this function.
// Assume that this function provides a parameter with two return values.

// Get the first parameter
Const char * par = lua_tostring (L,-1 );

Printf ("% s/n", par );

// Push the first result
Lua_pushnumber (L, 100 );

// Push the second result
Lua_pushnumber (L, 200 );

// Return 2 result
Return 2;
}

Int main (INT argc, char * argv [])
{
Lua_state * l = lua_open ();
Luaopen_base (L );
Luaopen_io (L );

Const char * Buf = "R1, R2 = Foo (" hello ") print (R1.. R2 )";

Lua_dostring (L, Buf );

Lua_close (L );

Return 0;
}

Program output:
Hello
100200

Apply Lua (3): Using Lua in CPP in your game

The Lua and Host Program Exchange parameters are implemented through a runtime stack. The runtime stack information is placed in a lua_state structure. The APIS provided by Lua require a lua_state * pointer, except one:

Lua_open ();

This function returns a lua_state * pointer. In your game code, you can have only one pointer or multiple pointers.

Finally, you need to release this pointer through the function:

Lua_close (L );

Note that in your host Program, open () and close () are always paired. In C ++, if something happens in pairs, this usually means that you need a constructor and an destructor. Therefore, we first encapsulate lua_state:

# Ifndef lua_extralibs
# Define lua_extralibs/* empty */
# Endif

Static const lual_reg lualibs [] =
{
{"Base", luaopen_base },
{"Table", luaopen_table },
{"Io", luaopen_io },
{"String", luaopen_string },
{"Math", luaopen_math },
{"Debug", luaopen_debug },
{"Loadlib", luaopen_loadlib },
/* Add your libraries here */
Lua_extralibs
{Null, null}
};

This is some auxiliary lib provided by Lua to users. When using lua_state, you can choose to open or close it.

The complete class implementation is as follows:

// Lua_state
Class state
{
Public:
State (bool bopenstdlib = false)
:
Err_fn (0)
{
L = lua_open ();

Assert (L );

If (bopenstdlib)
{
Open_stdlib ();
}
}

~ State ()
{
Lua_setgcthreshold (L, 0 );
Lua_close (L );
}

Void open_stdlib ()
{
Assert (L );

Const lual_reg * Lib = lualibs;
For (; Lib-> func; LIB ++)
{
Lib-> func (l);/* Open Library */
Lua_settop (L, 0);/* discard any results */
}
}

Lua_state * get_handle ()
{
Return L;
}

Int error_fn ()
{
Return err_fn;
}

PRIVATE:
Lua_state * l;

Int err_fn;
};

Generally, we only use a lua_state * pointer in the game code, so we implement a single piece for it. By default, we open all the Lib provided by Lua:

// Return the global instance
State * lua_state ()
{
Static State L (true );

Return & L;
}

Apply Lua (3): Using Lua in CPP in your game (encapsulate stack operations)

As mentioned above, Lua and host programs exchange information through a runtime stack, so we make a simple encapsulation of stack access.

We use the function overload mechanism of C ++ to encapsulate these operations. overloading provides us with a uniform mechanism to process operations.

Information is transferred to Lua through the stack operation. Therefore, we define some push () functions:

Inline void push (lua_state * l, int value );
Inline void push (lua_state * l, bool value );
...

Corresponding to the simple C ++ built-in type, we implement the same push function. As for the internal implementation of the function, we only need to use the API provided by Lua to implement it. For example:

Inline void push (lua_state * l, int value)
{
Lua_pushnumber (L, value );
}

The benefit of this method is that in our code, we can handle the operation of the Pressure stack in a unified way. If there is a type of operation that does not define the relevant pressure stack, A compilation error is generated.

Later I will mention how to pass a pointer of the User-Defined type to Lua. In that case, we do not need to change the basic code, as long as a corresponding push () is added () function.

Remember the close-open principle. It means that modification is closed and expansion is open. A good class library design allows you to expand it without modifying its implementation, you don't even need to re-compile.

C ++ new generic design thinking refers to a technology called type2type, which is essentially simple:

Template <typename T>
Struct type2type
{
Typedef t u;
};

As you can see, it does not have any data members. It only exists to carry type information.

Ing from types to types is very useful when applied to overload functions. type2type can be used to distribute data during compilation.

The following describes how to apply type2type when obtaining Lua information from the stack:

Test Type: Because the Lua type system is different from C ++, we need to perform a type check on the stack information.

Inline bool match (type2type <bool>, lua_state * l, int idx)
{
Return lua_type (L, idx) = lua_tboolean;
}

Similarly, we need to provide the corresponding match function for the built-in type of CPP:

Inline bool match (type2type <int>, lua_state * l, int idx );
Inline bool match (type2type <const char *>, lua_state * l, int idx );

...

It can be seen that the existence of type2type is only to determine the correct function when the match is called. because it does not have any Members, there is no runtime cost.

Similarly, we provide the get () function for the CPP built-in type:

Inline bool get (type2type <bool>, lua_state * l, int idx)
{
Return lua_toboolean (L, idx );
}

Inline int get (type2type <int>, lua_state * l, int idx)
{
Return static_cast <int> (lua_tonumber (L, idx ));
}

...

I think you may have noticed that there is a transformation action in int get (type2type <int>). Because the Lua type system is different from the CPP type, the transformation action is required.

In addition, there is a small detail in the get overload function (s). The return values of each get function are different, because the overload mechanism is identified by different parameters, instead of the return value.

The above are some basic encapsulation. Next we will introduce how to register a multi-parameter C function with Lua. Remember? Using Lua APIs, you can only register int (* ua_cfunction) (lua_state *) C functions. Do not forget that Lua is written in C.

Apply Lua (3): Using Lua in CPP in your game (register one of the C functions of different types)

As mentioned above, we can use the APIS provided by Lua to provide our own functions to the script. In Lua, only functions of the lua_cfunction type can be directly registered with Lua, lua_cfunction is actually a function pointer:
Typedef int (* lua_cfunction) (lua_state * l );

In actual applications, we may need to register various parameters and return value types with Lua. For example, provide an add Script Function and return the sum of two values:

Int add (int x, int y );

To achieve this purpose, we first define a lua_cfunction function:

Int add_proxy (lua_state * l)
{
// Obtain parameters
If (! Match (typewrapper <int> (), L,-1 ))
Return 0;
If (! Match (typewrapper <int> (), L,-2 ))
Return 0;

Int x = get (typewrapper <int> (), L,-1 );
Int y = get (typewrapper <int> (), L,-1 );

// Call a real function
Int result = add (x, y );

// Return results
Push (result );

Return 1;
}

Now, we can register this function with Lua:

Lua_pushstring (L, "add ");
Lua_pushcclosure (L, add_proxy, 0 );
Lua_settable (L, lua_globalindex );

In the script, you can call this function as follows:

Print (add (100,200 ))

As shown in the preceding steps, to register a non-lua_cfunction function with Lua, you must:
1. Implement an encapsulation call for this function.
2. Obtain the provided parameters from the Lua stack in the encapsulated call function.
3. Call this function using parameters.
4. Pass the result to Lua.

Note: currently, we only target global C functions, and the class member functions are not involved at the moment. In CPP, the static member functions of the class are similar to those of C functions.

If we have multiple non-lua_cfunction functions registered with Lua, we need to repeat the above steps for each function to generate an encapsulation call. We can see that these steps are mostly mechanical. Therefore, we need to implement the above steps automatically.

First, let's take a look at Step 1. in CPP, the best way to generate such a function to encapsulate calls is to use template. We need to provide a lua_cfunction type template function, in this function, call the real function registered with the script, similar to the following:
Template <typename func>
Inline int register_proxy (lua_state * l)

The problem is: to call a real function in this function, we must obtain a function pointer in this function. However, the lua_cfunction type function does not allow you to add other parameters to provide this function pointer. How can we let the regisger_proxy function know the function we actually want to register?

In Oop, it seems that classes can be used to solve this problem:

Template <func>
Struct register_helper
{
Explicit register_helper (func FN): m_func (FN)
{}
Int register_proxy (lua_state * l );

Protected:
Func m_func;
};

However, do not forget that the lua_cfunction type points to a C function, rather than a member function. Their calling methods are different. If the preceding int register_proxy () setting it to a static member function does not work either, because we need the Member variable m_func of the member class;

Let's take another look at the lua_cfunction function:

Int register_proxy (lua_state * l );

We can see that there is a lua_state * pointer here. Can we store the real function pointer here and retrieve it from it when it is actually called?

Lua provides an API to store user data:
Lua_newuserdata (L, size)

At an appropriate time, we can retrieve the data again:

Lua_touserdata (L, idx)

OK. Now we have solved the problem of passing the function pointer. Next, let's look at Step 2: Get the parameter.

Apply Lua (3) in your game: Using Lua in CPP (register different types of c Functions) 2

After solving the problem of passing the function pointer, let's take a look at what problems will occur when calling the function.

First, when we call this function through the function pointer, because we are dealing with functions of the unknown type, that is, we do not know the number of parameters, the type of parameters, and the type of returned values, therefore, we cannot directly obtain parameters from the Lua stack. Of course, we can obtain the number and type of parameters passed by Lua through the information in the runtime test stack, this means that when we call a function through the function pointer later, we also need to dynamically determine the correct function based on the number and type of parameters. In this way, apart from the runtime cost, CPP provides us with a strong type check mechanism with little benefit. What we need is a "polymorphism" during static compilation ".

In CPP, there are at least two methods to achieve this. The most direct and simple method is to use function overloading. Another method is to use the template special mechanism.

A Brief Introduction to template specialization:

In CPP, you can write some special versions for a template function or template class. The compiler will find the most appropriate version when matching template parameters. Similar to this:

Templat <typename T>
T Foo ()
{
T TMP ();
Return TMP;
}

// Provides the special version
Template <>
Int Foo ()
{
Return 100;
}

In the main () function, we can display the foo of the specified version:

Int main (INT argc, char ** argv)
{
Cout <Foo <int> () <Endl;
Return 0;
}

The program will output 100 instead of 0. The above code is compiled in G ++. Because vc6 does not support the template well, some template technologies can be compiled in vc6.

So it is best to use overload to solve this problem. In the encapsulation function call, we first get this function pointer, and then we need to provide a call function to actually call this function, similar to this:
// Pseudocode
Int call (PFN, lua_state * l, int idx)

But we don't know the type of this function pointer. How should we write it now? Don't forget, our register_proxy () is a template function, which has a parameter representing the type of this pointer:

Template <typename func>
Int register_proxy (lua_state * l)
{
// Pseudo code, get this pointer through the L Parameter
Unsigned char * buffer = get_pointer (L );

// Forcibly convert the pointer type and call the call function.
Return call (* (func *) buffer, L, 1 );
}

Call the overload function to call the real function. In this way, we can use the Lua API to register the relevant function. Then we provide a registered function:

Template <typename func>
Void lua_pushdirectclosure (func FN, lua_state * l, int nupvalue)
{
// Pseudocode, storing function pointers to L
Save_pointer (L );

// Provide Lua with our register_proxy Function
Lua_pushcclosure (L, register_proxy <func>, nupvalue + 1 );
}

Then define the relevant registration macros:
# Define lua_register_directclosure (L, func )/
Lua_pushstring (L, # func );
Lua_pushdirectclosure (func, L, 1 );
Lua_settable (L, lua_globalindex)

Now, suppose we have a function like int add (int x, int y), we can directly register with Lua:

Lua_register_directclosure (L, add );

Look, it's very convenient to use it at the end. We don't need to write so much code to encapsulate the call, but the problem is not complete yet. We have to solve the call function problem later.

Apply Lua (3) in your game: Using Lua in CPP (register different types of c Functions) 3

Next, let's focus on solving the problem of call overload functions.

As mentioned above, the call overload function accepts a function pointer, obtains relevant parameters from the Lua Stack Based on the function pointer type, and calls this function, then press the return value to the Lua stack, similar to the following:

// Pseudocode
Int call (PFN, lua_state * l, int idx)

The question now is how should PFN be declared? We know that this is a function pointer, but its parameters and return values are all unknown types. If we know the return values and parameter types, we can declare them using a typedef:

Typedef void (* PFn )();

Int call (pfn fn, lua_state * l, int idx );

We know that the return value and parameter type are only a template parameter T. in CPP, we cannot write as follows:

Template <typename T>
Typedef T (* func )();

One solution is to use the class template:

Template <typename T>
Struct callhelper
{
Typedef T (* func )();
};

Then reference it in call:

Template <typename T>
Int call (typename callhelper: func FN, lua_state * l, int idx)

Note the typename keyword. If this keyword is not found, a compilation warning is generated in G ++, which indicates that callhelper: func is a type rather than a variable.

If we solve this problem in this way, we need to define a large number of different types of function pointers in callhelper for each case. There is also a method in which the syntax is odd. Consider the parameter declaration in a function:

Void (int n );

The first is the type, and then the variable, which is applied to the function pointer:

Typedef void (* PFn )();
Void (pfn fn );

In fact, typedef can be written directly in the parameter table:

Void (void (* PFn )());

In this way, our call function can be written as follows:

// Call functions without Parameters
Template <typename RT>
Int call (RT (* func) (), lua_state * l, int idx );
{
// Call func
RT ret = (* func )();

// Return the value to Lua
Push (L, RET );

// Tell Lua how many return values
Return 1;
}

// For a call with a parameter
Template <typename T, typename P1>
Int call (RT (* func) (), lua_state * l, int idx)
{
// Obtain parameters from Lua
If (! Match (typewrapper <P1> (), L,-1)
Return 0;

RT ret = (* func) (get (typewrapper <P1> (), L,-1 ));

Push (L, RET );
Return 1;
}

According to the above code, we can provide call functions with any number of parameters. Now, back to the beginning, our function pointers should be stored through lua_state * L, you only need to use the API provided by Lua. Do you still remember our lua_pushdirectclosure function:

Template <typename func>
Void lua_pushdirectclosure (func FN, lua_state * l, int nupvalue)
{
// Pseudocode, storing function pointers to L
Save_pointer (L );

// Provide Lua with our register_proxy Function
Lua_pushcclosure (L, register_proxy <func>, nupvalue + 1 );
}

Here, save_pointer (l) can be implemented as follows:

Void save_pointer (lua_state * l)
{
Unsigned char * buffer = (unsigned char *) lua_newuserdata (L, sizeof (func ));
Memcpy (buffer, & func, sizeof (func ));
}

In the register_proxy function:

Template <typename func>
Int register_proxy (lua_state * l)
{
// Pseudo code, get this pointer through the L Parameter
Unsigned char * buffer = get_pointer (L );
// Forcibly convert the pointer type and call the call function.
Return call (* (func *) buffer, L, 1 );
}
The get_pointer function can be implemented as follows:

Unsigned char * get_pointer (lua_state * l)
{
Return (unsigned char *) lua_touserdata (L, lua_upvalueindex (1 ));
}

This can be effectively operated mainly on the fact that:

After we save this pointer in the Lua stack, we take it out of the stack without any operation on the stack, so we won't mess up the information in the Lua stack, remember, the data in the Lua stack is cleared by the user.

Up to now, we can register the C function of any parameter with Lua, just a simple line of code:

Lua_register_directclosure (L, func.

Apply Lua (3) in your game: Using Lua in CPP (one of the basic data types, pointers, and references)

Using Lua in CPP (basic data types, pointers, and references)

The previous sections are all about the built-in basic data types in CPP. However, even in this case, the situation will become more complicated in the face of pointers and references.

Using the previously completed macro lua_register_directclosure, you can only register the parameter functions in the form of by value. When the parameter contains pointers and references (once again, only for the basic data type ):

1. If it is a pointer, the intention of the function is usually to pass a result through this pointer.
2. for a reference, the same as above.
3. If it is a const pointer, it is usually used only when facing char *. The purpose of implementing the function is not to change the content of this parameter. In other cases, avoid using the const pointer.
4. If it is a const reference, this situation is generally avoided for basic data types.

Both Lua and CPP allow the function to return multiple values in some way. For CPP, multiple return values are returned in the above 1st and 2nd cases. For Lua, multiple return values can be returned directly:

-- In Lua
Function swap (x, y)
TMP = x
X = y
Y = TMP

Return X, Y
End

X = 100
Y = 200

X, Y = swap (x, y)

Print (x. y)

Output: 200100

Similarly, in the Host Program, we can also return multiple values to Lua:

Int swap (lua_state * l)
{
// Obtain two parameters
Int x = get (typewrapper <int> (), L,-1 );
Int y = get (typewrapper <int> (), L,-2 );

// Exchange value
Int TMP = X;
X = y;
Y = TMP;

// Return value to Lua
Push (L, X );
Push (L, y );

// Tell Lua how many values are returned
Return 2;
}

Now we can call this function in Lua as follows:

X = 100
Y = 200

X, Y = swap (x, y)

In our register_proxy function, only the by Value Method of the basic data type is valid. According to the above analysis, if we can know during the compilation, for a template parameter t:
1. Is this a basic data type or a user-defined data type?
2. Is this a common pointer or an iterator?
3. Is this a reference?
4. Is this a const normal pointer?
5. Is this a const reference?

If we know this, we hope that: (only for basic data types)
1. If this is a pointer, we want to return the content indicated by the pointer to Lua.
2. If this is a reference, we want to return the reference to Lua.
3. If this is the const pointer, we want to pass the parameters obtained from the Lua stack.
To call a function.
4. If this is a const reference, we also want to obtain the parameters from the Lua stack.
Data transmission is passed to the call function.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.