首页 > 代码库 > Cocos2dx 跨平台使用

Cocos2dx 跨平台使用

环境搭建

0 引擎介绍

Cocos2d-x 是一款开源的2d游戏引擎,目的是封装底层绘图代码(OpenGL),简化 2D 游戏的开发过程.它原型是 Cocos2d,最早出现在iphone上(Cocos2d-iPhone).后来中国的开发者(触控科技)在Cocos2d-iPhone进行了移植,使它成为一款跨平台的游戏引擎(目前支持ios,windows,android,tizen,blackberry等几乎所有的主流移动操作系统).

目前Cocos2d-x的最新版本是3.2,已经支持3D特性,因此,目前Cocos2d-x已经不单单是一款2D游戏引擎了.目前手机上相当数量的游戏基于Cocos2d-x开发,包括刀塔传奇/我叫MT/捕鱼达人/三国杀等等.

1 版本选择

尽管目前最新版本已经演进到了3.X版本,但是由于最新版本加入了C11特性,要求开发环境必须支持C11,这也就是意味着Visual studio的版本必须高于VS12,但是由于VS12要求操作系统的版本是win7 SP1,我们目前电脑无法满足,因此我们选用了2.1.4的版本(刀塔传奇使用此版本开发)来进行开发.

以下是所需要的搭建环境所需要的软件:
1.Cocos2dx 2.1.4
2.Android NDK-r10c(由于早期版本的NDK存在各种奇怪问题,会导致cocos2dx编译失败,建议使用最新版r10c)
3.Android SDK
4.Tizen 2.2(2.3版本目前缺乏足够文档支持,暂时没有找到可以在2.3版本上成功编译cocos2dx的方法)
5.VS2010 Express(目前win7支持的最高版本,如果系统不受限制,可选择安装更新版本)
6.Cygwin (windows上的linux模拟环境,android本地编译需要)
7.Python(V2.7)

安装好上面这些软件,即可开始cocos2dx的开发(如果只需要在windows上运行,只安装1和5即可).

2 创建windows项目

因为我们是在windows上进行开发,所以我们首先要创建一个windows项目.
进入Cocos2dx 2.1.4目录下的tools\project-creator目录,命令行执行以下命令:
python create_project.py -project MyGame -package com.MyCompany.Game -language cpp
其中project和package名称需要根据个人需要来命名.
技术分享

命令执行成功后,会在Cocos2dx 2.1.4目录下的projects目录下找到对应名字的项目.
进入项目下的proj.win32目录,使用VS打开项目,即可开始编写程序.
技术分享

项目会默认关连五个lib库,请不要修改这5个lib库.直接运行当前项目,我们就可以看到cocos2dx版的hello world了.
技术分享

这样,我们已经可以在这里开发windows版本的游戏了.

3 开发android项目

那么开发好的windows游戏如何运行在android上呢?

3.1 创建android项目

再次进入cocos2d-x-2.1.4所在的目录,打开文件create-android-project.bat进行编辑.
将文件中的_CYGBIN,_ANDROIDTOOLS,_NDKROOT分别设置为cygwin的bin目录,android sdk的tools目录,以及NDK所在的目录.
配置成类似下面的样子,然后保存.

set _CYGBIN=e:\cygwin\bin
if not exist "%_CYGBIN%" echo Couldn't find Cygwin at "%_CYGBIN%" & pause & exit 4
set _ANDROIDTOOLS=E:\development\android-sdk\tools
if not exist "%_ANDROIDTOOLS%" echo Couldn't find android sdk tools at "%_ANDROIDTOOLS%" & pause & exit 5
set _NDKROOT=E:\development\android-ndk32-r10b-darwin-x86.tar\android-ndk-r10b
if not exist "%_NDKROOT%" echo Couldn't find ndk at "%_NDKROOT%" & pause & exit 6


