Author Archives: Benny Chen

Game Loop的几种实现方式

写这篇博客的目的是为了对game loop(游戏主循环)做一个全面的总结和介绍,包括它的定义,与之相关的专业术语(terminology),以及最重要的,对它的几种实现方式,从代码层次做一些介绍以及优缺点分析。 1.什么是Game Loop 对任何一个游戏开发者来说,game loop都应该不是一个陌生的概念。任何一款游戏都会有一个自己的game loop,它是整个游戏的核心,是游戏的心脏。具体什么是game loop,这是来自于Game Engine Architecture[2]上对于game loop的定义:game loop是对于一个游戏(或者一个游戏引擎)的所有子系统(subsystem)的周期性的更新。一个游戏会包含各种不同的子系统(比如渲染,物理,动画,AI等等),这些不同的子系统负责处理不同的游戏任务,game loop的实现方式就决定了这些子系统的任务执行的组织方式。而这些就决定了整个游戏的基础架构,同时也将决定game在不同的机器上将如何运行,在下面介绍game loop的不同实现方式时会对此有详细介绍。 game loop中所处理的逻辑和操作包罗万象,但一般可抽象为以下这几种模块: 处理输入:这些输入包括,来自于交互设备(键盘,鼠标,游戏控制器等等)的输入,来自于网络的输入; 系统更新(update):(根据输入或者自发的)对各个子系统进行更新,以决定当前的游戏状态; 渲染(render):对整个游戏场景进行渲染。 所以一个最最简单的game loop可以抽象为如下的这些伪代码: 2.相关术语 常常和渲染紧密相连的一个参数是FPS(frame per second),它指的是每秒显示设备在屏幕上进行绘制的频率。对于游戏来说,一般50-60的FPS是最优,最低也不要低于16帧[1]。游戏中还有一个容易被忽略的重要参数可以被称为游戏速度(game speed)[3],它指的是游戏状态每秒被更新的次数,容易与FPS混淆。一个通俗的理解和区别它们的方式可以是,FPS是render函数被调用的频率,而game speed则是update函数被调用的频率。 游戏是对实时性(real-time)要求很高的系统,所以在game loop中,time是尤其重要的一个因素。这里需要说的是,游戏中会包含好几种不同的时间,这些时间有着各自的时间线(timeline),并且在游戏中发挥着各自不同的重要作用[2]。 系统的真实时间(real time)。真实时间一般在操作系统被定义为从过去某个固定时间点(比如常见的一个时间点被称之为Epoch, 是1970年1月1日的0:00:00, UTC时间)到当前点总共逝去的时间。 游戏时间(game time)。一般情况下,游戏时间等于系统时间。但在某些特殊情况下,游戏时间可以发挥不同的作用:比如在游戏暂停的时候,需要停止更新游戏时间;在游戏需要以慢速运行时,可以用比真实时间低的频率来更新游戏时间,这在我们需要进行一些游戏调试的时候,尤其有用。下面会介绍到,一个好的game loop结构,可以有助于游戏调试,这对开发人员来说是非常重要的。 局部时间线(local timeline)。不管是一个动画,还是一个音频或者视频片段,都会有一个局部时间线。通过将此局部时间线以不同的方式映射到全局的时间线,可以灵活的控制片段以特定的方式进行播放。[2] 帧时间(frame time)。两次帧循环之间所间隔的时间,具体可看本文章的3.2.1节。 3.Game Loop的实现方式 OK,介绍了这么多跟game loop相关的概念,接下来进入正题,game loop的各种实现方式大盘点。 有一点需要强调的是,game loop在单线程的游戏系统中是一种情况,而在多线程的系统中又是另一个不同的故事。多线程对于系统会引入异常的复杂性,关于这个,可以看我之前的一篇关于游戏引擎多线程的文章:http://www.bennychen.cn/2011/01/关于游戏引擎多线程的一些整理和思考/。相比来讲,单线程要简单很多,这篇文章里以下的一些game loop的实现方式也主要是针对在单线程环境中的。 在单线程环境中,game loop按照实现方式可以分为以下这几种类型: 基于帧的(frame-based)game loop 基于时间的(time-based)game loop 可变频率(variable-step)game [...]

3ds-max-exporter-plugin

