`
chinamming
  • 浏览: 151256 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Lua的使用心得: 数据定义和过程定义(Lua在程序中的数据定义和过程定义的界定原则的研究)

 
阅读更多

Lua在程序中的数据定义和过程定义的界定原则的研究


引言

作为宿主语言的衍生,Lua无论从数据对象的填充,还是处理过程的定制,都提供了很好的支持。甚至我们可以将全部的宿主语言都搬到Lua里来写。在这样大的灵活度下,如何界定什么样的函数需要导出到Lua,如何对数据对象定义,或者说使用Lua的基本思路是什么,时常让刚学会Lua的人迷惑。本文使用一个实际例子来讲述一个C++系统和Lua结合的演变过程、思路,并比较各个方案之间的优劣,提供一个使用Lua的参考思路。


找出需要定制的地方

在我们的游戏引擎中有一个 renderroom 系统,就是用来实时渲染一个对象到贴图的轻量系统。整个系统的静态结构就在这里略了,重点在于RenderRoom的定制。当你需要渲染一个角色的时候,需要确定
摄象机的位置、朝向
场景模型、特效

最开始的时候也就考虑到了这3点。因为我们那个时候需要渲染生物只有2种需求,渲染头和渲染全身,因此在C++里定义了以下类型

struct RenderType
{
enum T
{
Head,
Body,
};

};
并写了一个处理函数:
void refreshTexture(RenderType::T type)
{
switch( type)
{
case RenderType::Head:
设置摄象机等参数;
break;
case RenderType::Head:
设置摄象机等参数;
break;
}
}


但是在这之后发现,渲染人型生物和渲染爬行类生物的时候, Head的参数根本就不同。
为了迅速解决问题,添加了新的Head类型。
这当然是个非常临时的方法,所以不久之后我们就碰到生物体型过多,参数类型过多的问题。于是“可配置”的需求就变的强烈了。这个时候开始考虑利用Lua作为数据定义文件。我尝试了以下几种方式。

一、在Lua中定义数据文件结构,将数据对象返回给CPP进行解析。

// Lua
local data={
cameraPos = {x,y,z} // 这是一个原生结构,如果你将 Vector3 之类的结构导出到Lua了,这里就可以省不少事情
cameraDir = {x,y,z} // 这是一个原生结构,如果你将 Vector3 之类的结构导出到Lua了,这里就可以省不少事情
scene = "RenderRoomScene1.scene"
};

parseRenderRoomData(data); ---在C++中定义的函数. 导出到Lua
data=nil;

// CPP
parseRenderRoomData(lua_State* L)
{
// 解析表;
// 使用 lua_next 取得各个元素,按照Lua中定义的数据格式进行解析
};


这个方案是不好的,原因在于 parseRenderRoomData 对数据的解析过程受制于 data的定义,而data中的数据经常变化,甚至结构也经常变化,所以需要在 parseRenderRoomData 函数中编写大量的异常处理代码防止数据文件损坏的时候程序不崩溃,或者说引起Lua的栈不平衡.
这个方案唯一的优点可能就是好理解吧:Lua提供数据对象,CPP解析并创建实例对象.


二、CPP中定义数据文件结构,创建对象, Lua填充数据, 将数据对象返回给CPP进行解析。

这个方案的代码静态结构大致是这样:


//CPP
struct RenderRoomData
{
// 这是一个原生结构,如果你将 Vector3 之类的结构导出到Lua了,这里就可以省不少事情
float cameraPos_x;
float cameraPos_y;
float cameraPos_z;
float cameraDir_x;
float cameraDir_y;
float cameraDir_z;
std::string sceneFile;
};

RenderRoomData* createRenderRoomDate()
{
return new RenderRoomData;
}

parseRenderRoomData(lua_State* L)
{
// 同上,但是是按照 RenderRoomData 的定义进行解析.
}

// Lua
local data= createRenderRoomDate()
data.cameraPos_x;
data.cameraPos_y;
data.cameraPos_z;
data.cameraDir_x;
data.cameraDir_y;
data.cameraDir_z;
data.sceneFile = "RenderRoomScene1.scene"
parseRenderRoomData(data);
data=nil;

这个方案从核心上来说和方案一一致,就是Lua提供数据. 但是在数据协议方面则做了非常大的改进,因为是使用CPP定义的对象,所以解析的过程就可以确定下来.这样可以将错误处理代码省略.但这只是理由之一,最大的理由在于Lua文件一般是由编辑器或策划来负责维护,现在数据格式由程序定义好,则会大大减少数据文件出错几率.并且数据定义这样的事情也就在一开始变动频繁,当稳定后基本上是不变的.考虑到变动的成本,方案二远胜于方案一.

在数据定义方面,我会选择方案二,因为实际情况是如果你采用方案一,写错误处理的时间会根据数据文件的复杂程度急剧增长,最终从各个方面都会劣于方案一。但是如果是在系统设计的初期,几乎没有任何错误处理,所以方案一也不失为一种方便的原型模型。这个需要参考最终代码的质量标准。

(我哭死了,写了1个多小时的内容被我自己覆盖了...以后再也不用txt写东西了,用vs)
新的演化

如果需求能一次到位,写程序也就不用这样累了,所以"需求总是变化的"这句真理永远适用。RenderRoom的核心思路是为了将一个对象渲染到贴图,在一些引擎里这个系统叫"化身". 根据使用场合的不同,我们会希望贴图有的需要背景使用透明色,比如头像,有的又不希望它透明,比如装备栏里的人物角色,并且在一些时候你还想定制Fog,Bloom等参数. 但是这样的配置数据也就只有有限的几种,而像摄象机位置、朝向这样的信息是需要每怪物单独设置的,这样RenderRoomData里就需要包含2种不同类型的数据: 每对象数据 和 每类型数据, 两者的数量级比例大致是1000:10. 一般对于这样的数据拆分的常见手段是使用ID或者说句柄. 在这里首先定义一个新的数据结构:
struct RenderParameters
{
// 渲染相关的参数定义
};
不在这里定义ID的理由,随后说明.


对于这个新结构的使用方法,有以下几种方案:
一、数据定义冗余策略.

// CPP
在 struct RenderRoomData 中增加一个成员变量
struct RenderRoomData
{
...
RenderParameters renderParameters;
};


// Lua

local data= createRenderRoomDate()
...
data.renderParameters.propory1 = a;
data.renderParameters.propory2 = b;
data.renderParameters.propory3 = c;

...


对于数据冗余策略,ID是没有必要的,所以在RenderParameters的原型定义里没有定义ID。


二、使用ID检索策略

// CPP

在 struct RenderRoomData 中增加一个成员变量

struct RenderParameters
{
unsigned int typeID;
// 渲染相关的参数定义
};

在 struct RenderRoomData 中增加一个成员变量
struct RenderRoomData
{
...
unsigned int renderParametersID;
};

现在你可以使用任何喜欢的检索方式对RenderParameters对象进行创建和检索,比如:

1、使用在之前讨论数据定义的方式中提到的方案二,在CPP中创建,Lua中填充,并在CPP中检索他们。

2、利用Lua的全局对象,对其进行填充和检索。
3、定义Lua函数,利用ID将冗余数据自动填充。这个方法是方案一和方案二的结合。


//Lua

--相信看到这里,各位看官都有自己的想法了,我就不叨叨了


三、定义Lua函数对象

使用这个方法有先决条件:如果你将 refreshTexture 中调用的具体干活的函数导出了的话,就可以使用这个方法。暂时将这些功能函数称其为“干活函数”。

分析我们定义RenderParameters的最初目的就可以得知,实际上我们就是在为干活函数搜集参数,并从Lua中传递到CPP中给它们使用。因此如果我们能在Lua中直接调用干活函数的话,就可以使用更加简单的方式来处理“RenderRoom的定制”这个终极目标。

// CPP
// 实现一个能调用Lua函数的函数,如
template<typename Par1,typename Par2, typename Par3>

bool callLuaFunction(const char* functionName, Par1 param1, Par2 param2, Par3 param3);
这个函数需要能把 Lua对象向 ParX 类型的对象进行转化,实现方式很多,各位自行拿捏.
定义参数的理由随后说明.


// Lua

--定义一个全局表
RenderRoomBulider = {}
--添加具体Builder
RenderRoomBulider["头像"] = function(cameraPos, cameraDir, viewport) -- 视口对象.因为Fog , Bloom等参数是每视口设置的.
SetCameraPos(cameraPos);

SetCameraPos(cameraPos);
CreateSceneStage("RenderRoomScene1.scene");
SetFogDistance(...);
SetFogColor(...);

SetBloom();
end;
RenderRoomBulider["装备栏"] = function(cameraPos, cameraDir, viewport)
SetCameraPos(cameraPos);

SetCameraPos(cameraPos);
CreateSceneStage("RenderRoomScene1.scene");
SetFogDistance(...);
SetFogColor(...);

SetBloom();
end;

....


最后在RenderRoom初始化的地方写如下代码:

std::string builderTypeName = "RenderRoomBulider[头像]";
Vector3 cameraPos = 具体怪物的CameraPos;
Vector3 cameraDir = 具体怪物的CameraDir;
Viewport* viewport = RenderRoom关联的视口;
callLuaFunction( builderTypeName.c_str(), cameraPos, cameraDir, viewport);


最后的问题在于具体怪物的Camera属性记录在哪呢, 我相信每个游戏都有一个怪物数据表吧,就记录在那里吧.

Camera属性是每怪物的,RenderParameter是每应用场景的,我们通过怪物表获取Camera属性,通过Lua获取RenderParameter属性,最终将RenderRoom的定制从程序中释放出来了.


小结

Lua的特点在于灵活,具体表现就是没有任何限制,但是就象你可以拿 C风格的强制类型转换在C++里写任意对象的转换一样, 总是有原则存在其中的,这些原则可以帮助我们写出健壮的、易维护的代码,对于脚本语言更加如此. 这些原则可能来自于程序经理要求,项目规范,个人习惯等等方面,但是最重要的一条就是,要统一原则,并贯彻下去.

分享到:
评论

相关推荐

    Lua的使用入门之在C++程序中调用lua函数1

    你可以在Lua中定义`text`变量存储要显示的文本,然后在C++中获取这个文本并使用DirectX的字体渲染API(如`ID3DXFont`)进行绘制。例如: ```cpp ID3DXFont* font; // 假设已经创建了ID3DXFont对象 D3DCOLOR color...

    lua程序设计及lua中文手册

    3. 函数与闭包:Lua中的函数是一等公民,可以作为参数传递、返回结果,甚至可以存储在变量中。闭包是Lua中实现函数式编程的重要概念,它可以访问并修改外部环境的变量。 4. 表:表是Lua的核心,可以作为数组、关联...

    Lua编程事例:调用Lua有参函数

    为了在C++中使用Lua,你需要包含Lua的头文件,并链接到Lua库。通常,这可以通过设置项目属性来完成。 接下来,我们将讨论如何加载Lua脚本并执行其中的函数。在C++中,你可以使用`luaL_loadbuffer`或`luaL_loadfile`...

    Lua程序设计和lua-5.1中文手册

    在"Lua程序设计"中,你可以了解到Lua的基本语法和特性,包括变量、数据类型、控制结构(如if语句、循环)、函数定义和调用、表(Lua中的核心数据结构,类似于数组和关联数组的结合)的使用,以及模块和包的管理。...

    protobuf生成lua和go文件

    这将生成`your_file.pb.go`,其中包含了Go代码,可以方便地在Go程序中使用protobuf定义的数据类型。 4. **使用生成的代码**: 在Lua和Go中,你可以通过导入生成的模块,创建、解析和序列化protobuf消息。 除了基础...

    lua调试器:运行时的值查看

    在调试过程中,了解如何正确地设置和使用断点至关重要。断点可以帮助你锁定可能出错的代码区域,避免无目标地遍历整个代码库。同时,学会观察和分析调用堆栈可以让你知道函数是如何被调用的,从而追溯问题的源头。 ...

    Lua的最基本使用 C++与lua的互相调用

    LuaExample中的示例可能包含一个简单的C++程序,该程序加载一个Lua脚本,定义一个C++函数,然后在Lua中调用这个函数。同时,Lua脚本也可能调用C++注册的函数,完成数据交互。 四、注意事项 1. 错误处理:在C++调用...

    C# lua库 支持中文函数名中文变量

    5. 调用Lua函数:如果在Lua中定义了中文命名的函数,你可以像调用普通函数一样调用它们。 ```csharp object result = lua.CallFunction("我的函数", 参数列表); ``` 值得注意的是,由于Unicode编码的特性,中文函数...

    lua-pb解析

    总结来说,“lua-pb解析”是指使用Lua语言和特定的lua-pb库解析protobuf格式的数据,它涉及protobuf的编译、Lua的脚本操作以及数据的序列化和反序列化过程。lua-pb项目提供了一个纯Lua环境下的解决方案,使得在不...

    通过LUA为SEPM分发补丁和病毒定义

    在企业级的网络安全管理中,Symantec Endpoint Protection Manager (SEPM)是一款广泛使用的解决方案,用于集中管理和控制Symantec Endpoint Protection (SEP)客户端的安全设置及策略。为了确保所有客户端都具有最新...

    进行C++与LUA交互编程的LUA库

    在IT行业中,C++与LUA的交互编程是一种常见的技术,尤其在游戏开发领域,由于LUA的轻量级和易读性,常被用作脚本语言来扩展C++程序的功能。本篇将深入探讨如何利用LUA库在C++环境中进行交互编程,以及在给定的压缩包...

    LUA开发需要注意的一些地方

    9. **元表实现OOP**:LUA中推荐使用元表(metatable)实现面向对象编程,而非直接添加成员函数,以优化内存管理和运行效率。 #### 三、分隔和缩进 1. **空行使用**:合理使用空行,保持代码块之间的清晰界限。 2...

    Lua 5.1 中文参考手册.pdf (入门与提高)

    Lua是一种轻量级的脚本语言,它在设计上支持通用的过程式编程,并具备了数据描述的设施。除此之外,Lua还能很好地支持面向对象编程、函数式编程和数据驱动式编程。该语言的一个显著特点是它的扩展性,意味着它可以...

    使用Lua控制应用程序

    - **错误处理和调试**:学习如何在Lua中进行错误处理和调试技巧。 5. **实践项目** - **创建第一个Lua脚本**:写一个简单的Lua脚本,例如计算两个数字的和。 - **实现控制逻辑**:尝试编写一个控制应用程序行为...

    vc 程序调用lua脚本简单示例

    4. **面向过程和基于表的**:Lua支持函数作为一等公民,并使用表(一种通用的数据结构)来实现类和对象的面向对象编程。 在VC++项目中使用Lua,你需要先下载并包含Lua库。将Lua源代码编译为静态或动态链接库,然后...

    lua-utf8.zip

    Windows版:lua-utf8.dll(若是用在openresty中,openresty版本需使用32位版本,使用64位版本时会报错“lua-utf8.dll 不是有效的 Win32 应用程序”) 将lua-utf8库放在openresty安装目录下,使用时用require引入。

    面向Protobuf的Lua数据适配器研究与实现_郑建华.pdf

    【描述】: 本文探讨了在Lua中创建面向Google Protobuf的数据适配器,以实现不同编程语言之间数据交互的问题。随着多语言应用服务器环境的普及,如Java和C++服务器之间的交互,高效的数据格式变得至关重要。Google ...

    在VC 中应用LUA的方法实例.rar

    通过查看这些源码,你可以更深入地了解如何在实际项目中结合C++和LUA,例如如何定义和调用C++函数,如何在LUA中访问C++的数据结构,以及如何处理异常和错误。 总的来说,通过LUA的C语言接口,开发者可以在VC++程序...

    protobuf转为lua代码

    Protocol Buffers是Google推出的一种数据序列化协议,它允许开发者定义数据结构,然后生成能够在各种语言中使用的代码,如C++, Java, Python等,同时也包括Lua。这种方式可以方便地进行数据交换和存储。 描述中提到...

    Lua解码http post数据

    数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 #### 二、POST数据格式 POST数据通常以键值对的形式组织,各个参数之间用`&`符号连接,而键与值之间则用`=`符号分隔。例如: ``` ...

Global site tag (gtag.js) - Google Analytics