点击运行create-android-project.bat文件,按照要求输入包名,以及项目名,程序就会在cocos2d-x-2.1.4所在的目录下创建一个由项目名命名的android项目目录.
技术分享

接下来需要将之前在projects目录下开发好的Classes和Resources目录拷贝到android项目目录下.

3.2 编译android项目

接下来我们需要编译android项目,进入项目文件夹的下的proj.android\jni目录,打开Android.mk文件进行编辑,修改LOCAL_SRC_FILES一项,加入你所编写的所有cpp文件,示例如下:

LOCAL_SRC_FILES := hellocpp/main.cpp                    ../../Classes/AppDelegate.cpp                    ../../Classes/HelloWorldScene.cpp                    ../../Classes/BulletLayer.cpp                    ../../Classes/Enemy.cpp                    ../../Classes/EnemyLayer.cpp                    ../../Classes/GameLayer.cpp                    ../../Classes/GameScene.cpp                    ../../Classes/LifeCircleLogger.cpp                    ../../Classes/PlaneLayer.cpp                    ../../Classes/WelcomeLayer.cpp                    ../../Classes/WelcomeScene.cpp                    ../../Classes/BompLayer.cpp \


保存后退出.

接下来使用cygwin进入proj.android目录,执行./build_native.sh开始编译.
如果出现找不到NDK的错误,需要先执行如下两句:

NDK_ROOT=/cygdrive/d/Downloads/android-ndk-r10c 
export NDK_ROOT


其中NDK_ROOT为NDK所在的目录.

需要指出的是,由于NDK使用的编译器和VC使用的编译器不同,这里编译过程中可能出现各种编译的问题.使用最新版本的NDK可以解决大多数问题,其余具体问题请根据程序的不同具体进行修改.
(不得不说的是,在NDK的编译过程中你可能会遇到各种奇怪的问题,但是由于2.1.4是一个应用相当广泛的版本,因此几乎你可能遇到的所有问题都能在Google上找到解答)

一切顺利的话,经过十几分钟的编译后,会有库文件生成(libgame.so).
技术分享

3.3 运行android项目

接下来我们需要将项目import到Eclipse中.
技术分享
项目导入后会有一个android:icon的错误,修正之后,插上手机或者打开虚拟机,程序即可被编译运行.

这样,我们的游戏就可以被运行在android手机上了.

4 开发Tizen项目

Cocos2dx引擎也支持Tizen,但是Tizen的情况复杂的多.
因为目前Tizen处在高速迭代的过程中,各个版本之间甚至都互不兼容,因此网上仅有的一些帮助文档很多还是不可用的.我现在也仅仅搞定了把游戏编译成Tizen的TPK,但是目前卡在签名的安全性认证上,暂时无法运行.因此下面我们只讲如何编译出Tizen可用的TPK,系统安全性的问题不讨论,有兴趣的同学可以在此基础上进行研究.
另外,下面默认已经下好Tizen的SDK以及ide.