把两年前的那个3ds max导出插件项目上传到了google code,代码基本上已经能嗅到铁锈的味道了。现在再上传,一来作备份,二来open source是一件光荣的事情,或许会有人需要它。 地址: http://code.google.com/p/3ds-max-exporter-plugin/ 介绍: This project was developed with 3DS Max 9 SDK in C++, Visual Studio 2005. This exporter plug-in is aimed to export 3D mesh data from 3DS Max into a predefined file format. These mesh data includes vertices, texture, normals, skeletal animation, etc. The project was kinda old and [...]

iOS开发与OpenGL ES相关问题整理(2)- 绘制图片上下颠倒

使用CGContextDrawImage绘制图片上下颠倒 首先要说的是,在iOS的不同framework中使用着不同的坐标系: UIKit - y轴向下 Core Graphics(Quartz) - y轴向上 OpenGL ES - y轴向上 UIKit是iPhone SDK的Cocoa Touch层的核心framework,是iPhone应用程序图形界面和事件驱动的基础,它和传统的windows桌面一样,坐标系是y轴向下的; Core Graphics(Quartz)一个基于2D的图形绘制引擎,它的坐标系则是y轴向上的;而OpenGL ES是iPhone SDK的2D和3D绘制引擎,它使用左手坐标系,它的坐标系也是y轴向上的,如果不考虑z轴,在二维下它的坐标系和Quartz是一样的。 现在回到问题,当通过CGContextDrawImage绘制图片到一个context中时,如果传入的是UIImage的CGImageRef,因为UIKit和CG坐标系y轴相反,所以图片绘制将会上下颠倒。解决方法有以下几种, 解决方法一:在绘制到context前通过矩阵垂直翻转坐标系 解决方法二:使用UIImage的drawInRect函数,该函数内部能自动处理图片的正确方向 解决方法三:垂直翻转投影矩阵 这种方法通过设置上下颠倒的投影矩阵,使得原本y轴向上的GL坐标系看起来变成了y轴向下,并且坐标原点从屏幕左下角移到了屏幕左上角。如果你习惯使用y轴向下的坐标系进行二维操作,可以使用这种方法,同时原本颠倒的图片经过再次颠倒后回到了正确的方向:

iOS开发与OpenGL ES相关问题整理(1)

在极其有限的工作日的晚上和周末进行着iOS上game programming的研究,进展非常缓慢,不过还是有必要将过程中的一些问题随时记录下来。 OpenGL ES崩溃在函数’glMatrixMode’ 这很有可能是因为在OpenGL ES2的context中使用OpenGL ES1的函数,ES1是固定函数渲染管线 (fixed function pipeline),而ES2是可编程的渲染管线 (programmable pipeline),ES2不再支持这些ES1的固定渲染管线的函数, 比如’glMatrixMode’。所以当ES2遇到这些不支持的ES1函数时,你的程序会收到一个’EXC_BAD_ACCESS’消息并且崩溃。通过gdb查看callstack,显示最后一个函数是gliUnimplemented: 如果一定要使用ES1函数, 你只能以ES1来初始化你的GL context: 函数’pathForResource’返回nil 需要将资源文件添加到项目的’Groups & Files’中 OpenGL ES崩溃在绘制函数’glDrawArrays’ 这可能是因为你启动了GL的某个渲染状态(比如vertex,color,texture coordinate等等,这里有所有可能状态的清单),但是却没有在绘制函数(比如’glDrawArray’)进行之前,设置该状态所对应的数据指针。 比如说: 对于上面这两句代码,如果你设置了使用color数组,但是却没有设置color数组指针,在真正的绘制时你的程序就会因为找不到对应的指针而崩溃。同样的, 如果你调用了‘glEnableClientState(GL_TEXTURE_COORD_ARRAY), 则你也需要通过glTexCoordPointer()设置纹理坐标的数组指针,如此类推。 怎么样通过OpenGL ES以像素为单位来绘制,而不是屏幕比例? 在iPhone上通过OpenGL ES进行2D绘制时,默认的原点位置在屏幕中央,并且屏幕的坐标范围依次是从-1到1,不管是横轴还是纵轴。如下图所示,GL中的默认的单位1分别代表着屏幕长度和宽度的一半。 图片来自book: ‘Learning iOS Game Programming_A Hands-On Guide to Building Your First iPhone Game’ 如果希望将原点移动到屏幕左下角,并且绘制时希望以像素为单位,只需要通过glOrthof函数将投影矩阵设置成一个垂直投影矩阵即可,代码如下:

Game Framework的两种实现方式

一年多前,曾经写过一篇关于Game Engine Framework的文章,当时基本上是为了巩固并加深对framework的理解。最近又做了一些关于framework的工作,对于framework的实现方式又有了些新的认识。虽然我现在做的已经完全不是game了,不过方式对于game也同样适用。 这篇文章主要希望通过一些示例性的C++代码介绍game framework的两种实现方式。首先,我还是搬出一年多前的那篇文章里的game流程图,以下的一些代码也主要基于这张图实现。对于图的细节在这里不再赘述,可以再去翻看之前的那篇文章。 1.通过继承 这是一种最传统的方式了,之前我一直使用这种方式。基本上是提供一个基类,基类封装并决定了整个程序控制流,同时基于该控制流,基类提供了一系列的接口(在C++里是虚函数),以供继承类override,并实现定制化的行为。就像下面的这个Game类,run函数决定了整个程序的执行逻辑,同时initialize,update,render等非纯虚函数则提供了接口以供用户定制并实现具体Game想要的行为。 在上面的代码中,我把实现全部写在了类的定义头文件中,在这里只是为了省事,在现实中你最好还是分开在.h和.cpp文件中。而且在这里,Game类还可以做的更多,我这里只是为了说明实现方式,所以依照前面的流程图而尽量让它简单。另外,不要纠结这段代码中的Event,Mouse,Keyboard等几个类和popEvent方法,它们只是我为了将故事说得更圆满一点而假象出来的几个类,我想你应该能猜到它们是用来干嘛的。 基于此framework,一个具体的游戏只需要重写这些虚函数就可以了,像下面的这些代码。 2.通过Delegation模式 前一种方法有一个缺点,就是将程序的控制流和具体行为紧耦合在了一起,而且还必须使用继承,不易于扩展。现代软件设计的一些方法告诉我们,要尽量使用接口,且尽量使用组合而非继承。Delegation模式就可以帮我们达到这一目的。 何为Delegation模式,wiki上的解释一语中的: Delegation is the simple yet powerful concept of handing a task over to another part of the program. Delegation将一些task委托给程序的另外一部分来处理,以达到了行为使用者和具体行为的松耦合。 以下是通过Delegation模式重新实现的framework。 基于此framework,当需要具体实现一个游戏的时候,只需要实现GameDelegation接口即可,然后将Game类的GameDelegation设置为你所实现的具体的ConcreteGameDelegation类,代码如下。 最近我正好做了一些iOS上开发的研究,发现Delegation在iOS框架中被普遍使用。比如,iOS中的每个应用程序对应的是一个UIApplication类,为每一个UIApplication,开发人员必须要实现一个特定的UIApplicationDelegate,并将它指定给当前的应用程序(在main函数中通过UIApplicationMain函数指定,或者是在nib文件中绑定)。在这个UIApplicationDelegate类中,开发人员就需要重写诸如didFinishLaunchingWithOptions,applicationWillTerminate这样的方法,就类似与上面game framework中的initialize,destroy等方法。当然iOS框架要复杂很多,它还用到其它一系列的设计模式,有空研究些这样设计是非常有趣的。 图片来自于这篇苹果官方关于iOS中delegation的介绍:http://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html

Simulator无法运行OpenGL ES程序

