在你的游戏中应用LUA

lua_pop lua_settop 栈研究 我知道它是退栈的,但是退的是栈底还是栈顶。搜索一波还是没明白,自己动手测吧... 测试程序 static int lua_test(lua_State *L) { int pop_num = lua_tointeger(L,1); lua_settop(L,0); printf("1-top %d\r\n",lua_gettop(L)); lua_pushinteger(L,31); lua_pushinteger(L,32); lua_pushint 阅读详情

本文转自zx9597446的Blog,
欢迎对Lua感兴趣的朋友互相交流, mailto: zx9597446@sina.com 

在你的游戏中应用Lua(1):在你的游戏代码中运行解释器

  通常,你希望在你的游戏开始的时候读取一些信息,以配置你的游戏,这些信息通常都是放到一个文本文件中,在你的游戏启动的时候,你需要打开这个文件,然后解析字符串,找到所需要的信息。

  是的,或许你认为这样就足够了,为什么还要使用Lua呢?

  应用于“配置”这个目的,Lua提供给你更为强大,也更为灵活的表达方式,在上一种方式中,你无法根据某些条件来配置你的游戏,Lua提供给你灵活的表达方式,你可以类似于这样来配置你的游戏:

if player:is_dead() then
do_something()
else
do_else()
end

更为重要的是,在你做了一些修改之后,完全不需要重新编译你的游戏代码。

通常,在游戏中你并不需要一个单独的解释器,你需要在游戏来运行解释器,下面,让我们来看看,如何在你的代码中运行解释器:

//这是lua所需的三个头文件
//当然,你需要链接到正确的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;
}

程序输出:hello, world!

有时你需要执行一段字符串,有时你可能需要执行一个文件,当你需要执行一个文件时,你可以这么做:
lua_dofile(L, "test.lua");

看,非常简单吧。







在你的游戏中应用Lua(1):Getting Value

在上一篇文章我们能够在我们的游戏代码中执行Lua解释器,下面让我们来看看如何从脚本中取得我们所需要的信息。

首先,让我来简单的解释一下Lua解释器的工作机制,Lua解释器自身维护一个运行时栈,通过这个运行时栈,Lua解释器向主机程序传递参数,所以我们可以这样来得到一个脚本变量的值:

lua_pushstring(L, "var"); //将变量的名字放入栈
lua_gettatbl(L, LUA_GLOBALSINDEX);变量的值现在栈顶

假设你在脚本中有一个变量 var = 100
你可以这样来得到这个变量值:
int var = lua_tonumber(L, -1);

怎么样,是不是很简单?

Lua定义了一个宏让你简单的取得一个变量的值:
lua_getglobal(L, name)

我们可以这样来取得一个变量的值:
lua_getglobal(L, "var"); //变量的值现在栈顶
int var = lua_tonumber(L, -1);

完整的测试代码如下:

#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;
} 






在你的游戏中应用Lua(1):调用函数

假设你在脚本中定义了一个函数:

function main(number)
number = number + 1
return number
end

在你的游戏代码中,你希望在某个时刻调用这个函数取得它的返回值。

在Lua中,函数等同于变量,所以你可以这样来取得这个函数:

lua_getglobal(L, "main");//函数现在栈顶

现在,我们可以调用这个函数,并传递给它正确的参数:

lua_pushnumber(L, 100); //将参数压栈
lua_pcall(L, 1, 1, 0); //调用函数,有一个参数,一个返回值
//返回值现在栈顶
int result = lua_tonumber(L, -1);

result 就是函数的返回值

完整的测试代码如下:

#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;
}





在你的游戏中应用Lua(2):扩展Lua


Lua本身定位在一种轻量级的,灵活的,可扩充的脚本语言,这意味着你可以自由的扩充Lua,为你自己的游戏量身定做一个脚本语言。

你可以在主机程序中向脚本提供你自定的api,供脚本调用。

Lua定义了一种类型:lua_CFunction,这是一个函数指针,它的原型是:
typedef int (*lua_CFunction) (lua_State *L);

这意味着只有这种类型的函数才能向Lua注册。

首先,我们定义一个函数

int foo(lua_State *L)
{
//首先取出脚本执行这个函数时压入栈的参数
//假设这个函数提供一个参数,有两个返回值

//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;
}