因为目前我们还找不到创建Tizen项目的模版,Tizen IDE也不支持直接创建Cocos2dx项目(可以自己创建配置,参考https://developer.tizen.org/zh-hans/documentation/articles/using-cocos2d-x-tizen-native-applications?langredirect=1).因此目前比较简单的办法是直接修改demo提供的Tizen项目.

  • 使用samples\Cpp\HelloCpp目录下的项目,将你的代码和资源移入这个项目,首先保证在windows下可以正常运行.

  • 然后将资源文件拷贝入proj.tizen\res目录下.

  • 打开Tizen IDE,导入一个Native Project
    技术分享
  • 选择cocos2dx所在目录,保证如下五个项目一定被选择.
    技术分享
  • 接下来导入自己的Tizen项目,此时一般会有如下六个项目:
  • cocos2d-x/external/Box2D/proj.tizen
    cocos2d-x/external/chipmunk/proj.tizen
    cocos2d-x/cocos2dx/proj.tizen
    cocos2d-x/CocosDenshion/proj.tizen
    cocos2d-x/extenshions/proj.tizen
    cocos2d-x/samples/Cpp/TestCpp/proj.tizen


  • 接下来首先编译前五个库项目,顺利的话,几十分钟后前五个库项目可以编译完成
  • 打开tizen虚拟机,编译自己的tizen项目,正常情况下会编译失败.此时一般是由于C++编译器的环境配置有问题,修改项目配置中的C++编译器选项,如下图:
    技术分享
    查找电脑,找到编译过程中报错的缺失的文件在电脑中的位置,将这些目录加入C++ linker的libraries目录中.
  • 重新编译项目,编译产生TPK
  • 参考官方文档为程序签名(https://developer.tizen.org/dev-guide/2.2.1/org.tizen.gettingstarted/html/tizen_overview/app_sign.htm),安装程序,即可执行(签名后还是会遇到各种安全性问题,目前依然没有完全搞定)

游戏开发

概述

Cocos2d 的
游戏结构可以简单地概括为场景、层、精灵。每个游戏组件都可以添加到另一个组件中,形成层次关系,例如场景中可以包含多个层,层中可以包含多个精灵。

Cocos2d-x 的类都放置于 cocos2d命名空间下。以引擎目录下的"actions/CCAction.h"为例,我们可以看到文件的首位有两个宏: NS_CC_Begin和NS_CC_END。查看宏的定义可知,这两个宏相当于把所有的类型都包含在了 cocos2d 命名空间下。在游戏中,我们常使用引擎提供的另一个宏 USING_NS_CC 来引用 cocos2d 命名空间.

Cocos2d-x 拥有一个包含其他所有头文件的文件"cocos2d.h"。通常,我们只需要在使用时包含这个头文件,就可以使用引擎的全部功能了。

坐标

在 Cocos2d-x 中,存在两种坐标系。

  • 绘图坐标系。它是最常见的坐标系,与OpenGL采用的坐标系相同,以左下角为原点,向右为x轴正方向,向上为 y 轴正方向.在 Cocos2d-x 中,一切绘图相关的操作都使用绘图坐标系,如游戏元素中的 Position 和 AnchorPoint等属性。
  • 纹理坐标系。纹理坐标系以左上角为原点,向右为 x 轴正方向,向下为 y 轴正方向.在 Cocos2d-x 中,只有从纹理中截取部分矩形时才使用这个坐标系,如 CCSprite 的 TextureRect 属性。

类的创建

Cocos2d-x 不使用传统的值类型,所有的对象都创建在堆上,然后通过指针引用。创建 Cocos2d-x 对象通常有两种方法:第一种是首先使用new操作符创造一个未初始化的对象,然后调用init系列方法来初始化;第二种是使用静态的工厂方法直接创建一个对象(Cocos2d-x的初始化方法都以init作为前缀,因此可以轻易辨认出来。初始化方法返回一个布尔值,代表是否成功初始化该对象;工厂方法的名称统一为 create)。

这两种方法都可以创建 Cocos2d-x对象,然而它们在内存管理方面还是有一点点差异的。使用构造函数创建的对象,它的所有权已经属于调用者了,使用工厂方法创建的对象的所有权却并不属于调用者,因此,使用构造函数创建的对象需要调用者负责释放,而使用工厂方法创建的对象则不需要。

使用构造函数创建对象时,对象的引用计数为1,因此调用者需要在使用完毕后谨慎地释放对象;使用工厂方法创建对象时,虽然引用计数也为1,但是由于对象已经被放入了回收池,因此调用者没有对该对象的引用权,除非我们人为地调用了 retain() 来获取引用权,否则,不需要主动释放对象。

在 Objective-C 中并没有构造函数,创建一个对象需要先为对象分配内存,然后调用初始化方法来初始化对象,这个过程就等价于 C++中的构造函数。与Objective-C一样,Cocos2d-x也采用了这个步骤。Cocos2d-x类的构造函数通常没有参数,创建对象所需的参数通过init开头的一系列初始化方法传递给对象。

为了保证初始化方法可以被子类重载,需要确保初始化方法声明为虚函数.

Selector 选择器

在 Objective-C 中,选择器( Selector)是类似于 C++中的类函数指针的机制。由于 Cocos2d-x 继承了 Cocos2d-iPhone 的代码风格,因此也提供了一系列类似于Objective-C中创建选择器语法的宏,用来创建函数指针。这些宏都只有一个参数SELECTOR,表示被指向的类方法。将这些宏列举如下:

  schedule_selector(SELECTOR)
  callfunc_selector(SELECTOR)
  callfuncN_selector(SELECTOR)
  callfuncND_selector(SELECTOR)
  callfunc_selector(SELECTOR)
  menu_selector(SELECTOR)
  event_selector(SELECTOR)
  compare_selector(SELECTOR)


CCDirector 导演

CCDirector 的工作确实跟导演非常类似,主要负责以下工作。

  • 游戏呈现方面的设定,包括设定游戏呈现的窗口、 FPS 显示、默认帧率上限、纹理颜色位宽等。
  • 切换当前的游戏场景,暂停或恢复游戏场景的运行。
  • 总而言之,游戏在 CCDirector 的管理下完成了呈现设定与流程控制。
  • CCDirector 扮演着全局大总管的角色,因而很自然地采用了单例的设计模式。在程序的任何地方,都以通过下面的简单代码访问到:
    *CCDirector pDirector = CCDirector::sharedDirector();

    在 CCDirector 中,我们定义了以下管理场景的方法。

  • runWithScene(CCScene* scene):启动游戏,并运行scene场景。这个方法在主程序启动时第一次启动主场景时调用。

  • replaceScene(CCScene* scene):直接使用传入的scene替换当前场景来切换画面,当前场景将被释放。这是切换场景时最常用的方法。
  • pushScene(CCScene* scene):将当前运行中的场景暂停并压入到代执行场景栈中,再将传入的scene设置为当前运行场景。
  • popScene:释放当前场景,再从代执行场景栈中弹出栈顶的场景,并将其设置为当前运行场景。如果栈为空,则直接结束应用。与pushScene成对使用,可以达到形如由主界面进入设置界面,然后回到主界面的效果。
  • pause:暂停当前运行场景中的所有计时器和动作,场景仍然会显示在屏幕上。
  • resume:恢复当前运行场景中被暂停的计时器和动作。它与 pause 配合使用。
  • end:结束场景,同时退出应用。

值得注意的一点是,以上三种切换场景的方法(replaceScene、pushScene、popScene)均是先将待切换的场景完全加载完毕后,才将当前运行的场景释放掉。所以,在新场景恰好完全加载完毕的瞬间,系统中同时存在着两个场景,这将是对内存的一个考验,若不注意的话,切换场景可能会造成内存不足。

CCScene:场景

场景只是层的容器,包含了所有需要显示的游戏元素。
对于场景而言,通常我们添加的节点就是层。先添加的层会被置于后添加的层之下。

Cocos2d-x 提供了很多华丽的场景切换特效,例如翻页、波浪、淡出淡入等。这些特效是通过派生自CCScene 的 CCTransitionScene 系列特效类来实现的。

CCLayer:层

与 CCScene 类似,层也扮演着容器的角色。然而与场景不同的是,层通常包含的是直接呈现在屏幕
上的具体内容:我们需要在层中放入精灵、文本标签或其他游戏元素;设置游戏元素的属性,如位置、方向和大小;设置游戏元素的动作等。由此可见,游戏开发的大部分编码时间都用在创建层上。

zOrder,该参数指的是 child的z轴顺序,也就是显示的先后顺序,其值越大,表示显示的位置就越靠前。zOrder 的默认值为 0。
tag 是元素的标识号码,如果为子节点设置了 tag 值,就可以在它的父节点中利用 tag 值找到它了。

CCLayer 的另一个十分重要的功能是可以接受用户输入事件,包括触摸、加速度计和键盘输入等。

CCSprite精灵

精灵则与层或场景不同,它隶属于层,是场景中出现的可见图形。玩家控制的主角、 AI 控制的 NPC,以及地图上的宝箱、石块,甚至游戏主菜单的背景图片都是精灵。因此,可以这样认为,玩家看到的一切几乎都是由精灵构成的。

精灵不一定是静态的。通常,一个精灵可以不断变化,变化的方式包括:移动、旋转、缩放、变形、显现消失、动画效果(类似 GIF 动画)等。精灵按照层次结构组合起来,并与玩家互动,构成了一个完整的游戏。

CCSprite* pSprite = CCSprite::create("HelloWorld.png");
pSprite->setPosition( ccp(size.width/2, size.height/2) );
this->addChild(pSprite, 0);
CCSprite* sprite1 = new CCSprite();
sprite1->initWithFile("HelloWorld.png");

CCNode

一切游戏元素都继承自 CCNode,因此它们都具有 CCNode 所提供的特性。
CCNode 定义了一个可绘制对象的通用特性,包括位置、缩放、是否可见、旋转角度等.

CCNode::addChild 方法:用于将一个游戏元素添加到另一个元素中。在创建一个层或者场景时,通常会初始化自己的游戏元素,定义一些特殊的效果,或是将其他的游戏元素组合到一起,而addChild方法就是用于组合游戏元素的。

一旦建立起渲染树,组织复杂的场景就变得十分简单。我们赋予每个节点一系列属性,包括节点相对于父节点的位置、旋转角度、缩放比例和变形参数等。渲染树的优势在于,我们只需要考虑节点相对于父节点的属性,就可以逐层创建复杂的对象或动作。
一个简单的例子是《捕鱼达人》中的海龟由躯干和 4 条腿构成。在游戏中,不但海龟在水中游动,它
的 4 条腿也在不断做划水的动作。这一系列动作可以分解为: 4条腿相对整个海龟在一定角度内旋转;躯干相对于整个海龟静止不动;整个海龟在鱼层中游动,位置和方向在不断改变。
因此,建立一个节点表示海龟,在海龟节点下再建立5个精灵,分别表示4条腿和躯干。这样,每个动作都是可控的,只要为每个节点设置好了动作,就可以完成复杂的动画。反之,如果没有树型结构,组织一个稍微复杂的游动都会成为一个巨大的工程。

Cocos2d 也采用了渲染树架构。任何可见的游戏元素都派生自Cocos2d-x节点(CCNode),常见的游戏元素有场景(CCScene)、层(CCLayer)和精灵(CCSprite)等。前面提到过,通常游戏按照场景、层、精灵的层次顺序组织,每种节点都有各自的特点。然而在实际开发中,为了实现一些特殊的效果,也不必拘泥于这个层次顺序。层或精灵都是普通的节点,因此,即使向精灵中添加精灵,向场景中添加精灵,甚至向精灵中添加层,这些操作也都没被禁止。

节点的最基本的功能包括:

  • 包含其他 CCNode 对象;
  • 接受各种事件与回调函数,如定时器事件;
  • 运行动作。

update 定时器

CCNode 的刷新事件 update 方法,该方法在每帧绘制之前都会被触发一次.
CNode 默认并没有启用 update 事件,为了启用定时器,我们需要调用 scheduleUpdate 方法,并重载 update 以执行自己的代码。对应地,我们可以使用 unscheduleUpdate 方法停止定时器。

schedule 定时器

this->schedule(schedule_selector(EnemyLayer::addEnemy1),0.5f);

CCNode 的 schedule 方法接受一个函数指针并启动一个定时器,利用schedule方法不同的重载可以指定触发间隔与延时。schedule_selector则是一个把指定函数转换为函数指针的宏,用于创建schedule方法所需函数指针。传入这个宏的函数应该包含一个 float 参数,表示距离前一次触发事件的时间间隔。

CCMenuItemImage 菜单

CCMenuItemImage *pCloseItem =
CCMenuItemImage::create( "CloseNormal.png", //普通状态下的图片
"CloseSelected.png", //按下状态下的图片 
this, //响应对象
menu_selector(HelloWorld::menuCloseCallback)); //响应函数 
pCloseItem->setPosition(ccp(CCDirector::sharedDirector() ->getWinSize().width - 20, 20) ); 
CCMenu* pMenu = CCMenu::create(pCloseItem, NULL); 
pMenu->setPosition( CCPointZero ); 
this->addChild(pMenu, 1);


CCAction 动作

CCAction 是动作类的基类,所有的动作都派生自这个类,它创建的一个对象代表了一个动作。动作作用于 CCNode,因此,任何一个动作都需要由 CCNode 对象来执行。

 CCSprite* sprite = CCSprite::create("fish.png");
 CCAction* action = CCMoveTo::create(1.0f, ccp(0, 0));
 sprite->runAction(action);


一个 CCAction 只能使用一次,这是因为动作对象不仅描述了动作,还保存了这个动作持续过程中不断改变
的一些中间参数。对于需要反复使用的动作对象,可以通过 copy 方法复制使用。

由 CCFiniteTimeAction 派生出的两个主要类分别是瞬时动作(CCActionInstant)和持续性动作(CCActionInterval)

瞬时动作

  • CCPlace 该动作用于将节点放置到某个指定位置,其作用与修改节点的 Position 属性相同
  • CCFlipX 和 CCFlipY 分别用于将精灵沿 X 和 Y 轴反向显示,其作用与设置精灵的 FlipX 和 FlipY - 属性-相同。
  • CCShow 和 CCHide 分别用于显示和隐藏节点,其作用与设置节点的 Visible 属性的作用一样。
  • CCCallFunc系列动作包括CCCallFunc、CCCallFuncN、CCCallFuncND,以及CCCall-FuncO四个动作,用来在动作中进行方法的调用.CCCallFunc调用的方法不包含参数,CCCallFuncN调用的方法包含一个CCNode*类型的参数,表示执行动作的对象。CCCallFuncND调用的方法包含两个参数,不仅有一个节点参数,还有一个自定义参数( CCNode*与void*)。CCCallFuncO调用的方法则只包含一个CCObject*类型的参数。

CCLabelTTF 标签

CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Arial", 24);
CCSize size = CCDirector::sharedDirector() ->getWinSize();
pLabel->setPosition( ccp(size.width / 2, size.height - 50) );
this->addChild(pLabel, 1)


动作CCAction

动作( action)作用于游戏元素,可以使游戏元素运动起来。常见的动作有移动、转动、闪烁、消失等。动作分为持续性动作与瞬时动作,持续性动作在一段时间内连续完成,瞬时动作会瞬间完成。为了使游戏画面动起来,我们会在需要的时候创建一系列动作,并把它们应用到游戏元素中。在 Cocos2d-x 中,动作由 CCAction 类实现,由 CCAction 类派生出持续性动作类 CCAction Interval 和瞬时动作类 CCActionInstant。所有的动作都派生自以上两个类之一。

动画CCAnimation

动画( animation)是一种特殊的持续性动作,它只能应用于精灵上,用于实现帧动画效果。如同电影胶片一样,一个帧动画由多张静止的图片不停地切换形成。静止的图片叫做帧(frame),帧的序列代表一个动画效果。

在 Cocos2d-x 中,我们可以使用多个帧创建帧动画序列(CCAnimation),并用帧动画序列创建可作用于精灵的帧动画(CCAnimate)。

日志 CCLog

CCLog("Hello World!");


END

Cocos2dx 跨平台使用