新买了MBP,在上面下载并安装了Xcode3.2.6,终于算是迈出iOS game development的第一步。由于现在工作已经基本跟game毫不相干,只有周末才有时间搞点自己的研究了:-( 在Xcode里新建第一个项目,基于模板“OpenGL ES Application”。不用写任何一行代码,iOS SDK已经为我们创建了一个可以运行的OpenGL应用程序了。从工具栏上点击“Build and Run”(或者通过command+return快捷键),试图启动运行程序。但诡异的是,无论怎样,程序都是一启动,闪动一下就退出了。 打开console(菜单->Run->Console),报出一大串的错误: LLVM ERROR: Cannot yet select: 0x900cd10: v4i32 = bit_convert 0x9013b50 [ORD=129] [ID=69] …… …… Failed to launch simulated application: Unknown error. 由于完全是个iOS新兵,花了好一段周折才解决,解决办法: 1.从Groups&Files的Targets下选择你的项目的target; 2.从工具栏点击“info”按钮,设置target属性; 3.将Build->Deployment->iOS Deployment Target从iOS 4.3改为iOS 4.2; 4.回到Xcode主界面,从工具栏的Overview下拉列表中,可以发现,多出了simulator 4.2的项(原来只有4.3),勾选iPhone Simulator 4.2或者iPhone Simulator 4.3; 5.再次运行程序,跳动的彩色小方块终于出来了。 在Project的info中也可以设置iOS Deployment Target,但是在target level设置的优先级会更高,且一个项目可以有多个target。 这应该是Apple的一个bug,iOS4.3太新了。不过这个错误只有OpenGLES的程序才会有,普通程序没有这个错误。Apple每更新一个新版本的iOS,对于开发人员来说,我想或多或少都会带来一定程度的pain。

Bob Dylan真的来了

无论原来自己多么的不信,不过这回真的是真的,迪伦大爷真的来了。 美国60年代的文化和音乐一直让我着迷,而迪伦正是那个时代的代表之一。如今,这个和Beatles同一时代的音乐巨匠已经年过70岁了,他才第一次将要登上中国的舞台,明晚,就在工体。如果是迪伦的歌迷,估计这是第一次,也是最后一次了。我算不上一个真正的歌迷,因为我承认我听不懂迪伦的很多歌曲,但是我还是很崇拜这样一位神一样的人物,所以,明晚,我要去膜拜他。 迪伦的歌我听过很多,最喜欢的有这些: Blowing in the Wind Knockin’ on Heaven’s Door You Belong to Me I Want You Mr. Tumbourine Man Forever Young Like a Rolling Stone 晒晒我的票:-)

Runtime Game Engine Architecture

上上周末闲得无聊的时候画的:游戏引擎的大概架构图,图片源于Jason Gregory的《Game Engine Architecture》的第29页,这本书也基本上是围绕着这张图对每一个模块深入展开。这张图献给自己和其他游戏引擎架构研究的爱好者,应该打印一张贴到家里的剪贴板上。这段时间对我来说是一段困难的时期,不清楚未来是否还会在game的道路上走下去,但愿这不是一个终结。 runtime game engine architecture, from ‘Game Engine Architecture‘ P.29

关于游戏引擎多线程的一些整理和思考

在最近的项目中,由于时间紧任务重,我果断放弃了引擎原来有点畸形的多线程设计,退回到单线程。最后事实证明,这是一个明智的决定,不但效率没有降低,同时那些永无止境的多线程bug也一并被消除,系统稳定了很多,最终项目得以在deadline之前有一个安全的交付。 1.是否多线程 去掉多线程的主要原因是,原来的多线程设计本身就是一个未经过深思熟虑的设计,它几乎是在项目开发的中后期突然插入的。整个引擎的最初设计没有考虑多线程,现在突然引入了多线程,而又没有经过精心的设计,那结果就是各种多线程bug扑面而来,让开发人员焦头烂额。事实是,如果需要多线程,那最好应该从项目设计架构的第一天开始就仔细考虑。 直观上感觉,好像使用多线程就会带来性能的提升。其实不然,如果没有经过精心的设计,随便引入多线程,只会适得其反。不但性能没有得到提升,而且引入多线程后带来的庞大开发代价和潜在的多线程bug,会让你和团队崩溃到吐血,典型的吃力不讨好的事情。相比于单线程,引入多线程的设计需要带来2到3倍的开发和测试代价,在这么一个时间紧急的项目上,在项目的中后期突然引入多线程真是个大错特错的决策,应该果断拿掉。在这篇博客文章“一种3D引擎的多线程设计方案”里,作者就提到,“多线程的引入使引擎变得更加复杂,不良的设计带来的性能提升非常有限,甚至在单核环境下还会出现明显的性能下降”。 从这篇Unreal引擎工程师Tim Sweeney的访谈文章中,我找到了在我的情况中应该拒绝多线程的另一个重要原因。 Tim提到: For multithreading optimizations, we’re focusing on physics, animation updates, the renderer’s scene traversal loop, sound updates, and content streaming. We are not attempting to multithread systems that are highly sequential and object-oriented, such as the gameplay. This is also why it’s especially important to focus multithreading efforts on [...]