Cocos2d-x入门指南
Cocos入门-Cocos2d-x入门
Cocos2d-x简介:
全球占有率第一的手机游戏引擎, 基于MIT开源协议,OpenGL ES的免费跨平台开源引擎, 开发者可以使用C++、Lua和JavaScript来进行跨平台游戏的制作,覆盖世界上所有主流的操作系统包括iOS,Android,WP,Windows,Mac, JavaScript开发的项目还可以在浏览器运行,同时借助runtime技术,浏览器运行效率能够达到与平台运行效率一致,目前在Qzone平台,是使用率最高的JS游戏引擎。平台兼容性达到99.37%, 被开发者誉为“千万级手游摇篮”。
本教程将分为三篇,第一篇先使用C++语言介绍Cocos2d-x的基础部分,后两篇将带您入门Cocos2d-lua、Cocos2d-js,您可以根据自己需求选择其中一篇。
从学习难度、开发效率、项目发布之后的后期维护、成本等方面来看,个人推荐优先考虑Cocos2d-Lua/JS来开发游戏,因为对游戏设计来说,我们花费精力最多的应该是游戏的设计、调整、逻辑这类对性能要求不高的部分,对性能要求高的部分已经由Cocos2d-x使用C++实现了,若真的遇到对计算性能要求很高的模块,再考虑使用C++来重写这个模块,针对该模块进行优化。
使用IDE打开项目:
首先我们使用Cocos创建一个C++语言的项目,打开发布与打包页面(本文基于Cocos Framework3.6版本):
将发布行为修改为发布到Vitual Studio/XCode(可能需要您安装Visual Studio/XCode,本篇文章如无特别说明均使用Visual Studio进行讲解),点击确定:
接着会提示您是否使用Visual Studio打开项目,选择是,自动使用Visual Studio打开项目:
编译运行:
在Visual Studio打开项目之后,按下F7编译项目,然后按F5运行,无意外的话能够看到显示着Cocos2d-x椰子头形象的窗口:
基础篇:
我们的游戏从哪里开始启动?
在解决方案管理器下查看我们创建的项目的目录结构,如下所示,这里我们仅关注src目录:
这四个文件包含了AppDelegate和HelloWorld两个类。
其中HelloWorld是Cocos为我们创建的一个界面。AppDelegate负责将应用程序生命周期中一些特殊事件回调给我们,让我们有机会对这些特殊事件进行处理,但目前我们能够处理的事件只有三种:
l applicationDidFinishLaunching :应用程序加载完成。这里也可以认为是Cocos2d-x游戏框架的入口,从这里开始初始化我们的Cocos2d-x游戏框架并启动运行。
l applicationDidEnterBackground :应用程序被隐藏到后台。
l applicationWillEnterForeground :应用程序即将回到前台运行。
看看applicationDidFinishLaunching 的源码:
bool AppDelegate::applicationDidFinishLaunching() {
// 初始化 director
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if (!glview) {//设置桌面平台上的设备分辨率
glview = GLViewImpl::createWithRect("Example3", Rect(0, 0, 960, 640));
director->setOpenGLView(glview);
}
//设置游戏界面的设计分辨率
director->getOpenGLView()->setDesignResolutionSize(960, 640, ResolutionPolicy::SHOW_ALL);
// 设置是否显示渲染性能信息,即左下角的那几行字
director->setDisplayStats(true);
// 修改游戏的帧率倒数值。即每秒更新多少次界面
director->setAnimationInterval(1.0 / 60);
//将res添加到搜索路径,此后程序寻找资源时会尝试搜索这个子目录
FileUtils::getInstance()->addSearchPath("res");
// 创建一个场景
auto scene = HelloWorld::createScene();
// 运行场景
director->runWithScene(scene);
return true;
}
这里先对游戏项目进行一些配置,然后调用HelloWorld的createScene函数创建一个Scene交给Director运行。
这里的Scene是工程给我提供的一个初始界面,一般我们不需要这个东西。我们可以替换掉这里的Scene对象的创建,然后开始编码我们的程序。
如改为直接创建一个Scene对象:
// 创建一个Scene
//auto scene = HelloWorld::createScene();
auto scene = cocos2d::Scene::create();
// run
director->runWithScene(scene);
再运行,此时界面上空空如也,只剩下一些调试用的性能信息(可以将director->setDisplayStats改为false来关闭这些信息):
看到这里您可能有点疑惑,Scene是什么呢?别着急,接下来为您介绍。
Cocos2d-x界面的基本结构:
Cocos2d-x的界面基本结构如下:
这幅图的意思呢,就是我们界面上一般只有一个Scene,然后Scene上可以有很多很多个Node,每个不同的Node下又可以有很多很多个Node,循环往复。。。
您可能在其他地方见过这样类似的设计,这种设计模式称为组合模式,其优势是:(以下摘自W3CSchool)
l 它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
Cocos2d-x使用组合模式来维护界面对象之间的关系,界面对象最终放置在一个以Scene为根节点的树形结构中,可以认为Scene就是用于装载显示页面的一个容器。而在Cocos2d-x框架下任意可以显示到界面上的对象都是继承于Node的,如果我们希望将某个对象显示到界面上,那么将对象以某种方式挂到这个界面树上即可。
PS:可能您有接触过其他教程,有些教程会说Scene下再使用Layer类型来组织界面,这可能是个好习惯,但并不是强制的。实际layer的作用在cocos2d-x 3.0开始被淡化了,监听各种输入事件不必再依赖Layer,我们可以直接创建对应的Listener来做这部分工作,这样更简单。
往界面上添加点东西:
现在我们来显示点东西,比如这样一张图:
首先需要将这个图片文件放到我们的程序能够找到的位置,对于C++项目,这个位置是在项目下的Resource文件夹,Cocos2d-x创建的不同语言的项目有所不同,lua和js语言项目默认为项目根目录。注意除了这个路径之外,我们还可以放在Resource/res这个路径,因为在applicationDidFinishLaunching中使用了FileUtil添加了一个搜索路径:
FileUtils::getInstance()->addSearchPath("res");
这样Cocos2d-x框架寻找文件时就会去搜索res路径。注意这个寻找过程是通过组合各个搜索路径的方式来搜索的,这意味着路径越多效率会越低,基于性能考虑这个路径应当越少越好。
现在先将这张图片放置到Resource里边,并命名为“img1.png”。
然后在刚才创建Scene的地方编写如下代码:
//创建一个精灵,并添加到scene节点上
auto spriteObj = cocos2d::Sprite::create("img1.png");//使用create工厂方法创建一个精灵
scene->addChild(spriteObj);//使用addChild这个API为scene添加一个spriteObj
这里使用一个精灵(Sprite)对象来加载这张图片,并添加到Scene上。
编译运行,可以看到我们刚刚添加的图片羞涩地躲在了左下角:
设置节点的属性:
将所有要显示的东西都丢在左下角肯定不是我们想要的,我们可以使用setPosition这个接口将对象放到其他地方,当然还可以修改对象的旋转属性、颜色值等等,作为对比,我们另外创建一个对象进行设置:
auto spriteObj2 = cocos2d::Sprite::create("img1.png");
spriteObj2->setPosition(cocos2d::Vec2(480, 320));//设置坐标为480,320
spriteObj2->setRotation(45);//旋转45度
spriteObj2->setColor(cocos2d::Color3B::YELLOW);//设置颜色为黄色
scene->addChild(spriteObj2);//添加到场景上
再次编译运行:
当然对于强大的Cocos2d-x来说肯定不会只有这么几个属性可以用,您可以到这里搜索您使用的Cocos2d-x对象的更多API接口及其用法:http://api.cocos.com/cn/ ,这个是Cocos团队维护的API文档,三个不同引擎的API信息都可以在这里找到。
记得添加到收藏夹哦!
关于内存管理:
细心的您可能已经发现,目前为止我们创建对象使用的都是对象的create静态方法。您可以认为这个方法就是帮您申请一个会自动释放的对象,当这个对象不再被使用的时候他就会被立即释放清理其所占用的内存(Cocos2d-html5无此问题),此时您就要小心,使用被释放的对象会导致程序崩溃;这是C++语言的坏处——需要使用者维护对象的生命周期,但也是其优点——高性能。
那么如何管理呢?
Cocos2d-x使用的是引用计数的方式来管理对象的生命周期,即在对象的内部有一个计数器,当外部对象引用他时将这个计数器加1(使用retain接口),释放时减1(使用release接口)。当计数器值减至零时销毁对象。另外有时我们会希望释放某个对象的一次引用但却不希望导致对象立即销毁(比如在create工厂方法中)这时可以调用对象的autorelease接口,此后对象就会在当前帧结束之后自动释放一次引用,引用计数为零时还会引起销毁。
实际上Cocos2d-x提供的内存管理机制十分方便有效,对于使用者来说只有少数情况需要直接接触到内存管理相关的问题。必须使用时我们只需要遵循两个原则即可:
1.谁引用谁释放。
2.释放引用成对出现,比如你为了执行某些操作引用了一次对象,那就必须操作完成之后释放对象。比较常见的是将对象从界面树上的一个节点移动到另外一个节点上,因为从界面树上移除节点很大可能会导致对象释放,通常我们会这么做:
//假定有三个节点,A、B、C,其中A是B的子节点,现在我们想要将A迁移到C上:
A->retain();//A很可能只被界面树结构引用,移除下来前需要保证移除后A有其他引用
A->removeFromParentAndCleanup(false);//移除
C->addChild(A);//放到界面树上。
A->release();//完事
//好吧没B什么事
PS:这里有一点需要注意,移除对象本身我使用的是removeFromParentAndCleanup这个接口,他带有一个bool类型的参数,当这个参数为True的时候,他会帮我们清理节点上的action动画和回调等数据。另外一个接口removeFromParent则总是会清理这些数据,注意根据需要选择接口。
显示点文字:
我们可以用Label对象来显示文字:
auto labelObj = cocos2d::Label::create();//创建一个文本对象
labelObj->setString("I am a bulb");//设置文字
labelObj->setSystemFontSize(36);//设置字体大小
spriteObj2->addChild(labelObj);//添加到spriteObj2上
编译运行:
可以看到我们添加的几个文字显示到界面上了。
同时,受父节点的影响,文本也跟着出现在屏幕中间并发生了一定角度的偏转。为什么会出现这样的现象呢?这里有三个点需要说明:
1.坐标系
2.锚点
3.并不是所有属性都会影响子节点(看清楚啦,文本没灯泡那么黄!)
关于坐标系:
对于Cocos2d-x框架,每一个节点对象,包括Scene在内,都有自己的坐标系,且任意对象的坐标系都是从对象的左下角开始:
(为了更具体的标识对象的坐标系及讲解接下来的锚点功能,我为对象套上了一层颜色层)
同时对象的旋转、倾斜、缩放等会改变、扭曲对象外形的属性也会使得对象的坐标系受到相应的影响。
关于锚点:
我们没有给label对象进行任何坐标相关的设置,但上图中label对象的原点并不与灯泡图片的原点重叠。因为决定对象在父节点坐标系位置的除了Position坐标属性还有AnchorPoint锚点属性,你可以把对象想象成一张纸,如果我们用一个图钉将这张纸钉在桌面上,图钉钉住的位置就是锚点,这里桌面就是对象的父节点。当对象旋转、缩放拉伸、倾斜时,它的锚点的位置总是相对父节点不变的。
上图中文本对象是以它的中心来与父节点对其的,此时它的锚点为一个(0.5,0.5)的Vec2对象。锚点采用比率值来表示,其值为(0.5,0.5)时,锚点的位置在对象的(宽度*0.5,高度*0.5)位置上。
PS:
1.锚点不影响对象的原点,即原点总是在对象左下角。
2.锚点可以大于1或者小于0,此时锚点不在对象上。
部分属性不会影响子节点:
开始的时候,我除了给灯泡对象设置坐标、旋转之外,还设置了颜色值,受此影响后来添加的文本对象在没有设置任何属性的情况下也跟着出现在了屏幕中心并跟着旋转了45度。但是文本的颜色值却没有受影响。
因为如果父节点的颜色值影响了子节点,那么子节点将没有任何手段来修正这种影响,即有些效果做不了了,为了保持最大的灵活性,Cocos2d-x允许配置颜色值是否影响子节点。
除了颜色之外,透明度也是如此。
既然说允许配置,那么我们也可以让颜色值和透明度影响子节点。使用如下两个接口即可:
virtual void setCascadeColorEnabled(bool cascadeColorEnabled);
virtual void setCascadeOpacityEnabled(bool cascadeOpacityEnabled);
当我们调用以上接口并传入true时对象的颜色/透明度就能够影响子节点了(false就不影响了),如调用上述灯泡对象的setCascadeColorEnabled接口:
spriteObj2->setCascadeColorEnabled(true);
可以看到文本对象也跟着变成黄色了:
另外需要注意这个属性只会使当前节点的颜色/透明度影响子节点,如果你希望一层一层影响下去,那么需要逐层开启这个属性:
谁在前面——关于深度值:
当我们将几个兄弟关系的节点(拥有相同的父节点)放置到一块的时候,对象会按添加顺序相互遮挡:
我们按从左到右的顺序添加如上三个节点,可以发现晚添加的节点会盖在早添加的节点上。决定这个遮挡关系的是渲染顺序,晚渲染的图片会盖在早渲染图片的上边,就好像往您客厅的墙上贴纸一样,如果贴的两张纸有重叠,那么晚贴上去的纸就会盖在此前贴的纸上。
当然您也可以通过深度值属性,在图片贴到界面上之后再去控制他们的遮挡关系。在Cocos2d-x中有两种深度值属性,分别是LocalZOrder和GlobalZOrder,这两个值较大对象会比较小的对象渲染的晚,即深度值大的会盖住深度值小的对象。
对于LocalZOrder:
初始状态下,对象的LocalZOrder值为0。LocalZOrder用来决定兄弟节点之间的遮挡关系,这个属性会影响到对象的子节点。
我们将上图中间的对象LocalZOrder设置大一点
spriteObj2->setLocalZOrder(1);
这个对象就贴到其他对象的上边了:
我们再给上述对象添加一个子节点,可以发现子节点跟随父节点将灯泡遮挡住:
在深度值相同的情况下深度值的设置顺序也会影响他们的遮挡关系,比如在执行完上边的设置之后,再将灯泡的深度值设置成跟spriteObj2一样:
spriteObj1->setLocalZOrder(1);
可以发现虽然深度值相同,但后设置的灯泡跑到上边来了:
对于GlobalZOrder:
使用GlobalZOrder来设置深度可以突破界面树的限制,这个属性不传递给子节点,也就是说我们能够修改父子节点的渲染顺序。
比如,我们在上边添加了子节点的例子上做点修改,将该节点的GlobalZOrder设置为1:
spriteObj2->setGlobalZOrder(1);
这样,spriteObj2能够遮挡住所有GlobalZOrder小于1的节点了(包括自己的自子节点):
深度值小结:
通过以上说明,我们知道能够影响对象遮挡关系的有GlobalZOrder、LocalZOrder、节点的父子关系、LocalZOrder的设置顺序、对象的添加顺序五个条件。他们的优先级关系是:
GlobalZOrder>父子关系>LocalZOrder>LocalZOrder的设置顺序/对象的添加顺序。
即决定两个对象渲染的先后顺序的方法是首先比较他们的GlobalZOrder,不相等时先渲染GlobalZOrder值小的对象,当GlobalZOrder值相同时,使用父子关系来决定渲染顺序,若两个对象属于同一对象的子节点则尝试使用LocalZOrder,若还是相等就使用深度值的设置顺序来决定,这个就不可能相同了。
动画篇:
在程序世界里,动画其实就是每隔一小段时间修改一下对象的属性,让对象的位置、形态从一个状态慢慢地变化到另一个状态的过程。虽然每次修改都是瞬时的,但因为人类强大的脑补能力,这种多次连贯的瞬时变化就被理解为动画了。
比如在1秒内,每隔0.1秒将一个对象的X轴坐标值增加10像素:
是不是感觉得到这个灯泡移动起来了?
但我们会明显感到一卡一卡的,这是因为这个修改属性的时间间隔太长了,我们改成一秒变化60次,每次3像素,再看看效果:
效果好多了。
为了使动画达到更好的效果,我们可以改成每秒变化80次、100次、1024次……但基于资源、性能、效率等方面的考虑,这个值并不是越高越好,那么多少合适呢?经过前人大量探索、尝试、对比,他们给出的答案是每秒变化60次,即在这个频率下,已经可以达到非常好的动画效果了。
当然对于开发者来说决定权在自己手上,我们可以根据自己的需求来修改这个值,比如老板觉得耗电太大了或者他想用某米煎鸡蛋了。。。
在Cocos2d-x框架下,我们也需要配置一个类似的更新频率,这个频率指示了整个游戏界面每秒最多更新多少次(这决定了所有动画的刷新频率上限)。C++语言下,这个值的设定一般放在applicationDidFinishLaunching函数中,调用director导演类的setAnimationInterval接口进行设定,注意传递的参数是帧率的倒数,如1/60表示60帧每秒。
Cocos2d-x提供的动画机制:
以上讲的是动画的原理,接下来我们讲讲Cocos2d-x为我们提供的动画机制:Action。
Action分为有限时间的和其他特殊类型的动画。
有限时间类型的动画指的是能够在有限时间内运行完成的动作。他又分为持续型(ActionInterval)和瞬时型(ActionInstant)两种。
关于持续型动画:
持续型即这个动画会在一段时间内持续地更新对象的属性,达到动画的效果。更新频率与setAnimationInterval设置的一致。
比如我们希望将某个在坐标(100,100)位置处的对象在1秒的时间里一点一点移动到(280,100)位置处,那么我们需要一个能够帮我们将对象缓慢移动到某处的东西,Cocos2d-x为我们提供了MoveTo这个Action来实现这个功能,具体代码:
//创建一个对象,放置在坐标(100,100)处
auto spriteObj = cocos2d::Sprite::create("img3.png");
spriteObj->setPosition(100, 100);
scene->addChild(spriteObj);
//创建一个moveTo的Action交给上述对象执行
auto moveAct= cocos2d::MoveTo::create(1,Vec2(280, 100));
spriteObj->runAction(moveAct);//调用runAction来执行Action
运行,可以发现效果跟上边一样:
上述使用的MoveTo动作可以帮我们将某个对象移动到指定坐标,他不需要知道我们的对象的起始坐标,我们只需要将目的坐标和持续时间给他,他就会自动在执行过程中计算每一帧的位置信息并将对象设置到那个位置。
除了MoveTo之外,Cocos2d-x提供的MoveBy也可以实现跟上边一样的效果,不同的是这个Action需要的参数是偏移值,即我们期望将对象移动到偏离原来的位置多少的地方。
如我们将上述代码替换为MoveBy来实现:
auto moveAct= cocos2d::MoveBy::create(1,Vec2(180, 0));
spriteObj->runAction(moveAct);//调用runAction来执行Action
除了上述MoveTo和MoveBy函数外还有不少其他属性的持续动作,如RotateTo和RotateBy、ScaleTo和ScaleBy、SkewTo和SkewBy等等。
后缀为To和By的区别是:后缀To的Action都可以实现在某一段时间里持续地将对象的某个属性改变到某个值;带By则是持续地修改对象的属性到跟原来偏移特定值的功能,By类型还能够获取他的反向动作。
更多的Action类型请去Cocos的API文档查阅,这里就不赘述了:
http://api.cocos.com/cn/dd/db0/classcocos2d_1_1_action_interval.html
其他:
1.reverse获取相反动作:
继承于ActionInterval类型的对象都会有一个reverse的接口,这个接口可以获取一个跟原来动作相反的action,但基本上只有后缀带By的偏移类型动作实现了这个接口:
//伪代码:假设有A这么一个挂载在界面树上的Node对象以及一个Act的Action
auto reAct = Act->reverse();
A->runAction(reAct);
2.同一个动作不允许被多个对象执行:
Cocos2d-x的Action机制并不支持同一个Action动作被多个Node节点使用,如下面的代码是错误的。
//伪代码:假设有A、B两个挂载界面树上的Node对象以及一个Act的Action
A->runAction(Act);
B->runAction(Act);//错误,程序崩溃!同一个Action不允许被多个对象执行
但绝大多数的Action都提供了clone接口,我可以利用这个接口复制一个一模一样的Action出来:
//伪代码:假设有A、B两个挂载界面树上的Node对象以及一个Act的Action
A->runAction(Act);
B->runAction(Act.clone());//正确
缓动函数:
缓动函数是Action机制提供的,用于包装其他Action对象,能够改变其他Action动作运行过程的速度变化的一种Action。这种Action的名称以Ease开头。
用法示例:
//创建一个对象,放置在坐标(100,100)处
auto spriteObj = cocos2d::Sprite::create("img3.png");
spriteObj->setPosition(100, 100);
scene->addChild(spriteObj);
//创建一个moveTo的Action交给上述对象执行
auto moveAct = cocos2d::MoveBy::create(1, Vec2(180, 0));
//使用EaseOut包装移动动作,EaseOut可以实现慢慢加速的效果。
auto easeOut = cocos2d::EaseOut::create(moveAct, 0.5f);
spriteObj->runAction(easeOut);//执行Action
//另外添加一个对象来执行未包装Ease的动作做对比:
auto spriteObj1 = cocos2d::Sprite::create("img2.png");
spriteObj1->setPosition(100, 200);
scene->addChild(spriteObj1);
spriteObj1->runAction(moveAct->clone());
运行效果如上,这里使用了EaseOut,可以实现缓慢加速的效果。
EaseOut的create函数有两个参数,1是期望包装的ActionInterval(持续型动作)类型对象,2是加速度变化比率,每个Ease对象的构造函数都有点差别,这里不做赘述,有需要的话请去查阅API文档,或者转到这些对象的定义处,每个类的上方都有详细的注释说明这个类的具体信息。
循环动作:
循环播放某些动画也是一个很常见的需求,比如一个一直旋转的小按钮,不停卖萌的猫猫等。Cocos2d-x提供了Repeat和RepeatForever两个动作来帮助我们解决这个需求。
Repeat接收一个动作,并可以指定循环的次数,RepeatForever则仅接收一个动作,然后一直重复的执行直到对象销毁或者我们通过调用对象的stopAction等方式主动停止动作为止。
RepeatForever是继承于FiniteTimeAction下唯一不能预测执行时间的动作(Duration总是为0)。
比如让一个对象一直旋转:
//创建一个对象,放置在坐标(100,100)处
auto spriteObj = cocos2d::Sprite::create("img3.png");
spriteObj->setPosition(100, 100);
scene->addChild(spriteObj);
//创建一个旋转动作
auto rotateAct = cocos2d::RotateTo::create(1, 360);
//使用RepeatForever实现无限旋转的效果
auto repeatAct = cocos2d::RepeatForever::create(rotateAct );
spriteObj->runAction(repeatAct);//执行Action
效果就是界面上有一个不停旋转的图,单纯的GIF图片无法演示,同学们自己试试写一个吧。
动作的组合:
顺序地执行多个动作:
很多时候我们会希望在某个动作执行完毕后再执行一个其他的动作,比如希望对象先执行移动,然后旋转,再移动到其他地方。
Cocos2d-x提供了Sequence这个Action来帮助我们解决这个需求,他的create函数接收多个action对象,在运行时帮我们按传入顺序执行这些Actions。举个例子,比如让一个坐标为(100,100)的对象移动到(200,200),然后旋转。
//创建一个对象,放置在坐标(100,100)处
auto spriteObj = cocos2d::Sprite::create("img3.png");
spriteObj->setPosition(100, 100);
scene->addChild(spriteObj);
//分别创建一个moveTo和RotateTo的Action
auto moveAct = cocos2d::MoveTo::create(1, Vec2(200, 200));
auto rotateAct = cocos2d::RotateTo::create(1, 360);
//交由Sequence的组合成序列动作
auto sequenceAct = cocos2d::Sequence::create(moveAct1, moveAct2, nullptr)
spriteObj->runAction(sequenceAct);//执行Action
注意最后一个参数是nullptr,sequence的create函数能够接受不定个数的action,不过最后一个参数需要以nullptr结尾,即我们能够利用这个函数顺序执行无穷多个Action(实际会受限于机器性能)。
同时执行多个动作:
Cocos2d-x提供了Spawn动作来支持同时执行多个动作,他的构造函数跟Sequence相同,比如同时执行旋转和移动动作:
//创建一个对象,放置在坐标(100,100)处
auto spriteObj = cocos2d::Sprite::create("img3.png");
spriteObj->setPosition(100, 100);
scene->addChild(spriteObj);
//分别创建一个moveTo和RotateTo的Action
auto moveAct = cocos2d::MoveTo::create(1, Vec2(200, 200));
auto rotateAct = cocos2d::RotateTo::create(0.5, 360);
//交由Spawn的组合成并行动作
auto spawnAct = cocos2d::Spawn::create(moveAct1, moveAct2, nullptr)
spriteObj->runAction(spawnAct);//执行Action
注意这里设置的两个动作的持续时间是不一样的,这样的情况下Spawn会同时执行这些动作,并以时间最长的那个作为自己的持续时间,在跟Sequence配合的时候需要需要注意这点。
PS:
Sequence和Spawn也是Action类型,我们也可以任意组合这两者来构造更复杂的动作。
瞬时型动作:
瞬时型动作即瞬间完成的动作类型。可以帮我们将程序的坐标、翻转、等等的属性设置为某个特定值,从界面树上移除当前对象,执行某个函数等行为。
一般来说即时型动作都是与Sequence动作配合使用。
举个栗子,使用MoveBy、repeatForever搭配FlipX瞬时动作实现一个来回走动的小怪物:
//创建一个moveTo的Action交给上述对象执行
auto moveAct = cocos2d::MoveBy::create(1, Vec2(100, 0));
auto flipAct = cocos2d::FlipX::create(false);
auto sequence = cocos2d::Sequence::create(moveAct, flipAct, moveAct->reverse(), flipAct->reverse(), nullptr);//FlipX也支持reverse
auto repeat = cocos2d::Repeat::create(sequence,3);
spriteObj->runAction(repeat);//执行Action
交互篇:
交互篇介绍处理玩家的输入事件的方法。在手机上,我们能够监听到的输入一般有触摸消息、按键消息以及重力感应传感器的变化信息。Cocos2d-x框架封装提供了EventListener机制,我们可以十分方便的利用这个机制来监听这些变化信息。
我们以单点触摸消息来简单介绍下EventListener机制的用法:
Coocs2d-x提供了两个不同的EventListener来帮助我们处理玩家的触屏消息,他们分别是EventListenerTouchOneByOne和EventListenerTouchAllAtOnce。两者的区别是使用OneByOne我们每次只能得到一个触摸点的信息,不适合处理多点触摸,但处理单点消息简单方便。如果使用AllAtOnce类型则每次都可以得到所有的触摸点信息,适合在需要处理多点触摸操作的场景下使用。
EventListenerTouchOneByOne具体使用方法:
首先我们需要创建一个EventListener,然后对我们希望处理交互事件回调函数赋值,方便起见,这里我使用C++11的lambda来编写回调函数:
auto eventListener = cocos2d::EventListenerTouchOneByOne::create();
eventListener->onTouchBegan = [](cocos2d::Touch *touch, cocos2d::Event *event){
// do something
return true;//如果我们希望处理这一次完整的触摸过程,那么返回true。
}
//获取导演类的EventDispatcher对象,将eventLitener添加上去,此后就能接受到所有触摸消息了
director->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener,scene);
我们将相关的监听函数赋值给Listener,待会触发对应事件的时候,监听函数就会被调用,并传回触摸点信息及事件的具体信息。这里的onTouchBegan在用户触摸到屏幕时回调,我们处理完这个回调后需要返回一个布尔值,如果我们对这一次触摸过程(一次触摸指从用户的一个手指接触屏幕到这一手指离开屏幕这一过程)感兴趣,则返回true,否则返回false。
如果返回false,那么这次触摸接下来的所有事件我们就收不到了。
比如我们写个拖拽操作:
//添加一个精灵作为拖拽的对象
auto spriteObj = cocos2d::Sprite::create("img1.png");
spriteObj->setPosition(100, 100);
scene->addChild(spriteObj);
//创建触摸时间监听对象:
auto eventListener = cocos2d::EventListenerTouchOneByOne::create();
//配置监听函数
eventListener->onTouchBegan = [=](cocos2d::Touch *touch, cocos2d::Event *event){
//使用Touch对象的getLocation获取当前触摸事件的具体坐标,并转换到精灵的坐标系中
auto position = spriteObj->convertToNodeSpace(touch->getLocation());
//生成精灵对象的矩形范围
auto size = spriteObj->getContentSize();
cocos2d::Rect rect(0, 0, size.width, size.height);
//返回当前触摸位置是否在矩形范围中
return rect.containsPoint(position);
};
eventListener->onTouchMoved = [=](cocos2d::Touch *touch, cocos2d::Event *event){
//获取先前触摸事件的位置,计算偏移值
auto offset = touch->getLocation() - touch->getPreviousLocation();//position类型重载了各种算数运算符,我们可以直接用加减乘除进行运算
//将根据偏移值移动对象
auto pos = spriteObj->getPosition();
spriteObj->setPosition(pos + offset);
};
//添加到导演类的EventDispatcher:
director->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener,scene);
注意使用EventDispatcher添加监听对象时,我们需要还传入另外一个Node类型的参数:
director->getEventDispatcher()->addEventListenerWithSceneGraphPriority(eventListener,scene);
EventDispatcher类除了addEventListenerWithSceneGraphPriority接口可以添加监听对象,还有addEventListenerWithFixedPriority。
这两个接口的区别涉及一个优先级的概念,addEventListenerWithSceneGraphPriority利用传入的Node节点的深度值来决定优先级,传入的Node对象深度值越高的Listener的优先级也越高,每次派发事件之前,EventDispatcher会根据使用这个接口传入的Node的深度信息对listener排序,然后再进行派发,也就是说在传入node节点之后,如果改变了node的深度值,listener的优先级也会跟着变化。addEventListenerWithFixedPriority则直接使用一个int值来决定优先级,int值越小优先级越高,使用上一个接口添加的listener相当于这个接口的0。
声音:
在大多数平台上,Cocos2d-x通过调用平台的API来播放背景音乐和音效,并通过封装,给我们提供了一致的接口。但不同平台所支持的音频文件格式是不同的,具体如下表所示:
Android | mp3, mid, oggg, wav | 可以播放android.media.MediaPlayer所支持的所有格式 |
iOS | aac, caf, mp3, m4a, wav | 可以播放AVAudioPlayer所支持的所有格式 |
Windows | mid, mp3, wav | 无 |
那么如何播放一个音乐文件呢?
首先在C++下我们需要先引用AudioEngine的头文件:
#include "audio/include/AudioEngine.h"
然后在合适的地方使用AudioEngine的play2d接口进行播放:
//AudioEngine在experimental中
auto audioId = cocos2d::experimental::AudioEngine::play2d(audio_filepath);
play2d后边还有loop和volume、profile三个参数,分别代表循环、音量和音效文件的详细信息。
在使用完播放接口之后,我们可能还需要在去控制所播放的音效。比如控制音量:
//需要传入播放音效后得到的id值,以标识我们所控制的是哪个音效。
AudioEngine::setVolume(audioId,0.5);
具体的更多接口请去Cocos官方API文档查询:
http://api.cocos.com/cn/d0/d75/classcocos2d_1_1experimental_1_1_audio_engine.html
PS:
使用完音乐文件之后记得在恰当的时机调用uncache释放其所占用的内存。
非常重要的PS:
在不需要音效引擎时,需调用end接口以释放其所占用的资源。否则可能造成程序退出后的内存泄漏。一般将这个调用放置在AppDelegate的析构函数中。
AudioEngine::end()