龙源期刊网 http://www.qikan.com.cn
基于GDI的2D游戏框架设计
作者:罗林 李俊琴
来源:《电脑知识与技术》2014年第27期
摘要:2D游戏具有很大的应用前景。游戏软件是一种复杂度较高的软件,利用游戏框架可以降低游戏开发难度,提高游戏开发效率,因此设计2D游戏框架具有现实的意义。该文探讨了框架的概念,分析了2D游戏软件开发的特征,从中总结出2D游戏框架需要实现的功能,结合Windows的图形设备接口(GDI),提供了一个2D游戏框架的设计,其中实现了精灵创建、释放、渲染、资源管理、帧频锁定、事件传递等2D游戏中必需的功能,并留出了可扩展的接口。该文最后给出了框架的使用方法。 关键词:2D游戏;GDI;框架设计
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2014)27-80-03 Abstract: 2D Game has great application prospect. Game software is a higher complexity software, and the use of game framework can reduce the difficulty of game development, improve the efficiency of game development, so design a 2D game framework has practical significance. This paper discusses the concept of a framework, analyze the characteristics of 2D game software development, summed up the 2D game framework needed to achieve the functionality, provides the framework for the design of a 2D game with Windows Graphics Device Interface (GDI), realize the sprite to create, release, render, resource management, frame frequency locking, events dispatcher and other necessary function in 2D game, and set aside a scalable interface. Lastly, the paper proposes the framework of the method of use. Key word: 2D Game;GDI; Framework Design
在软件开发中,利用框架可以在保证质量的情况下提高软件开发效率,降低软件开发难度,是目前开发软件的一种主流方法。游戏软件一般要求响应快速精确,需要同时处理声音、图像等很多资源,开发难度较高。现实开发中,通常利用各种框架或者引擎来降低开发难度,提高效率。因此,研究游戏开发中的框架设计具有实际意义。 1 框架的概念
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法。另一种定义认为,框架是可被应用开发者定制的应用骨架。
从软件设计的角度来看,框架首先是解决一类问题的规范。框架有特定的应用领域,不同的框架用于解决不同的软件开发需求。框架也是一种规范,它向使用者承诺,使用框架可以正确完成某些特定的功能,帮助软件开发者快速准确地完成软件开发任务。同时,软件开发者只
龙源期刊网 http://www.qikan.com.cn
能遵循框架所约定的特定的调用方法,才可以正确的使用框架完成软件开发任务。其次,框架抽象了共同的部分。设计框架的目的是帮助开发者快速搭建好软件开发的骨架,提供某一类软件开发任务同的解决方案。因此框架有必要将某一类软件开发过程中不变的、共同的解决方法抽象出来,形成类结构体系,便于软件开发者调用。最后,框架还应该提供可扩展的接口。开发者使用框架的目的是为了方便开发出自己想要的软件,因此框架必须是可扩展的,便于开发者在框架的基础上设计出不同的软件项目。
因此,框架设计中要注意面向对象设计思想中的“开—闭”原则的使用,即对修改封闭,对扩展开放,才能达到既抽象共同部分,又留下可扩展接口的设计目标。 2 基于GDI的2D游戏框架
游戏软件开发中,如何高效的管理、渲染各种图像是关键技术点。GDI是Windows操作系统中图形设备接口(Graphics Devices Interface)的简称。GDI提供了与设备无关的图形图像显示接口,开发者通过GDI可以方便地驱动硬件设备显示指定的图形图像。
游戏框架有多种,该文仅讨论一种2D游戏框架的设计思路。其中渲染相关的操作将借助GDI完成,因此在框架设计时必须考虑GDI所需要的渲染环境准备。 3 框架的设计分析
总结一下2D游戏的特点可以发现,所有的2D游戏都具有共同的一些特征: 1) 加载必要的资源(图像,声音等)
游戏包含丰富多彩的多媒体效果,这些效果离不开图像和声音。其中重点是各种图像效果的呈现,简单起见,该文仅讨论游戏中的图像呈现部分,框架设计也围绕图像的呈现来展开。 在屏幕上显示的图像,就是把存放在存储设备上的图像文件加载到内存中,按一定的方式显示。因此,图像显示的第一步是需要将图像文件作为资源加载到计算机内存中。资源加载需要用到一定的策略。游戏中将呈现众多图元,如100架敌机,它们对应了同一个图像文件。可以设计一个资源池,将所有资源载入,在内存中以一定的方式组织和管理起来,在需要用到某资源时,从内存中将其读出并应用。
2) 开始游戏后,按与用户的交互情况推进游戏的发展,直到用户退出游戏
游戏的发生、发展需要按照一个时间轴往前推进。在推进的过程中,需要接受用户的输入事件,根据用户的操作,执行游戏逻辑,推进剧情的发展。根据与用户的交互结果,显示当前的游戏画面和声音变化。值得注意的是,在游戏推进过程中,需要考虑在不同性能的机器上获得大致相同的执行速度,也就说框架需要有一个锁定帧频的机制。
龙源期刊网 http://www.qikan.com.cn
3) 释放加载的资源
所有的资源的加载,都会花费一定的内存空间。这些内存空间需要在特定的时候由开发者主动释放,否则会导致内存泄漏,影响操作系统的运行性能。因此,框架也需要引入一种机制,可以在需要时把所加载的资源卸载,回收相应的空间。
分析以上所描述的游戏特征,可以总结出框架设计时应该达到的设计目标:系统的时间的传递,锁定帧频和准备渲染环境;资源管理;图像呈现和管理;键盘和鼠标事件的处理;可扩展的接口设计等。 4 框架设计与实现
基于C++的实现环境,框架功能的实现依靠类和类的有序组合来实现,类的设计依赖于设计目标。各类的设计重点详细描述如下: 1) CSprit类
制作2D游戏首要解决的问题是怎样将形形色色的精灵显示在屏幕指定的位置上,同时还能对精灵进行旋转、缩放和透明等一系列操作。在GDI的绘图环境里,图像通过LoadImage函数载入到内存,这个内存区域称作为“离屏缓冲”,之后使用BitBlt函数族(BitBlt,StretchBlt和TransparentBlt)将“离屏缓冲”缩放或透明地渲染到显示设备的指定位置上。 因此CSprite类里设计了一个渲染函数Render将上述内容进行封装,完成渲染操作。另外,在游戏中存在各种不同的精灵类型,比如可以播放帧序列动画的精灵,可以按自己固定轨迹移动的精灵等。这些不同类型的精灵在渲染图像方面的操作是一样的,不同的是它们随着时间的流逝,各自的行为有所不同。所以在CSprite类里为更新精灵留出可扩展的接口Update,这是一个虚函数:
virtual void Update(float fDeltaTime){}
该函数的参数是系统从上一帧到这一帧流逝的时间。因为CSprite类本身不需要更新自己,所以该函数的实现是空的,留给子类进行扩展。 2) CAnimationSprite类
CAnimationSprite类用于描述动画精灵。动画精灵的纹理不是固定的一帧,而是由若干帧组成。动画精灵在表现角色动作方面特别有用,比如角色的行走、战斗等动作都不是用固定的一帧图像能表现出来的,有了动画精灵,就可以把多幅图像按照指定的速度播放出来,这样能较好地表达角色的动作行为。
龙源期刊网 http://www.qikan.com.cn
CSprite类已经具备一般的渲染能力,因此CAnimationSprite类只需要扩展CSprite类,重写Update函数,为其添加加载图片播放控制部分的功能即可。播放控制主要体现在速度控制和纹理坐标截取两方面。为此,动画精灵需要在每帧里计算自己需要显示哪一帧图片。这个计算包含两方面的意思:一是计算时间,以决定是否该更新到下一帧;二是如果需要更新,下一帧的纹理坐标如何计算。 3) CResourceManager类
如前所述,游戏中的资源需要利用资源池统一管理。在设计CResourceManager类时需要考虑资源如何加载、存储、获取。在GDI环境里,资源的加载是通过LoadImage函数完成,加载后以离屏缓冲的形式存储在内存中。资源加载后的数据可以以标准模板库中的list容器方式来存储。在设计list容器中的节点数据类型时,要考虑资源数据的存储和获取两方面;由于游戏中用到的资源较多,可以将资源分为多种类型或组别。在资源管理类中,设计新增、删除、获取资源节点的方法即可。 4) CSpriteManager类
2D游戏中将不断的有精灵产生和消亡,同一时刻,游戏中的精灵数目是不确定的。而这些不确定数目的精灵,都需要在每帧通知它们更新和渲染自身,因此需要把它们有序的组织起来。
精灵的组织和管理包括以下方面:创建精灵、删除精灵、通知所有精灵更新、通知所有精灵渲染、释放所有精灵对象所占内存。其中很多操作都需要遍历所有精灵,而且精灵产生和消亡是不确定的,所以精灵采用标准模板库中的list容器作为数据存储结构,由此创建、删除和遍历的操作就转化为对list容器的相应的操作。list容器中节点的数据类型是指向CSprite的指针。注意到CSprite是精灵体系的基类,那么当把精灵存储到list中后,在进行遍历操作时,由于多态,将把更新、渲染、释放等操作事件通知到不同类型的精灵。框架中目前提供了CSprite和CAnimationSprite两中精灵类型,那么CSpriteManager类也提供了两种对应的创建函数。
5) CGame类
CGame类是框架和系统事件之间的桥梁,是整个框架的骨干。CGame类需要具备以下功能:通过消息循环来获得推动时间轴向前的动力,对不同性能的计算机保持大致相同的帧频;为渲染做好准备;在每一帧,通过CSpriteManager对象通知游戏中所有精灵更新和渲染自身;在游戏中,将键盘和鼠标事件传达给每个可视对象;在游戏结束之前,通过CSpriteManager的析构函数释放所创建的精灵对象等等。
因此,CGame类中设计了初始化函数Init,帧函数FrameFunc,更新函数Update,渲染函数Render,CSpriteManager指针类型成员。在消息循环中调用帧函数,主要完成锁定帧频、调
龙源期刊网 http://www.qikan.com.cn
用Update函数更新精灵,调用Render函数渲染精灵。这里的Update函数和Render函数都要设计成虚函数,便于子类继承扩展,完成对游戏逻辑和游戏渲染的重写。在初始化函数中完成GDI绘图环境的准备,主要是要完成“双缓冲”的创建,以避免游戏中的屏幕产生闪烁的现象。同时,该函数定义成虚函数,其目的是方便子类继承扩展,用以完成不同类型的资源加载和不同游戏环境的初始化操作。 5 框架的使用
CGame类是整个框架的骨干,也就是整个游戏的核心。为此,使用本框架首先要定义一个类,继承自CGame类,在其中重写Init函数,完成加载资源和初始化工作。而游戏逻辑可以放在Update函数里,特定的渲染相关的操作可以放在Render函数里完成。总结使用该框架的一般步骤如下:
1) 建立一个类,继承自CGame,如CDemoGame; 2) 重写Init,加载各种资源;
3) 如有必要,重写Update函数,实现特定的游戏逻辑; 4) 如有必要,重写Render函数,实现特定的渲染操作; 5) 在进入消息循环之前创建CDemoGame对象;
6) 在消息循环的空闲段,调用创建的CDemoGame对象的FrameFunc函数更新一帧; 7) 如果有需要,拦截相应的消息,调用创建的对象的相应的消息处理函数; 8) 在程序结束之前,释放创建的对象。 6 结束语
游戏开发是比较复杂的软件开发,一个好的框架可以大大简化游戏开发的复杂程度。该文就这个问题给出了一个确实可行的解决方案,能满足性能、可扩展等方面的要求。该文是基于GDI进行讨论的,也可以在此基础上改为通过Direct3D来完成渲染的操作,进一步提高游戏的运行性能。 参考文献:
[1] 全国科学技术名词审定委员会.框架[EB/OL].http://baike.baidu.com/view/66971.htm. [2] 罗林.2D游戏中的精灵管理[J].科技创新导报,2009(23):176.
龙源期刊网 http://www.qikan.com.cn
[3] 刘生建,罗林.Windows游戏编程中双缓冲处理技术的封装设计[J].科技信息,2010,(34):633
[4] 秦海玉.Windows游戏程序设计基础[M].北京:电子工业出版社,2011.
[5] Stanley B.Lippman,Josée LaJoie,Barbara E.Moo.C++ Primer (4th Edition) [M].李师贤.蒋爱军,梅晓勇,林瑛.译.北京:人民邮电出版社,2006.