【2D游戏引擎】WIP反思

WIP(Working In Progress)是我初学游戏引擎开发时候开发的一个2D游戏引擎,当时计划为它实现类似Unity一样的编辑器,具有和Unity相似的工作流,但是由于水平不够,走了很多弯路,闭门造车,做了很多错误的设计,导致很多地方反人类和难以维护,加之时间有限,所以已经停止了对它的继续开发。由于停止了开发,又不想把所有资料都全部搞丢,所以把在开发中学到的东西,和一些自己的思考都记录一下。以便后可以参考。

他最后可以实现这些功能,不过实现起来远比成熟的引擎蛋疼得多:

  • 脚本游戏编程
  • 基本的2D游戏(我曾经用这个东西做过flappy bird和一个简单的RPG游戏(由于没有写编辑器,制作起来非常耗时特别是帧动画和布置场景的时候))
  • 一些2Ddemo(之前上课的几个作业一直用这个做)
  • 简单的OpenGL shader
  • 简单的3D呈现

 

当时就选择了脚本流,因为之前接触过一段时间的Lua,所以脚本语言也直接选择了Lua。脚本的嵌入采用了很简单的思想,没有去导出c++类,而是简单实现了lua中的类,再把所有的c++对象都映射为一个lua对象中的指针,这样做的效率也许不高,但是后来也没有遇到什么大问题,只是有的时候会不方便,而比较棘手的就是对象销毁问题,我在哪些对象需要自动销毁,那些对象不需要上,想了很久,最终也没有理出一个完全彻底的方案。另外,我的脚本系统仅仅是执行游戏逻辑,我在主循环中使用一个函数每帧都调用Lua脚本的入口函数,从而驱动整个脚本系统。

至于基于组件的游戏对象,和数据驱动基本上全靠Lua和XML。场景保存到XML文件中的,每个场景中的对象就是一个XML结点,而读取场景则选择了在lua中实现,一切的一切都全仰仗于这个函数:

function System.genertate_component_by_name(component_name)
    s = "local _ = require \""..component_name.."\";return _:new()"
    return loadstring(s)
end

也就是说全靠lua的loadstring函数。我的每一个组件都是一个Lua脚本,这些脚本继承于一个基类,当然也都是Lua实现的,这些,我都是借鉴自Unity的脚本系统中我看得懂的部分,所以部分看起来和Unity的很类似。虽然简单,但是应该说,我从这次实现尝试中学到的东西还是不少,至少解了了一些很有用的概念。对Lua的了解也深入了一步。

渲染是我这次尝试中最失败的部分,因为首先我并不熟悉OpenGL,而且在此之前我基本只使用Opengl的老渲染管线渲染图像。所以最开始的时候,我的渲染系统中导出都是glbegin,glend,后来随着我的深入学习,我将那些地方全部改成了gldrawelements或者gldrawarray来渲染,但是思想依然是固定渲染管线的思想。在我的渲染系统中,每次渲染一个四边形,都会绑定一个纹理上去,然后把这个东西的顶点数据和纹理坐标以及纹理id打包存到一个容器v中,直到一帧结束,将v中的所有东西全部取出来,一个一个的调用drawelements,然而这对渲染的效率并没有什么提升,多次drawcall依然是多次drawcall,固定管线依然是固定管线,对于所谓的多线程也完全没什么用处。后来我准备在这个系统上开刀,试图把所有的drawcall合并,然而这将会直接导致整个系统的重写。后来逐渐深入学习了Opengl,想使用可编程管线来渲染3D网络,强制加入了Shader功能,然而这样的行为并不好,而且很多时候如果2D3D一起渲染会产生问题,也就是说,这个渲染系统仅仅只支持2D渲染,如果想渲染3D图形,对不起只能用OpenGL,连封装都没有……而当时使用在渲染2D对象的时候,幸好用了mesh的概念,而不是直接拉四个点去……

场景管理其实那时候是没有那个概念的,2D游戏计算量没到,当然不会去考虑场景管理优化之类的,Scene类唯一有的功能就是分了3个层,一个层用来装object,一个用来装UI,剩下的用来装volumes。因为当时考虑到,object的更新和UI的更新并不是一样频繁的,至于volumes,本来是想用来存放一些阻挡体积,碰撞体积,就和UE里面的volume差不多的,但是由于一些硬伤,这些东西知道最后也没有能够加进去。