我们可以在脚本中这样调用这个函数

r1, r2 = foo("hello")

print(r1..r2)

完整的测试代码如下:

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int foo(lua_State *L)
{
//首先取出脚本执行这个函数时压入栈的参数
//假设这个函数提供一个参数,有两个返回值

//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;
}

程序输出:
hello
100200




在你的游戏中应用Lua(3):using lua in cpp


lua和主机程序交换参数是通过一个运行时栈来进行的,运行时栈的信息放在一个lua_State的结构中,lua提供的api都需要一个lua_State*的指针,除了一个:

lua_open();

这个函数将返回一个lua_State*型的指针,在你的游戏代码中,你可以仅仅拥有一个这样的指针,也可以有多个这样的指针。

最后,你需要释放这个指针,通过函数:

lua_close(L);

注意这个事实,在你的主机程序中,open()与close()永远是成对出现的,在c++中,如果有一些事情是成对出现的,这通常意味着你需要一个构造函数和一个析构函数,所以,我们首先对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}
};

这是lua提供给用户的一些辅助的lib,在使用lua_State的时候,你可以选择打开或者关闭它。

完整的类实现如下:


//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;
};


通常我们仅仅在游戏代码中使用一个lua_State*的指针,所以我们为它实现一个单件,默认打开所有lua提供的lib:


//return the global instance
state* lua_state()
{
static state L(true);

return &L;
}




在你的游戏中应用Lua(3):using lua in cpp(封装栈操作) 

前面提到了lua与主机程序是通过一个运行时栈来交换信息的,所以我们把对栈的访问做一下简单的封装。

我们利用从c++的函数重载机制对这些操作做封装,重载提供给我们一种以统一的方式来处理操作的机制。

向lua传递信息是通过压栈的操作来完成的,所以我们定义一些Push()函数:

inline void Push(lua_State *L, int value);
inline void Push(lua_State *L, bool value);
...

对应简单的c++内建类型,我们实现出相同的Push函数,至于函数内部的实现是非常的简单,只要利用lua提供的api来实现即可,例如:

inline void Push(lua_State *L, int value)
{
lua_pushnumber(L, value);
}

这种方式带来的好处是,在我们的代码中我们可以以一种统一的方式来处理压栈操作,如果有一种类型没有定义相关的压栈操作,将产生一个编译期错误。

后面我会提到,如何将一个用户自定义类型的指针传递到lua中,在那种情况下,我们的基本代码无须改变,只要添加一个相应的Push()函数即可。

记住close-open原则吧,它的意思是对修改是封闭的,对扩充是开放的,好的类库设计允许你扩充它,而无须修改它的实现,甚至无须重新编译。

《c++泛型设计新思维》一书提到了一种技术叫type2type,它的本质是很简单:

template <typename T>
struct type2type
{
typedef T U;
};

正如你看到的,它并没有任何数据成员,它的存在只是为了携带类型信息。

类型到类型的映射在应用于重载函数时是非常有用的,应用type2type,可以实现编译期的分派。

下面看看我们如何在从栈中取得lua信息时应用type2type:

测试类型:由于lua的类型系统与c++是不相同的,所以,我们要对栈中的信息做一下类型检测。

inline bool Match(type2type<bool>, lua_State *L, int idx)
{
return lua_type(L, idx) == LUA_TBOOLEAN;
}

类似的,我们要为cpp的内建类型提供相应的Match函数:

inline bool Match(type2type<int>, lua_State *L, int idx);
inline bool Match(type2type<const char*>, lua_State *L, int idx);

...

可以看出,type2type的存在只是为了在调用Match时决议到正确的函数上,由于它没有任何成员,所以不存在运行时的成本。

同样,我们为cpp内建类型提供Get()函数:

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

...

我想你可能注意到了,在int Get(type2type<int>)中有一个转型的动作,由于lua的类型系统与cpp的类型不同,所以转型动作必须的。

除此之外,在Get重载函数(s)中还有一个小小的细节,每个Get的函数的返回值是不相同的,因为重载机制是依靠参数的不同来识别的,而不是返回值。

前面说的都是一些基础的封装,下来我们将介绍如何向lua注册一个多参数的c函数。还记得吗?利用lua的api只能注册int (*ua_CFunction)(lua_State *)型的c函数,别忘记了,lua是用c写的。




