手把手教你架构3D引擎高级篇系列八
本篇博客是给读者介绍引擎底层如何与Lua进行结合,方便开发者直接使用脚本编程,给读者介绍的是最基本的C++与Lua的交互,引擎的封装会在下篇博客中具体讲解。
为什么选择Lua,通常开发者会使用Json,XML,txt等等。相比Lua有哪些优点呢?
a、除了Lua库,在没有使用其他库可以使用。
b、可以在文件中使用不同的公式,例如:some_variable = math.sqrt(2)* 2
c、它非常轻巧,速度快
d、它是在MIT许可下换句话说代码是开源的,因此我们可以以任何想要的方式使用它
e、它是用C语言编写的,几乎可以编译任何C编译器
f、可以使用表对数据进行分类,易于编辑和阅读
既然这么多优点,我们就采用Lua作为脚本使用,Lua与引擎的结合还是非常重要的。我们下面先从基础的讲起,慢慢给读者深入。先看看Lua语言编写的脚本:
player = { pos = { X = 20, Y = 30, }, filename = "res/images/player.png", HP = 20,-- you can also have comments}
我们要使用Lua脚本中的内容,需要执行下面的语句:
LuaScript script("player.lua");
std::string filename = script.get("player.filename");
int posX = script.get("player.pos.X");
如何使用Lua与C++绑定,读者可以参考网址:
http://lua-users.org/wiki/BindingCodeToLua
接下来我们分析一下上面的Lua脚本,Player表是全局的,因此需要通过lua_getglobal方法获取它, 现在Player表将位于堆栈顶部,使用lua_getfield函数获取pos表,然后使用变量x,如下图所示:
对应的代码下载地址如下所示:
https://github.com/EliasD/unnamed_lua_binder
使用上面的代码时,不要忘记把Lua的库加进工程里面。我们可以使用Lua做很多事情,如下所示:
-- somefile.luasome_array = { 1, 2, 3, 4, 5, 6}
std::vector v = script.getIntVector("some_array")
如何清理Lua堆栈?我们可以使用lua_gettop函数返回数组中元素的数量,从而弄清楚我们必须弹出多少项。
void clean(){ int n = lua_gettop(L); lua_pop(L, n);}
下面实现的接口:
std::vector LuaScript::getIntVector(const std::string& name){ std::vector<int> v; lua_getglobal(L, name.c_str()); if(lua_isnil(L, -1)){ return std::vector(); } lua_pushnil(L); while(lua_next(L, -2)){ v.push_back((int)lua_tonumber(L, -1)); lua_pop(L, 1); } clean(); return v;}
它们是如何工作的?首先,我们获取全局表并检查是否找到它。 如果它是nil(尚未定义,或者…,nil),我们只返回一个空向量。
然后我们将nil值推到Lua堆栈的顶部, 这是因为lua_next的作用,它从堆栈中弹出键值,然后将键值对推送到堆栈。 如果数组中没有更多元素,我们清理堆栈并返回结果向量。
还可以创建一个函数来获取字符串或浮点数组, 这需要更改的是矢量类型和一些强制转换(请不要忘记将lua_tonumber更改为lua_tostring)
使用Lua脚本编程,因为它是脚本,对性能要求比较高的代码建议不要使用Lua脚本,直接使用C或者C++编程。
通过案例的方式给读者介绍,比如下面代码:
function sum(x, y)
return x + y
end
对应的C++代码如下所示:
int sum(int x, int y){ lua_State* L = luaL_newstate(); if (luaL_loadfile(L, "sum.lua") || lua_pcall(L, 0, 0, 0)){ std::cout<<"Error: failed to load sum.lua"<<std::endl; return 0; } lua_getglobal(L, "sum"); lua_pushnumber(L, x); lua_pushnumber(L, y); std::cout<<"loaded"<<std::endl; lua_pcall(L, 2, 1, 0); int result = (int)lua_tonumber(L, -1); lua_pop(L, 1); return result;}
接下来给读者分析一下,首先,我们创建新的Lua状态并加载文件。注意:这只是一个示例,我们应该将状态与加载的文件保持在某个位置,以防止每次使用函数时重新加载,因为这样做效率不高。
然后我们在Lua堆栈的顶部得到名为sum的全局函数。 使用lua_pushnumber函数然后我们推送2个变量,现在我们的堆栈看起来像这样:
第一个是lua_State,第二个是你要调用的函数中的参数个数, 第三是你希望返回的功能。 第四是错误代码(应该在Lua参考手册中阅读)
在我们调用一个函数之后,它会从它的参数中弹出堆栈。 堆栈中剩下的唯一东西是值sum函数返回,所以现在我们可以用lua_tonumber获取它的值并弹出它。
说了这么多,现在给读者介绍如何使用它们?
假设我们在游戏中实现NPC, 当玩家靠近NPC时,NPC会做不同的事情。
我们经常会安排玩家与NPC的一些对话,比如说“让我帮助你”,而另一个NPC只是说“你好”并且什么都不做。我们的交互代码可能如下所示:
if(isPressed(ACTIVATION_BUTTON))
{ Character* character = find_nearby_character(player); if(character) { character->interact(player); }}
如何实现这个交互方法?我们很容易想到使用枚举,如下所示:
enum CharacterType { Player, Talker, Healer };
CharacterType type;
函数如下所示:
void Character::interact(Character* secondCharacter){ switch(type) { case Character::Player: break; case Character::Talker: say("Hello"); break; case Character::Healer: say("Let me help you"); heal(secondCharacter); break; }}
通过代码我们可以看出,这么设计非常不利于扩展,我们还可以想到使用另一种解决方案是使交互虚拟功能和使用继承, 但是我们会去实现每种类型的NPC,这种方案也是不可取的。
或者有读者可以使用更好的策略模式,用C ++编码的,必须重新编译代码,也是不可取的。
最终的解决方案就想到了Lua的编写,在使用Lua之前,我们首先要创建一个Character类,如下所示:
class Character
{
public: Character(const char* name, int hp);
void say(const char* text); void heal(Character* character); const char* getName() { return name; } int getHealth() { return health; } void setHealth(int hp) { health = hp; } // will be implemented later void interact(Character* character);private: const char* name; int health;}; Character::Character(const char* name, int hp) { this->name = name; health = hp;}void Character::say(const char* text) { std::cout << name << ":" << text << std::endl;} void Character::heal(Character* character) { character->setHealth(100);}
我们遇到了一个问题, 如果Lua现在没有关于这种类型,我们如何将Character *作为参数传递? 我们如何在Lua中注册非静态成员函数并调用它们?Lua包装器可以参考网址:http://lua-users.org/wiki/BindingCodeToLua
决定使用LuaWrapper,它没有额外的依赖关系而且不需要构建, 只需将一个头文件复制到项目中即可开始使用。
使用LuaWrapper,函数的编写如下所示:
int Character_getName(lua_State* L){ Character* character = luaW_check<Character>(L, 1); lua_pushstring(L, character->getName()); return 1;} int Character_getHealth(lua_State* L){ Character* character = luaW_check<Character>(L, 1); lua_pushnumber(L, character->getHealth()); return 1;} int Character_setHealth(lua_State* L){ Character* character = luaW_check<Character>(L, 1); int hp = luaL_checknumber(L, 2); character->setHealth(hp); return 0;}
从现在开始,我们将使用checknumber而不是tonumber。 它基本相同,但如果出现问题,它会抛出错误信息。
LuaWrapper提供了相同的方法,可以用它来获取C ++对象,还可以创建对象并调用它们的方法,如下所示:
player = Character.new(“Hero”, 100)player:getHealth()
使用luaW_check(L,1)可以获得玩家对象在C ++中使用它,余下的代码如下所示:
static luaL_Reg Character_table[] = { { NULL, NULL }};
static luaL_Reg Character_metatable[] = { { "getName", Character_getName }, { "getHealth", Character_getHealth }, { "setHealth", Character_setHealth }, { NULL, NULL }};static int luaopen_Character(lua_State* L) { luaW_register<Character>(L, "Character", Character_table, Character_metatable, Character_new); return 1;}
Character_table用于静态函数, 我们在Character类中没有它们,所以这个结构是空的。
Character_metatable用于设置将在Lua中使用的函数名称。
luaopen_Character注册一个类, 第一个参数是lua_State *,第二个参数是如何在Lua脚本中命名我们的类。 其他参数是静态表,元表和构造函数。
我们的测试脚本如下所示:
player = Character.new("Hero", 100)
player:setHealth(80)
hp = player:getHealth()
name = player:getName()
print("Character name: "..name..". HP = "..hp)
最后代码下载地址如下所示:
链接:https://pan.baidu.com/s/1Rn3WwXYVLA-t79s0MFFarQ
提取码:mtgr
参考网址:https://eliasdaler.wordpress.com/2013/10/11/lua_cpp_binder/
手把手教你架构3D引擎高级篇系列八相关推荐
- 手把手教你架构3D引擎高级篇概述
前几年写过一本书<手把手教你架构3D游戏引擎>电子工业出版社,主要内容讲的是固定流水线编程,目的是让读者理解第一代引擎是如何实现的,从本篇博客开始,给读者介绍关于使用可编程流水线自己搭建3 ...
- 手把手教你架构3d游戏引擎pdf_一个在游戏行业摸爬滚打了十几年的人,为何我对这本书情有独钟...
Big News!<游戏开发:世嘉新人培训教材>今日开始预售啦!经过漫长的等待,这次终于可以买到了.现在下单,你将在图书出印厂的第一时间收到书哦- 这本书由世嘉一线开发者执笔,并被选为世嘉 ...
- 手把手教你架构3d游戏引擎pdf_白鹭引擎团队即将发布 Egret Pro,并公布后续路线图...
各位开发者好. 春节前,白鹭引擎团队发布了 Egret3D 1.4,引入了大量新特性.上周,白鹭引擎团队发布了 5.2.14 版本,修复了多个白鹭引擎2D渲染器相关的 BUG,接下来我们会在下周继续发 ...
- 手把手教你架构3d游戏引擎pdf_游戏开发中的算法
游戏技术这条路,可深可浅.你可以满足于完成GamePlay玩法层面的东西,你也可以满足于架构和框架设计层面的东西,你也可以醉心于了解某一游戏引擎带来的掌控感.但是,我们不该止步于此,止步与目前所见或所 ...
- 一文搞定!手把手教你文字识别(识别篇:LSTM+CTC, CRNN, chineseocr方法)
个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈 文字识别是AI的一个重要应用场景,文字识别过程一般由图像输入.预处理.文本检测.文本识别.结果输出等环节组成. 其中,文本检测. ...
- windows脚本编制引擎_手把手教你写脚本引擎(一)
手把手教你写脚本引擎(一)--挑选语言的特性 陈梓瀚 华南理工大学软件本科05级 脚本引擎的作用在于增强程序的可配置性.从游戏到管理系统都需要脚本,甚至连工业级产品的Office.3DS Max以及A ...
- 手把手教你编写游戏模拟器 - Chip8篇(1)
转自 http://www.cnblogs.com/YiranXie/p/3439934.html 手把手教你编写游戏模拟器 - Chip8篇(1) 手把手教你编写游戏模拟器 - Chip8篇 翻译整 ...
- 谷粒商城--认证中心--高级篇笔记八
谷粒商城–认证中心–高级篇笔记八 1. 环境搭建 1.1 新建模块gulimall-auth-server 1.2 pom文件 上面没选好直接复制下面的pom文件,记得排除gulimall-commo ...
- 微信接口开发之高级篇系列【网页授权获取用户基本信息】
PHP微信接口开发之高级篇之网页授权获取用户基本信息 二.WEB开发工具 转载于:https://www.cnblogs.com/tinywan/p/5860981.html
- 手把手教你爬虫requests实战演练——python篇
文章目录 一.前言 二.实战 1)获取百度网页并打印 2)获取帅哥图片并下载到本地 4) 获取美女视频并下载到本地 5)搜狗关键词搜索爬取 6)爬取百度翻译 7)爬取豆瓣电影榜单 8)JK妹子爬取 总 ...
最新文章
- 河套酒业集团远程应用K/3系统案例解析
- 内存文件系统——sysfs
- Pixysoft.Framework.MemoryCache 开发实录
- python解析二维码_Python二维码生成识别实例详解
- 用《内网穿山甲》共享内网中的远程桌面服务
- The Furthest Distance In The World
- 【概率论】复习资料(手写复习)
- coreldraw错误代码14001_应用程序配置不正确,应用程序未能启动 提示14001错误代码解决方法...
- 创建Vue实例对象基础语法模板
- Lookahead、LazyOptimizer、MaskedAdamOptimizer、AdaBound
- bi工具有哪些,该怎么选择呢?
- 计算机专业简历教育背景怎么写,简历中教育背景怎么写?填写教育背景注意事项...
- 什么是七日年化收益率和万分收益?
- 读南师《金刚经说什么》有感
- 差分走线_HFSS学习笔记(2)
- 不讲一点数学知识,步步图解条理清晰,手把手带你理解DBSCAN算法
- 网络3共享网络2计算机打印机,如何添加本地打印机与共享网络上的打印机
- android加密墙,Android代码混淆加密配置(Proguard文件解析)
- 卷积神经网络分类实战:疫情期间戴口罩识别
- 西班牙中国鞋事件的思考【ZZ】