关于物理,我并不太懂物理,wip当前仅支持基本的刚体物理,物理部分主要是我的一个同学帮助我完成的,我仅仅介入他写好的功能。物理上,我遇到的问题就是更新。物理引擎为我提供的东西主要就是一个body,我把这个body作为我的object的一个组件。于是在更新的时候,我就要不断地将当前的位置写入body更新后在写出到object,这样做我一直觉得特别低效。不过还好这个东西并不会涉及到lua交互而是批量更新。

动画方面,我实现了帧动画,以及尝试嵌入了spine这个骨骼动画插件。帧动画就是很简单的将一张纹理上的各个区域一直做变换,不停的播放。帧动画我实现了一个叫做clip的文件,这个文件记录一个动画clip的每一帧的信息,包括uv和帧数等等。然而在实际使用的时候,没有编辑器,手动修改大批这样的数据是及其痛苦的。关于骨骼动画,我使用了spine来嵌入,但是这个嵌入由于渲染部分的设计问题,效果并不是很好,由于我的每一个对象渲染出来都是一个矩形区域,所以我只能先将骨骼动画结果渲染到一个fbo上,然后由这个fbo渲染到矩形上,而问题就在于这个矩形区域又和碰撞矩形挂钩,而一个骨骼动画对象在运动时往往需要一张最大的矩形来容纳他的所有运动范围,所以这个矩形应该尽量能够框住整个运动对象,但是碰撞却不是,骨骼动画的碰撞需要拆分到各个部件来计算!所以无论我怎么修改骨骼动画的嵌入方式,也无法改善碰撞和渲染的耦合,唯一的方案就是重写。而这也是我放弃了wip的主要原因之一。后来我心血来潮在其中自己实现了一个骨骼动画系统,但是这个系统的设计是不完备的,也就是它只能播放骨骼动画,而没有考虑动画数据来源。

声音部分其实我不是很懂,我使用了OpenAL来驱动声音,而声音文件解析则采用了libogg。声音我基本上参考来参考去实现了流播放和整体加载播放的基本能力。剩下的研究就不多了。

其实在整个实现中遇到的最大问题还是文本渲染,首先我完全不懂文字的造型什么的,所以我使用了freetype,借助freetype造型好文字后,我则将其加载到纹理进行渲染。这个系统应该是最不完善的一个系统把。

UI系统本来最开始是没有的,但是我有段时间跟着老师做东西,做了个基于of的简uii系统。想了一下直接就把能用的控件移植到引擎中来了。UI部分还是基于回调的UI,思想比较老旧,但是基本能用。我在demo里面就展示了几个可用的UI控件。

其他的一些比较基础的库,算法数据机构上我一开始还在考虑要不要使用stl还是使用boost,但是后来发现一使用起stl就停不下来。数学库,则是我之前参考UE的数学库自己半抄半写的,因为总是读书少瞎想多,所以当年还真的特别担心完全自己瞎写数学库会拉低效率,然后想到UE的源代码,于是乎想到,UE用的方法应该不会低效吧,抄一个算了,也能学习学习,于是有段时间我就真的专心的去抄数学库了,不过恰好那段时间在学习3D数学,还是获益不少。然后是图片的解析我没有自己去折腾,而是采用了freeimage,这个库唯一的缺点就是大,其他的是还很方便全面。最后关于内存管理自己实现了一个简单的内存分配器,但是比较搞笑的是,这个管理器,我在我自己写的这些代码中并没有怎么使用,反而是我同学写的物理引擎用的多……但是后来出事了,在macos上这个内存管理器不能使用。

后来,我也曾今思考过将这个引擎移植到安卓或者ios上,有相当一段时间我都在做尝试,和一个懂苹果开发的同学搞了几天,解决了无数傻逼问题,最后在macos上跑了出来,但是却终究没有在osx上跑出来,安卓上,我也自己尝试了一下,感觉是还不如基于安卓重写来的快……

总之一句话,读书少而瞎想多

当初在做这个东西的时候,由于太年轻想的是将这个东西做成一个2D交互应用开发套件,最好的效果是能做做成一 个和Unity2D一样的东西。而当时看过的唯一一本书就是《Game Engine Architecture》,知道一个游戏引擎大概有哪些东西,但是这些东西具体怎么实现,则完全是一篇空白。尽管在后来的开发中,参考了cocos2d 和UE4,但是对原代码的解读都只是浅尝则止,没有深入的理解,往往是拿到一个概念,自己望文生义就乱实现。比如当时不知从什么地方了解到了多线程渲染这 个概念,于是想当然的设计了wip的多线程渲染系统,但是就是这个所谓的多线程系统导致现在整个渲染系统都像一只面目全非的怪兽。

 WIP的Github

OSC镜像

posted @ 2015-05-18 00:44  wubugui  阅读(1382)  评论(3编辑  收藏  举报