在你的游戏中应用Lua(3):using lua in cpp(注册不同类型的c函数)之一 

前面说到,我们可以利用lua提供的api,向脚本提供我们自己的函数,在lua中,只有lua_CFunction类型的函数才能直接向lua注册,lua_CFunction实际上是一个函数指针:
typedef int (*lua_CFunction)(lua_State *L);

而在实际的应用中,我们可能需要向lua注册各种参数和返回值类型的函数,例如,提供一个add脚本函数,返回两个值的和:

int add(int x, int y);

为了实现这个目的,首先,我们定义个lua_CFunction类型的函数:

int add_proxy(lua_State *L)
{
//取得参数
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);
  
  //调用真正的函数
  int result = add(x, y);
  
  //返回结果
  Push(result);
  
  return 1;
}

现在,我们可以向lua注册这个函数:

lua_pushstring(L, “add”);
lua_pushcclosure(L, add_proxy, 0);
lua_settable(L, LUA_GLOBALINDEX);

在脚本中可以这样调用这个函数:

print(add(100, 200))

从上面的步骤可以看出,如果需要向lua注册一个非lua_CFunction类型的函数,需要:
1. 为该函数实现一个封装调用。
2. 在封装调用函数中从lua栈中取得提供的参数。
3. 使用参数调用该函数。
4. 向lua传递其结果。

注意,我们目前只是针对全局c函数,类的成员函数暂时不涉及,在cpp中,类的静态成员函数与c函数类似。

假设我们有多个非lua_CFunction类型的函数向lua注册,我们需要为每一个函数重复上面的步骤,产生一个封装调用,可以看出,这些步骤大多是机械的,因此,我们需要一种方式自动的实现上面的步骤。

首先看步骤1,在cpp中,产生这样一个封装调用的函数的最佳的方式是使用template,我们需要提供一个lua_CFunction类型的模板函数,在这个函数中调用真正的向脚本注册的函数,类似于这样:
template <typename Func>
inline int register_proxy(lua_State *L)

现在的问题在于:我们要在这个函数中调用真正的函数,那么我们必须要在这个函数中取得一个函数指针,然而,lua_CFunction类型的函数又不允许你在增加别的参数来提供这个函数指针,现在该怎么让regisger_proxy函数知道我们真正要注册的函数呢?

在oop中,似乎可以使用类来解决这个问题:

template <Func>
struct register_helper
{
explicit register_helper(Func fn) : m_func(fn)
{}
int register_proxy(lua_State *L);

protected:
Func m_func;
};

可是不要忘记,lua_CFunction类型指向的是一个c函数,而不是一个成员函数,他们的调用方式是不一样的,如果将上面的int register_proxy()设置为静态成员函数也不行,因为我们需要访问类的成员变量m_func;

让我们再观察一下lua_CFunction类型的函数:

int register_proxy(lua_State *L);

我们看到,这里面有一个lua_State*型的指针,我们能不能将真正的函数指针放到这里面存储,到真正调用的时候,再从里面取出来呢?

Lua提供了一个api可以存储用户数据:
Lua_newuserdata(L, size)

在适当的时刻,我们可以再取出这个数据:

lua_touserdata(L, idx)

ok,现在传递函数指针的问题我们已经解决了,后面再看第二步:取得参数。





在你的游戏中应用Lua(3):using lua in cpp(注册不同类型的c函数)之二

在解决了传递函数指针的问题之后,让我们来看看调用函数时会有一些什么样的问题。

首 先,当我们通过函数指针调用这个函数的时候,由于我们面对的是未知类型的函数,也就是说,我们并不知道参数的个数,参数的类型,还有返回值的类型,所以我 们不能直接从lua栈中取得参数,当然,我们可以通过运行时测试栈中的信息来得到lua传递进来的参数的个数和类型,这意味着我们在稍后通过函数指针调用 函数时也需要动态的根据参数的个数和类型来决议到正确的函数,这样,除了运行时的成本,cpp提供给我们的强类型检查机制的好处也剩不了多少了,我们需要 的是一种静态的编译时的“多态”。

在cpp中,至少有两种方法可以实现这点。最直接简单的是使用函数重载,还有一种是利用模板特化机制。

简单的介绍一下模板特化:

在cpp中,可以针对一个模板函数或者模板类写出一些特化版本,编译器在匹配模板参数时会寻找最合适的一个版本。类似于这样:

templat <typename T>
T foo()
{
T tmp();
return tmp;
}

//提供特化版本
template <>
int foo()
{
return 100;
}

在main()函数中,我们可以显示指定使用哪个版本的foo:

int main(int argc, char **argv)
{
cout << foo<int>() << endl;
return 0;
}

程序将输出100,而不是0,以上代码在 g++中编译通过,由于vc6对于模板的支持不是很好,所以有一些模板的技术在vc6中可能不能编译通过。

所以最好使用重载来解决这个问题,在封装函数调用中,我们首先取得这个函数指针,然后,我们要提供一个Call函数来真正调用这个函数,类似于这样:
//伪代码
int Call(pfn, lua_State *L, int idx)

可是我们并不知道这个函数指针的类型,现在该怎么写呢?别忘记了,我们的register_proxy()是一个模板函数,它有一个参数表示了这个指针的类型:

template <typename Func>
int register_proxy(lua_State *L)
{
//伪代码,通过L参数取得这个指针
unsigned char *buffer = get_pointer(L);

//对这个指针做强制类型转化,调用Call函数
return Call(*(Func*)buffer, L, 1);
}

由重载函数Call调用真正的函数,这样,我们可以使用lua api注册相关的函数,下来我们提供一个注册的函数:

template <typename Func>
void lua_pushdirectclosure(Func fn, lua_State *L, int nUpvalue)
{
//伪代码,向L存储函数指针
save_pointer(L);

//向lua提供我们的register_proxy函数
lua_pushcclosure(L, register_proxy<Func>, nUpvalue + 1);
}

