作者:Simon Yeung翻译:游戏邦

系列目录:
iPhone游戏引擎编写之内存管理和Maya工具(1)
iPhone游戏引擎编写之脚本处理和流动传输(2)
iPhone游戏引擎编写之音频和性能(3)
iPhone游戏引擎编写之项目事后分析(4)


脚本处理

  在引擎中添加脚本支持能够带来诸多好处,如无需耗费大量时间重新编译引擎源代码就可以进行游戏玩法代码的编写,还能够在游戏代码和引擎代码间划定清晰的界线。我选择运用Lua语言(注:版本编号是5.1.4),因为它易于整合且所占空间较小。我并不擅于使用Lua语言,所以我想在下文中阐述如何将Lua和C/C++起来。

从Lua处调用C函数

  首先,你需要通过lua_open()创建lua_State*(注:如果你需要将内存分配器与Lua衔接起来的话,可以使用lua_newState()),所有Lua运算都在其中完成。

lua_State* luaState= lua_open();

  Lua和C可以通过虚拟栈交换数据。Lua和C都可以向虚拟栈推送或获取数据。比如,我们已经注册了一个供Lua使用且带有函数标记的C函数:

function drawText(str, screenPosX, screenPosY);

  随后在Lua端,当执行下列Lua脚本时:

textWidth= drawText(“Ready Go~”,240, 160);

  3个值将被推送至Lua Stack:


lua Stack 1 from altdevblogaday.com

  通过进行从存储栈底部开始(从1开始)的绝对指数运用或增至存储栈顶部(从-1开始)的相对指数运算,我们便可以得到C中的存储栈数值。

  随后,我们在C函数内检索存储栈中的数值,Lua使用下列代码调用该C函数:

int drawText(lua_State* luaState)
{
float screenPosY = (float) lua_tonumber(luaState, -1);// get the value 160
float screenPosX = (float) lua_tonumber(luaState, -2); // get the value 240
const char* str = lua_tostring(luaState, -3); // get the value “Ready Go~”
printf(“Text ‘%s’ draw at (%f, %f)\n”, str, screenPosX, screenPosY);
int textWidth= strlen(str);
lua_pushnumber(luaState, textWidth); // return a value to Lua
return 1; // number of values return to Lua
}

当C函数停止时,存储栈将如下所示:


lua Stack 2 from altdevblogaday.com

Lua获得来自C函数的返回数值后,C函数的参数和返回数值将跳出存储栈。

但是,在Lua执行drawText()前,记得运用下列代码将其注册至lua_State*:

lua_register(luaState, “drawText”, drawText);

从C处调用Lua函数

  我们还可以从C处调用Lua函数。比如,我们已经在Lua脚本中编写出函数,目的是初始化引擎配置:

function initEngineConfig(date)
print(‘initialize engine on ‘ .. date);
end

  由于Lua属于无类型语言,因此它将函数也视为变量。所以我们需通过下列C代码将Lua变量“initEngineConfig”推送至存储栈中:

lua_getglobal(luaState, “initEngineConfig“); // get the function to the stack
lua_pushstring(luaState, “18th Aug, 2011″); // push the argument of the function
lua_call(luaState, 1, 0); // execute the function

  如果发生错误,你还可以使用lua_pcall()来代替lua_call()以获得更多的排除故障信息。

  上述C代码等同于在Lua中调用函数:

initEngineConfig(“18th Aug, 2011″);

  我们还可以使用类似的技术来执行C中的对象方法。比如,我们可以在Lua中调用对象方法:

gameObjectA:update(timeSlice);

  我们也能够在C中进行此操作。在Lua中,冒号句法只是编写声明的短结构:

gameObjectA.update(gameObjectA, timeSlice);

  所以,我们需将“gameObjectA”的“更新”函数推送至带有2个自变量的Lua存储栈中:

lua_getglobal(luaState, “gameObjectA“); // for getting the ‘update’ function of ‘gameObjectA’
lua_getfield(luaState, -1, “update“); // get the ‘update’ function of ‘gameObjectA’
lua_insert(luaState, -2); // swap the order of “gameObjectA” and “update”
// so that “gameObjectA” becomes an argument
lua_pushnumber(luaState, 1.0f/30.0f); // push the timeSlice argument on the stack.
lua_call(luaState, 2, 0); // execute the functions.

  从根本上说,这就是Lua与C的互动方式,但你还需要知道如何以用户数据或轻用户数据的形式呈现C结构。你还需了解LUA_REGISTRY_INDEX,以在C中创建变量,无需担心变量名称的冲突问题。在把握这些内容后,你或许可以尝试通过库来生成。但是,我希望这些方法能够对那些想要自行Lua和C的人有所帮助。

