`
chinamming
  • 浏览: 140130 次
  • 性别: 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-utf8.zip

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

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

    这次说明的了,在VC++ 6.0中怎么样调用一个lua脚本中的有参函数。

    lua程序设计中文版

    深入地介绍了Lua中唯一的数据结构table,还讨论了数据结构、持久化、包和面向对象编程。展示了Lua的标准库,对那些想将Lua作为一门独立语言来使用的开发者特别有用,每一章介绍一个库,包括数学库、table库、字符串...

    LUA程序设计教程LUA程序设计教程LUA程序设计教程

    LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程LUA程序设计教程

    lua解析器,方便lua开发

    lua解析器,方便lua开发

    lua-5.3.4.tar.gz Mylua-5.3.4.tar.gz lua生成动态库 lua包管理 pkgconfig PKG_CONFIG_PATH

    在源码包中,此宏定义在GCC中,打开lua-5.3.4/src/Makefile,可以看到:CFLAGS= -O2 -Wall -Wextra -DLUA_COMPAT_5_2 $(SYSCFLAGS) $(MYCFLAGS)。 默认编译后,再回头编译vlc开源库,发现:lua/demux.c:55:13: 错误...

    十分钟Lua脚本系统入门

    Lua是一种面向过程的简单轻量级的脚本语言,我编写了一些简单代码试图向C/C++程序员描述基本的Lua脚本系统使用,涉及: Lua库的使用 Lua基本语法 Lua脚本加载执行 通信:Lua脚本中调用Native函数 通信:Native代码中...

    lua程序设计及lua中文手册

    lua程序设计,lua中文手册,lua相关资料

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

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

    Lua程序设计_书籍

    深入地介绍了Lua中唯一的数据结构table,还讨论了数据结构、持久化、包和面向对象编程。展示了Lua的标准库,对那些想将Lua作为一门独立语言来使用的开发者特别有用,每一章介绍一个库,包括数学库、table库、字符串...

    微信62数据源码 LUA

    lua微信62数据源码,可以读取写入62数据,用62数据登录。

    LUA程序设计一书的源码

    深入地介绍了Lua中唯一的数据结构 table,还讨论了数据结构、持久化、包和面向对象编程。展示了Lua的标准库,对那些想将Lua作为一门独立语言来使用的开发者特别有用,每一章介绍一个库,包括数学库、table库、字符串...

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

    基本的调用lua变量与函数,实现文本的获取与显示策略,若要改变显示方式,只要修改move()函数即可.

    Lua经典编程书籍: 编程指南.doc 、Lua程序设计_第二版_中文.dpf 、Programming in Lua, 2Nd Edition.pdf

    Lua经典编程书籍, 编程指南.doc ,Lua程序设计_第二版_中文.dpf ,Programming in Lua, 2Nd Edition.pdf。 lua编程书籍,高清版,带目录,非常好的参考书

    易语言LUA支持库1.0#0版(第三方)

    下面对易语言中使用LUA做一个简单的介绍。LUAC函数操作(命令分类)。这是一组全局函数,当您在您的应用程序中实现一个能被LUA调用的函数时候,需要用到这些函数。主要是数据交换相关。包含 LUA取参数数目、LUA取...

    Lua解码http post数据

    Lua脚本编写的解码http post数据

    LUA 程序设计

    LUA 程序设计 LUA 程序设计 LUA 程序设计 LUA 程序设计

    lua参考使用手册

    Lua是一种为支持有数据描述机制的一般过程式编程语言而设计的扩展编程语言。它同样可以对面向对象语言、函数式程序设计(Functional Programming,如Lisp)以及数据驱动编程(data-driven programming)提供很好的...

    emoji.lua:Lua的基本表情符号支持模块

    表情符号 :speech_balloon:Lua的基本表情符号支持模块 :crescent_moon:例子 local emoji = require ( " emoji " )print (emoji. emojify ( " I :heart: :tea:! " ))-- &gt; "I :red_heart: :teacup_without_handle:!...

Global site tag (gtag.js) - Google Analytics