再定义相关的注册宏:
#define lua_register_directclosure(L, func) /
lua_pushstring(L, #func);
lua_pushdirectclosure(func, L, 1);
lua_settable(L, LUA_GLOBALINDEX)

现在,假设我们有一个int add(int x, int y)这样的函数,我们可以直接向lua注册:

lua_register_directclosure(L, add);

看,最后使用起来很方便吧,我们再也不用手写那么多的封装调用的代码啦,不过问题还没有完,后面我们还得解决Call函数的问题。




在你的游戏中应用Lua(3):using lua in cpp(注册不同类型的c函数)之三 


下面,让我们集中精力来解决Call重载函数的问题吧。

前面已经说过来,Call重载函数接受一个函数指针,然后从lua栈中根据函数指针的类型,取得相关的参数,并调用这个函数,然后将返回值压入lua栈,类似于这样:

//伪代码
int Call(pfn, lua_State *L, int idx)

现在的问题是pfn该如何声明?我们知道这是一个函数指针,然而其参数,以及返回值都是未知的类型,如果我们知道返回值和参数的类型,我们可以用一个typedef来声明它:

typedef void (*pfn)();

int Call(pfn fn, lua_State *L, int idx);

我们知道的返回值以及参数的类型只是一个模板参数T,在cpp中,我们不能这样写:

template <typename T>
typedef T (*Func) ();

一种解决办法是使用类模板:

template <typename T>
struct CallHelper
{
typedef T (*Func) ();
};

然后在Call中引用它:

template <typename T>
int Call(typename CallHelper::Func fn, lua_State *L, int idx)

注意typename关键字,如果没有这个关键字,在g++中会产生一个编译警告,它的意思是告诉编译器,CallHelper::Func是一个类型,而不是变量。

如果我们这样来解决,就需要在CallHelper中为每种情况大量定义各种类型的函数指针,还有一种方法,写法比较古怪,考虑一个函数中参数的声明:

void (int n);

首先是类型,然后是变量,而应用于函数指针上:

typedef void (*pfn) ();
void (pfn fn);

事实上,可以将typedef直接在参数表中写出来:

void (void (*pfn)() );

这样,我们的Call函数可以直接这样写:

//针对没有参数的Call函数
template <typename RT>
int Call(RT (*Func) () , lua_State *L, int idx);
{
//调用Func
RT ret = (*Func)();

//将返回值交给lua
Push(L, ret);

//告诉lua有多少个返回值
return 1;
}

//针对有一个参数的Call
template <typename T, typename P1>
int Call(RT (*Func)(), lua_State *L, int idx)
{
//从lua中取得参数
if (!Match(TypeWrapper<P1>(), L, -1)
return 0;

RT ret = (*Func) (Get(TypeWrapper<P1>(), L, -1));

Push(L, ret);
return 1;
}

按照上面的写法,我们可以提供任意参数个数的Call函数,现在回到最初的时候,我们的函数指针要通过lua_State *L来存储,这只要利用lua提供的api就可以了,还记得我们的lua_pushdirectclosure函数吗:

template <typename Func>
void lua_pushdirectclosure(Func fn, lua_State *L, int nUpvalue)
{
//伪代码,向L存储函数指针
save_pointer(L);

//向lua提供我们的register_proxy函数
lua_pushcclosure(L, register_proxy<Func>, nUpvalue + 1);
}

其中,save_pointer(L)可以这样实现:

void save_pointer(lua_State *L)
{
unsigned char* buffer = (unsigned char*)lua_newuserdata(L, sizeof(func));
memcpy(buffer, &func, sizeof(func));
}


而在register_proxy函数中:

template <typename Func>
int register_proxy(lua_State *L)
{
//伪代码,通过L参数取得这个指针
unsigned char *buffer = get_pointer(L);
//对这个指针做强制类型转化,调用Call函数
return Call(*(Func*)buffer, L, 1);
}
get_pointer函数可以这样实现:

unsigned char* get_pointer(lua_State *L)
{
  return (unsigned char*) lua_touserdata(L, lua_upvalueindex(1));
}

这一点能够有效运作主要依赖于这样一个事实:

我们在lua栈中保存这个指针之后,在没有对栈做任何操作的情况下,又把它从栈中取了出来,所以不会弄乱lua栈中的信息,记住,lua栈中的数据是由用户保证来清空的。

到现在,我们已经可以向lua注册任意个参数的c函数了,只需简单的一行代码:

lua_register_directclosure(L, func)就可以啦。







在你的游戏中应用Lua(3):Using Lua in cpp(基本数据类型、指针和引用)之一

Using Lua in cpp(基本数据类型、指针和引用)

前面介绍的都是针对cpp中的内建基本数据类型,然而,即使是这样,在面对指针和引用的时候,情况也会变得复杂起来。

使用前面我们已经完成的宏lua_register_directclosure只能注册by value形式的参数的函数,当参数中存在指针和引用的时候(再强调一次,目前只针对基本数据类型):

1、 如果是一个指针,通常实现函数的意图是以这个指针传递出一个结果来。
2、 如果是一个引用,同上。
3、 如果是一个const指针,通常只有面对char*的时候才使用const,实现函数的意图是,不会改变这个参数的内容。其它情况一般都避免出现使用const指针。
4、 如果是一个const引用,对于基本数据类型来说,一般都避免出现这种情况。

Lua和cpp都允许函数用某种方式返回多个值,对于cpp来说,多个返回值是通过上述的第1和第2种情况返回的,对于lua来说,多个返回值可以直接返回:

--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)

程序输出:200100

同样的,在主机程序中,我们也可以向Lua返回多个值:

int swap(lua_State *L)
{
//取得两个参数
int x = Get(TypeWrapper<int>(), L, -1);
int y = Get(TypeWrapper<int>(), L, -2);

//交换值
int tmp = x;
x = y;
y = tmp;

//向Lua返回值
Push(L, x);
Push(L, y);

  //告诉Lua我们返回了多少个值
return 2;
}

现在我们可以在Lua中这样调用这个函数:

x = 100
y = 200

x, y = swap(x, y)

在我们的register_proxy函数中只能对基本数据类型的by value方式有效,根据我们上面的分析,如果我们能够在编译期知道,对于一个模板参数T:
1、 这是一个基本的数据类型,还是一个用户自定义的数据类型?
2、 这是一个普通的指针,还是一个iterator?
3、 这是一个引用吗?
4、 这是一个const 普通指针吗?
5、 这是一个const 引用吗?

如果我们能知道这些,那么,根据我们上面的分析,我们希望:(只针对基本数据类型)
1、 如果这是一个指针,我们希望把指针所指的内容返回给Lua。
2、 如果这是一个引用,我们希望把引用的指返回给Lua。
3、 如果这是const指针,我们希望将从Lua栈中取得的参数传递
          给调用函数。
4、 如果这是一个const引用,我们也希望把从Lua栈中取得的参
          数传递给调用函数。

Lua栈操作详解 1.让我们看看下面例子C++调用LUA函数的例子栈的使用情况 #include "stdafx.h" int main() { //新建一个lua状态 lua_State *pLua = luaL_newstate(); if( nullptr == pLua ) { return 0; } //得到初始化栈大小 printf( "初始化栈大小:%d\n", lua_gett 阅读详情

相关推荐

lua--从小白开始(3)

lua笔记

shinhwalin 4154

Lua常用函数说明

1.lua_setglobal (1)原型 void lua_setglobal (lua_State *L, const char *name); (2)作用   将栈顶元素赋值给name变量。(name参数的值,是lua脚本中全部变量的名字。) (注:调用完函数,我们调用luaL_dofile,然后我们就可以在lua脚本中打印出_G表中内容,会发现name名字的变量正在其中,所以可以

和尚的专栏 9276

Lua脚本在游戏开发中的5个实战应用:从AI行为到插件系统

本文深入探讨了Lua脚本在游戏开发中的五大核心实战应用,包括游戏逻辑热更新、AI行为树构建、事件驱动UI响应、数据驱动配置以及插件系统开发。通过将Lua作为轻量级“粘合剂”嵌入C++引擎,开发者能实现逻辑与引擎解耦,支持动态修改与热更新,极大提升开发迭代效率和游戏可扩展性。

tequila的博客 1203

游戏辅助从0到1-C++调用调用游戏Lua脚本实现辅助

本课程以TLB*和W*W为例子,讲述如何通过游戏的Lua脚本来实现一个游戏的辅助。众所周知,游戏的逻辑基本上都是采用Lua等脚本语言编写的,我们通过逆向分析游戏,找到游戏Lua接口和Lua函数后,很容易的就能实现一个游戏辅助所需要的所有功能,比起逆向游戏来讲,本课程更容易上手,适合0基础的朋友们学习,在学完本课程后再去学习游戏逆向技术会事半功倍,再者,在实际的游戏辅助编写的过程中,可能有很多地方我们需要花大量的时间找call,如果是我们直接去调用游戏的Lua脚本的话也会节约我们大量的时间,节省我们的开发成本。 本课程适合有一定的C/C++基础,想做点实际应用,或者是对游戏辅助有强烈兴趣的朋友们学习。 课程目前已经完成了第一部分的制作,第二部分的课件已经完成 大概每周会都更新1到2节课的内容 

Lua 在游戏开发中的应用、优缺点、C 语言完整对比

Lua 优势是开发快、可热更、易嵌入,是游戏上层业务的最优脚本;短板是性能、GC、动态类型问题。C 优势是极致性能、底层可控、运行稳定,承担游戏引擎核心;短板开发慢、无法热更新、上手难度高。二者不是替代关系,而是互补组合,这也是目前所有商业游戏统一的技术架构。

a18551767309的博客 523

Unity游戏开发中Lua语言的学习与应用

Unity 是全球领先的游戏开发引擎之一,它支持跨平台的开发,允许开发者使用同一个项目文件来构建游戏,并发布到多个设备和平台。从2D到3D,从简单的休闲游戏到复杂的模拟器,Unity都能提供一个全面的解决方案。Lua是一种轻量级的脚本语言,由巴西里约热内卢天主教大学的Roberto Ierusalimschy, Waldemar Celes和Luiz Henrique de Figueiredo在1993年开发。它的设计目标是嵌入到应用程序中提供灵活的扩展和定制功能。

weixin_42174098的博客 1015

为什么Lua在游戏开发中应用普遍?

开发者可以直接基于Lua实现游戏的业务逻辑和内容扩展,如新增关卡,同时利用Lua的热更新特性,实现代码的即时修改和应用,无需停止引擎。支持热更新作为Lua游戏开发语言的一个独特优势,其主要价值在于开发者可以不停机热加载修改后的Lua代码,来快速对游戏中的逻辑和内容进行修复和迭代更新,而无需等待整个客户端版本打包发布。同时也方便了游戏平衡性的优化调整。此外,Lua的灵活性和可扩展性使其非常适合在快速迭代的开发环境下使用,这对于移动应用和云游戏的开发至关重要,因为它们需要不断更新和改进以适应不断变化的需求。

Richard_shen的博客 9897

Vivado 2018.3 SDK烧写bin文件避坑指南:如何解决QSPI模式下的常见错误

本文深入解析了在Vivado 2018.3 SDK中为Zynq FPGA烧写QSPI Flash时遇到的常见问题,特别是启动模式为QSPI时出现的警告与失败。文章从Zynq启动机制入手,提供了修改FSBL代码以强制使用JTAG模式、调整硬件配置等核心解决方案,并分享了实战案例与最佳实践,帮助开发者高效避坑,确保bin文件烧写成功。

arduino9maker的博客 389

在你的游戏中应用Lua

本文转自zx9597446的Blog,欢迎对Lua感兴趣的朋友互相交流, mailto: zx9597446@sina.com 在你的游戏中应用Lua(1):在你的游戏代码中运行解释器  通常,你希望在你的游戏开始的时候读取一些信息,以配置你的游戏,这些信息通常都是放到一个文本文件中,在你的游戏启动的时候,你需要打开这个文件,然后解析字符串,找到所需要的信息。  是的,或许你认为这样就

痞子龙3D编程 1943

基于STM32F407与AD9910/AD9959的高频可调DDS信号源设计与实战

本文详细介绍了基于STM32F407微控制器与AD9910/AD9959高性能DDS芯片的高频可调信号源设计与实现。文章从DDS原理入手,阐述了双芯片方案的优势,并重点分享了高频电路设计中的电源、PCB布局布线等关键硬件设计技巧与避坑指南。同时,提供了通过SPI配置DDS芯片及利用Matlab生成任意波形的软件驱动方法,旨在帮助开发者打造一个覆盖10Hz至4200MHz的实用信号发生器。

weixin_36184718的博客 120

Python 静态导入与动态导入

python中模块的静态导入 与 动态导入(运行时指定模块),以及具体对应示例。

慢也好,步子小也好,往前走就好~ 717

Lua脚本在游戏开发中的5个实战应用案例

实际使用时发现它的AI辅助能自动补全Lua语法,遇到问题还能实时对话调试,比本地开发环境更省心。Lua作为轻量级脚本语言,在游戏开发中因其高效和易嵌入性备受青睐。今天分享5个实际项目中的Lua应用案例,这些方案都经过生产环境验证,能直接提升开发效率。用Lua实现NPC的「巡逻-追击-逃跑」三态切换特别合适。相比C++实现,用Lua调试时可以实时修改行为逻辑。设置种子保证可复现性,然后通过柏林噪声生成高度图,最后用。分形算法用Lua实现比预想更高效。实现技能CD的核心是时间戳比对。循环检查当前时间差。

BlackStone33的博客 453

免费搞定DWG转DXF:ODA File Converter保姆级配置指南(附常见报错解决)

本文详细介绍了如何使用ODA File Converter免费工具实现DWG到DXF的格式转换,包括环境配置、命令行参数解析、编程集成方案及常见问题解决。特别针对网络访问异常、参数配置等实际痛点提供应对策略,帮助用户高效完成工程设计文件的格式转换。

weixin_29291185的博客 6008

Lua 在游戏开发中的高级应用:从脚本控制到性能优化

Lua 在游戏开发中的高级应用涵盖了脚本控制和性能优化等多个方面。通过合理运用 Lua 的特性,结合其他技术,开发者能够打造出高效、稳定且富有创意的游戏。无论是小型独立游戏,还是大型商业游戏,Lua 都为游戏开发提供了强大的支持,随着游戏行业的不断发展,Lua 在游戏开发中的应用前景将更加广阔。未来,随着硬件性能的提升和游戏开发技术的不断进步,Lua 有望在更多领域得到应用,如虚拟现实(VR)、增强现实(AR)游戏开发等。

数字魔方操控师的博客 766

群晖NAS硬盘挂了别慌!用Ubuntu 18外接USB保姆级数据抢救指南

本文提供群晖NAS硬盘故障时的数据抢救指南,详细介绍了使用Ubuntu 18外接USB硬盘盒的完整流程。从构建救援环境、硬盘识别到数据迁移策略,涵盖关键步骤和实用技巧,帮助用户零成本恢复重要数据。特别适合非RAID配置的单盘故障场景。

9o8p7i6u5y的博客 1146

带隙基准源要点

要点: 负反馈>正反馈;电流镜匹配;双极型晶体管匹配;电阻匹配; 经过R0的电流????????????=????????????????−????????????????????????=∆????????????????????i_R0=(〖VBE〗_1-〖VBE〗_2)/R_0 =∆VBE/R_0 经过R1的电流????????????=i_R1= ????????????????????VBE/R_1 零温电流I = ∆????????????????????∆VBE/R_0 +

HI_WALLE的博客 2264

在你的游戏中应用LUA收藏

  在你的游戏中应用LUA收藏 新一篇: LUA杂项 | 旧一篇: 使用C++调用 LUA 函数 function StorePage(){d=document;t=d.selection?(d.selection.type!=None?d.selection.createRange().text:):(d.getSelection?d.getSelection()

skyremember的专栏 764

流体动力学仿真软件:XFlow_(16).案例研究:热传输与相变

包括密度、比热容、热传导系数等。

kkchenjj的博客 722

lua脚本在游戏中的应用

为什么要在游戏中使用脚本语言?   要解释这个问题首先我们先来了解一下脚本语言的特性: 学习门槛低,快速上手 开发成本低,可维护性强 动态语言,灵活性高   相对于C/C++这类高复杂性、高风险的编译型语言来说,Lua脚本做为一种轻量级的动态语言,简单的语言特性,精简的核心和基础库,使得语言的学习门槛大大的降低,即使是没有任何游戏经验的人都能快速上手,开发游戏功能。实际上...

weixin_34362991的博客 537

什么是信噪比?信噪比越大好还是越小好?

在进行声音增强的时候,往往需要考虑声音信号和噪声的信噪比,那么信噪比是什么呢?越大越好还是越小越好,此处引入百度百科的解读: 信噪比,英文名称叫做SNR或S/N(SIGNAL-NOISE RATIO),又称为讯噪比。是指一个电子设备或者电子系统中信号与噪声的比例。这里面的信号指的是来自设备外部需要通过这台设备进行处理的电子信号,噪声是指经过该设备后产生的原信号中并不存在的无规则的额外信号(或信息),并且该种信号并不随原信号的变化而变化。 同样是“原信号不存在”还有一种东西叫“失真”,失真和噪声实际上有一

码匀的博客 7万+

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Ant引擎协程系统:Lua协程在游戏中的应用

在游戏开发中,我们经常需要处理复杂的异步操作:角色动画序列、AI行为树、对话系统、资源加载等。传统的回调方式会导致代码嵌套过深,难以维护。Ant引擎基于Lua的协程系统提供了优雅的解决方案,让异步代码看起来像同步代码一样清晰。 ## Ant协程系统架构解析 ### 核心设计理念 Ant的协程系统在标准Lua协程基础上进行了增强,主要特点包括: - **与ltask框架深度集成**:支持多线...

gitblog_00157的博客 652

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

《游戏AI开发指南(基于Lua的人工智能在游戏中的应用)》(Yanlz+Unity+SteamVR+5G+AI+VR云游戏+Lua+人机交互+沙箱+导航+决策树+影响力地图+立钻哥哥+==)

《游戏AI开发指南(基于Lua的人工智能在游戏中的应用)》 《游戏AI开发指南(基于Lua的人工智能在游戏中的应用)》 版本 作者 参与者 完成日期 备注 YanlzAI_LuaForGames_V01_1.0 严立钻 ...

VRunSoftYanlz的博客 6876

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Lua脚本在Lumos Engine中的应用:快速开发游戏逻辑

Lumos Engine作为一款跨平台C++ 2D/3D游戏引擎,提供了强大的Lua脚本支持,让开发者能够快速实现游戏逻辑。通过Lua脚本,你可以轻松控制游戏实体、处理用户输入、创建交互系统,而无需深入底层C++代码。本文将介绍如何利用Lua脚本在Lumos Engine中高效开发游戏功能,从基础设置到实际案例,帮助你快速上手。 ## 🚀 为什么选择Lua脚本开发游戏逻辑? Lua脚本在游戏

gitblog_00064的博客 865
上一篇: linux平台下svn环境搭建【未完】
下一篇: 关于模版的一个问题
nellson
博客等级 码龄20年 48粉丝 84原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值