转自:http://elvisco.de/2012/08/利用cocos2d-x-for-xna快速开发windows-phone游戏/
注:本文已发表在《程序员》杂志第六期,参见:http://www.programmer.com.cn/11833/
新生的移动平台Windows phone
说Windows phone是一个新生的移动平台,已经不那么准确了,早在2010年微软就正式公布了windows phone 7操作系统,但对于国内市场来讲,今年才是windows phone正式进入中国市场的开始。2012年3月21日,微软正式在北京召开了windows phone 7.5发布会,宣布windows phone正式登陆中国市场。随后诺基亚和中国电信也宣布了Lumia手机将在中国全面上市。
Windows phone拥有全新的Metro风格,硬件标准化以及以编程方式对其进行访问的标准化,深度整合的社交功能,以及对Office的支持等等。也是微软重拳进入移动互联网的重要武器。截止5月,WP7的Marketplace已经有8万多款应用,毫无疑问已经建立起了手机应用的第三大生态圈,仅次于Android和ios平台。
Windows phone应用开发
我们知道,在Windows phone系统上开发应用程序有两种选择:Silverlight for Windows phone和XNA Framework。
Silverlight是一个基于XAML的事件驱动的应用程序框架,XAML是一种描述Silverlight界面组件的标记语言,利用Expression Blend工具可以设计出非常绚丽的应用程序外观。Silverlight集成了一些传统的控件,高品质的文本,矢量图,媒体动画效果及数据绑定等功能。Silverlight适用于开发具有固定页面外观的应用程序,如新浪微博。
XNA Framework的设计初衷是为了让游戏开发变得更简单,XNA主要用来编写高性能游戏,它同时支持2D和3D。使用XNA,你不必考虑创建窗口以及管理它,你需要关注的就是你的游戏逻辑,XNA提供了图形设备访问的接口,另一方面XNA对于游戏中很重要的资源的管理做了统一的优化处理,内容管道将会为你处理内容的导入,编译和载入。同时,XNA也是一个跨平台的游戏框架,你可以轻松的移植游戏到Xbox360,PC和Windows phone设备上。
本篇,我们主要讨论基于游戏轮询的XNA游戏开发。
Cocos2d游戏引擎进入Windows phone平台
我们看到,XNA已经帮助我们处理了很多事情,大大的降低了游戏开发的难度,例如不需要管理窗口,统一的内容模型,以及使用托管的C#语言开发游戏。然而在游戏逻辑之外,XNA只是提供了一些基本的编程接口,我们还需要维护游戏界面各种元素的层级关系,实现精灵在游戏中的各种炫酷的动作,甚至于创建一个复杂的地图,使用粒子效果等。这些都是一个游戏引擎实现的功能,然而Windows phone平台一直缺乏比较好的游戏引擎。
在iOS平台,cocos2d是一个被公认的优秀的2D开源游戏引擎,截止目前,已知的有3600多款游戏采用了该引擎。在各大移动终端百花齐开的时代,跨平台已经成为游戏厂商重要的关注点,cocos2d-x正是基于coco2d-iphone采用C++移植的版本,支持包括iOS,Android,Bada,BlackBerry Playbook,Windows XP,Windows 7,Linux等多个平台。使游戏开发者能够以同一套API、甚至同一套代码将游戏分发至多个游戏平台,大大减少多个平台之间的研发和维护成本。目前已经有200多款使用cocos2d-x实现跨平台的游戏,其中不乏Zynga,Glu,Disney Mobile,空中网,网龙,Chillingo各手游大厂的作品,和大量荣登AppStore Top10的中小团队乃至个人开发者作品,去年以来国内大热的《捕鱼达人》也正是基于cocos2d-x引擎开发。
正是在此背景下,cocos2d-x社区联合国内知名的Windows phone社交游戏平台OpenXLive移植了coco2d-x for XNA版本,大大降低了在Windows phone平台上开发游戏的难度。
Cocos2d-x for XNA概述
2012年2月17日,cocos2d-x for XNA发布了第一个beta版本,目前已经是0.1.1的release版。
在移植过程中,不可避免的是,XNA版本会有比较大的变化,因为它不仅仅是由C++翻译为C#,底层的绘制接口也由OpenGL变换为XNA。即便如此,在翻译过程中我们还是保持了大部分接口一致,以至于大部分的代码都可以由cocos2d-x轻松1:1转换为cocos2d-x for XNA。
相信大家也看过不少cocos2d-x引擎相关的教程文档,cocos2d引擎各个版本保持接口一致,这里我就不重点介绍引擎结构,我将结合移植过程给大家分享一些cocos2d-x在XNA版本的一些变化及注意事项。后面我会以一个实例带大家体验cocos2d-x for XNA快速开发一个简单的小游戏。
我将沿着主要类的变化来分析XNA版本的变化,在每个变化的类里,我将说明一些变化的地方,以及为什么会有这样一些变化,主要涉及的类包括:
- CCApplication
- CCNode
- CCSprite
- CCSpriteBatchNode
- CCImage
- CCRenderTexture
- CCSAXParser
- Zip Support
CCApplication的变化
关于CCApplication,主要有三方面的变化:
一方面是因为XNA中去掉了窗口管理,所以去掉了CCEGLView相关的类。
第二是游戏轮询结构的变化。在XNA中,CCApplication是继承于DrawableGameComponent,所以去掉了run方法中实现的循环,因为GameComponent本身就实现了游戏循环结构。在XNA版本中,CCApplication的Update重载方法处理Touch事件,Draw重载方法调用CCDirector的mainLoop方法进入游戏递归绘制元素。
另一个比较重要的变化是,CCApplication增加了一些XNA绘制相关的全局属性,包括:
- ContentManager:用于管理资源的加载。
- SpriteBatch:用于绘制2D的纹理。
- WorldMatrix:当前着色器的世界矩阵。
- ViewMatrix:当前着色器的视图矩阵。
- ProjectionMatrix:当前着色器的投影矩阵。
- basicEffect:用来绘制元素的着色器。
这些属性主要用于绘制,它们会被CCSprite,CCSpriteBatchNode以及CCTextureAtlas等与绘制相关的类使用。如果你的游戏中需要自己绘制2D或者3D的元素,也可以直接调用这些属性进行绘制。
CCNode的变化
在cocos2d中,CCNode提供了cocos2d中各个元素的一些公共属性,节点的层级结构,以及主要实现元素递归绘制的逻辑。由于cocos2d中每个节点的位置和变换是相对于父节点的,因此CCNode还承担重要责任,那就是调整着色器的世界矩阵变换。
在OpenGL中,有一个当前矩阵的概念,并且有一个矩阵栈,当开始绘制CCNode的时候,将当前矩阵压入栈,然后根据当前CCNode的位置,缩放,旋转,扭曲等属性进行一个变换计算,然后将当前矩阵左乘这个变换,就可以实现相对父元素绘制节点。
然后在XNA中没有当前矩阵的概念,因此需要模拟一个这样的概念,因此在XNA版本中做了一点变化。
只不过这里并没有实现一个矩阵栈,因为在这里的矩阵栈都是通过一些列的左乘形成的,只要按相反的顺序左乘对应矩阵的逆矩阵,就可以返回栈中的矩阵。所以这里添加一个Matrix类型的变量m_tCCNodeTransform来保存每个CCNode的变换,然后:
- 在transform方法的末尾,将着色器的世界矩阵左乘以该矩阵,以将着色器变换到当前元素相对于父元素的位置: app.basicEffect.World = m_tCCNodeTransform * app.basicEffect.World;
- 在visit方法的末尾,当前元素绘制完毕时,将着色器的世界矩阵左乘该矩阵的逆矩阵,以将着色器变换回到上一级元素的位置: asicEffect.World = Matrix.Invert(m_tCCNodeTransform) * CCApplication.sharedApplication().basicEffect.World;
除此之外,CCNode没有其他变化。
XNA中的绘制
在XNA中绘制3D(注意,cocos2d虽然是2D引擎,但底层是采用3D绘制的),归纳起来有以下几个步骤:
- 构造一个basicEffect,这在CCApplication的初始化中构造。
- 设置摄像机的视图矩阵和投影矩阵,这在CCDirector中的Projection属性赋值时设置。
- 给basicEffect的Texture属性赋值一个纹理Texture2D。
- 构造顶点数组VertexPositionColorTexture,描述每个点的位置,颜色等信息。
- 调用DrawUserIndexedPrimitives或相应的方法进行绘制。
绘制的部分,几乎都体现在CCSprite.Draw方法中,当然在Draw方法中还涉及到许多参数的设置,比如混合因子,透明度等等。
以上几乎是cocos2d-x for XNA所有绘制的部分,所不同的是在CCSpriteBatchNode和CCTextureAtlas中是使用一个纹理同时绘制多个精灵。
而所有跟绘制相关的变化,基本上都封装在Draw方法中,比如将cocos2d中的Quads数组转化为VertexPositionCOlorTexture数组,cocos2d中的混合因子转化为XNA中的混合因子,这样使得其他方法基本上不发生变化。
关于管道和资源
在win32或者大多数开发场景中,资源(声音,音效,图片,3D模型等)一般都是以文件流的形式在运行时加载到程序中,然后由一些内容导入器对特定格式的文件进行解析处理,系统往往提供一些受支持的格式,另外一些特殊或自定义格式文件则需要开发者自行处理。并且不同的平台处理内容的方式还不太一样,例如在Xbox 360和Windows phone上对声音的支持都不一样。
在XNA中,为了简化游戏内容的加载,这部分被抽象成一种叫做内容模型(Content Pipeline)。当你把文件添加到XNA Studio项目中,他们自动被处理,编译成适合当前选定平台的输出格式。不过这样比较麻烦的是它要求每一个原始内容文件格式都要被支持。
由于cocos2d主要是由iphone发展而来,它支持的许多都是ios中常用的格式,如.plist,.tmx等,这些格式的文件在XNA中是原生不受支持的,因此需要实现自定义的管道用于编译和加载这些类型的文件。
所以在cocos2d-x for XNA中添加了一个cocos2d.Content.Pipeline.Importers的工程,用来处理这些格式的文件。但是这里做得比较简单,仅仅是直接转化成文本,所有的解析都需要在运行时完成。
如图,我们看到选中了自己的导入器和处理器TextImporter和TextProcessor。所以在开发者自己添加plist或者tmx等格式中,都需要按照tests项目中该类文件一样的设置,以保证能够被引擎正确解析。
不过,这种处理方式并不标准,它只能用于引擎内部使用,如果开发者使用自定义的plist文件,开发者不得不在程序中自己去解析,就像引擎所做的那样,所以后续我们也会考虑在管道里直接做得通用一些,因为像自定义plist的文件使用非常频繁。
另外一点需要注意的是,在win32中,。.fnt,.plist,.tmx等格式文件中对应的图片资源基本上都是和该描述文件处于同一目录,但是在XNA中,资源的识别不包括文件名扩展,所以这三类描述文件对应的图片资源都需要放在同级目录的images文件夹下才能被引擎正确解析。
关于集合和数组
Cocos2d中实现了对集合和数组的处理,例如CCArray和CCSet。其实对于其他一些类型如CCPoint,CCDictionary,CCSet等cocos2d也有自己的封装,这样做的好处是在不同平台对上层的接口保持一致,同时避免不同平台这些类型的实现差异,将这些差异隐藏在引擎内部而使接口保持一致。
尽管如此,当初在移植的时候我们还是选择了用C#本身的Array和List类型代替了cocos2d中的CCArray和CCSet,这样做的目的是当时为了赶进度,然而有很多开发者反应对这些封装的改变导致移植上需要改变很多地方,所以后期我们还是尽量改回cocos2d原生的封装类型上去。
然而这里需要强调的是开发者自己在移植过程中需要注意的地方,这也是cocos2d-x for XNA引擎在移植过程中遇到比较大的问题,那就是关于C++在对数组上应用指针来索引。
例如我们看看C++中初始化数组的方法: m_pVertices = malloc((m_sGridSize.x+1) * (m_sGridSize.y+1) * sizeof(ccVertex3F));
在C#中对应则是: m_pVertices = newccVertex3F[(m_sGridSize.x + 1) * (m_sGridSize.y + 1)];
在C++中我们可以通过指针定位到数组的不同位置,如果你知道某个指针的数据类型,你可以转化为对应的类型进行使用,同一个指针可以转化为不同类型的数组。而C#中则不能使用指针(在Windows phone中不能使用不安全代码),这些通过指针索引和操作的方式,全部需要改为基于数组索引和使用强类型,所以这会导致移植上的一些工作量。
其他变化
前面我们总结了一些cocos2d-x for XNA版本中比较大的变化和注意事项,剩下还有其他一些小的细节,比如CCSAParser中采用XMLElement流式读取文档的方式解析数据,对于gzip和zlib的支持采用了SharpZipLib和zlib两个类库,同时将get和set方法改为C#中的属性等等,大家可以慢慢去发现。