流动传输

介绍

  我的游戏是款开放世界游戏。玩家可以自由探索游戏世界。游戏不可能在开始时就加载所有游戏物体,所以我的引擎应当能够在玩家玩游戏时流动呈现游戏物体。

加载

  要顺畅呈现游戏物体,我必须将整个游戏世界分割为多个正方形格子:


partition World from altdevblogaday.com

  我将根据玩家的位置加载游戏世界的格子。我将每个空间方格像下图那样分为9个区域:


region from altdevblogaday.com

  每个格子中都包含有3种类型的区域(注:分别标注为A、B或C)。当玩家位于A区域时,游戏只会加载玩家所处的方格图层。当玩家位于B区域时,游戏会加载临近的1个方格图层:


load 2 tiles from altdevblogaday.com

  当玩家位于C区域时,游戏就会加载临近的3个方格图层:


load 4 tiles from altdevblogaday.com

  所以,内存中的风格数量极限值为4。

卸载

  要把握内存方格数量不超过最大值,需卸载看不见的方格图层。针对卸载,我将每个格子分为4个区域:


region Unload from altdevblogaday.com

  假设当玩家位于0区域时,下图中的X、Y和Z风格都将被卸载:


unload Tile from altdevblogaday.com

  根据上述规则,我只要确保X、Y和Z方格在需要加载新方格图层前被卸载。如果出现意料之外的情况,我会暂停游戏直到它们被卸载。

内存和线程

  现在我们已经知道,游戏最多只可能存在4个方格。除主线程deep物理和脚本所使用的共用内存分配器外,我还有4个线性分配器能够进行空间方格的输送,所有方格都在此限制范围内。内存分配样式与线程模型在游戏中的运作情况有关。游戏中有两个线程:主线程和流线程。主线程负责游戏逻辑、物理和播放的更新,流线程用来加载资源和减压质感等内容。当玩家更新主线程位置时,就标志着流线程要加载所需的方格图层。经过系列图像之后,流线程将以完成加载的信息模式反馈给主线程。两个线程间的传输将进行双倍缓冲以实现加锁时间最小化,同时确保线性分配器只会被用在流线程中,进而避免使用任何互斥量。但是,当图像对象和Lua对象等需在主线程中创建时,事情就会变得复杂很多。比如,流线程在完成纹理的解压缩操作后,应当通知主线程创建openGL纹理。

优势

  在流动传输中使用线性分配器可以避免出现内存碎片。将游戏世界分割成方格图层能够将内存管理变得更简单,因为每个方格所使用的内存量大致相同。

劣势

  要卸载空间方格,通过重置线性分配器就可以轻松释放在CPU端生成的资源。但是,事情并不像我最初想象的那么简单。比如,当我卸载方格中的实体对象时,在重置分配器前我需要先将它们完全从碰撞世界中移除。而且,对于图像对象,在重置分配器前我需要释放该方格图层中的所有openGL对象,否则,GPU端就会发生泄漏现象。而且我还需要释放方格中的脚本对象,这样那些脚本就可以集中于Lua。因此,这几乎同挨个“删除”方格中的所有游戏对象相同,我们无法简单地通过重置线性分配器来释放所有资源。除此之外,运用另一自定义分配器在物理模式中创建对象(而非通过当前衔接的btAlignedAllocSetCustom()模式分配器)绝非易事。因为并非出于将对象分配给另一个内存分配器而设计。我需要修改源代码,方能使之生效。

结论

  在制作了流系统后,我对应当精心计划的线程间通信及需要在特定线程上创建的对象有了更清晰的认识。而且,我觉得运用针对流对象的线性分配器来区分内存区域并非睿智选择,因为当这些对象需要被移出游戏世界时,它们只能挨个进行删除。修改物理,使之能与这种内存模型相适应需要耗费大量的工作,这也会导致物理库的维护和更新变得更为困难。

来自:游戏邦锐亚教育

锐亚教育,游戏开发论坛|游戏制作人|游戏策划|游戏开发|独立游戏|游戏产业|游戏研发|游戏运营| unity|unity3d|unity3d官网|unity3d 教程|金融帝国3|8k8k8k|mcafee8.5i|游戏蛮牛|蛮牛 unity|蛮牛