2.3 Cocos2d-x中的一些概念
在上一节中已经通过一个简单的例子介绍了在Cocos2d-x中项目的组成,但是我有理由相信读者心中一定是有着不少疑惑的,比如说场景是什么、层又是什么?这些概念都是在Cocos2d-x开发中必不可少的基础知识。
在介绍这些内容之前,首先要了解什么是节点(Node),它是Cocos2d-x场景图中最基本的元素,换句话说,场景图中的每一个对象都必须是节点对象或者是节点类的子类生成的对象。本节所要介绍的节点、场景、图层、精灵等都属于节点对象。
提示:这里所说的场景图与后面要介绍的场景类并不是一回事,场景图是指屏幕上一切已经显示了的或者要显示的元素的容器,可以简单地理解为屏幕,而场景则是节点的一个子类,是在屏幕中被显示出来的一个元素。在后期介绍到场景类时还将进一步分析它们的不同。
节点作为场景图中最基本的元素,可以对它的位置、大小、角度以及可见性进行设置,也可以使用addChild方法向节点中加入新的节点,如范例2-2的第07行就可以理解为是向一个场景(Scene)型的节点中加入一个图层型(Layer)子节点。
图2-4中展示出了场景类(Scene)、图层类(Layer)等与Node之间的关系,接下来的小节将依次来介绍它们。
图2-4 Node与其子类的关系
提示:在老版本的Cocos2d-x中对应的类名为CCNode、CCScene、CCLayer、CCMenu、CCSprite等,在新版本中为它们设立别名不再使用CC作为前缀,但仍可以使用旧的命名方式。
2.3.1 导演
在介绍这些概念之前,首先要了解什么是导演(Director)。它在Cocos2d-x游戏中的地位与实际电影拍摄过程中的导演是非常类似的。在拍摄电影的过程中,电影的剧情是由编剧早就写好的,而对观众来说剧情是通过一个个演员扮演出来的,在正常情况下观众在观影过程中应该不会在剧中发现导演的存在。
那么导演做什么呢?导演要做的就是指挥演员要怎样去演,指挥摄像师怎样拍摄,指挥后期制作人员剪辑视频,决定将哪一部分视频展示给观众。而在游戏过程中,游戏的剧情、脚本等是由开发人员决定的,人物要怎样行动是由玩家控制的,而这一切是通过各种节点以及子节点展示出来的。导演类的作用就是在恰当的时期选择让哪些节点显示和隐藏,比如玩家在开始菜单中单击了开始按钮之后,跳转到游戏画面就是导演在发生作用。
现在再回过头来看AppDelegate.cpp文件的applicationDidFinishLaunching方法,可以看到如范例2-5所示的代码,这就是导演类的一个应用。
【范例2-5】导演类的使用。
01 auto director = Director::getInstance(); //创建“导演” 02 auto glview = director->getOpenGLView(); //获取openGL渲染对象 03 if(!glview) 04 { 05 glview = GLViewImpl::create("My Cocos2d"); 06 glview->setFrameSize(640,360); //设置窗口尺寸 07 director->setOpenGLView(glview); //将openGL对象传递给导演 08 } 09 director->setDisplayStats(false); //不显示fps 10 director->setAnimationInterval(1.0 / 60); //设置默认fps 11 auto scene = MyWorld::createScene(); //创建场景 12 director->runWithScene(scene); //运行
通过本范例可以看出,虽然屏幕上要显示的内容是早已准备好了的(即本例中第11行的scene)但是最终却还要由导演决定是否将其渲染在屏幕上,如第12行所示。
注意:在实际拍电影时虽然可以有许多个副导演,但只能有一个能够最终做决定总管一切的正导演,在Cocos2d-x中也是如此。展示在玩家面前的人物、怪物、背景等元素都是通过节点来实现的,节点的行为则是由游戏本身的脚本以及玩家来操作的,需要有一个导演来决定哪些内容该显示,哪些内容不该显示,在必要的时候退出游戏等等。
2.3.2 节点
从图2-4中就可以看出,在Cocos2d-x中有许多种类都是继承自节点类(Node)的,这些类的一个共同特点就是它们都与Cocos2d-x中的屏幕渲染有关,即如何把内容显示在屏幕上。由此可见Node类在整个Cocos2d-x中有着何等重要的地位。
然而,作为一个根源种子类,Node类中并没有提供用来具体显示某个内容的方法,但是却提供了显示其子对象的方法和属性,参见表2-1。
表2-1 Node类中的属性
通过该表就不难了解到Node类的作用了,但是前面已经提到了Node类中并没有提供具体显示某个内容的方法,那么Node类究竟怎样实现物体的显示呢?这就不得不提到Node类及其子类的3个特点了:
❑可以包含Node类及其子类对象作为子节点,对应的方法为:addChild、getChildByTag、removeChild等。
❑每一个Node类及其子类都可以使用定时器,如schedule等。
❑每一个Node类及其子类都可以继承动作,如runAction、stioAction等。
这样就不难理解了,原来Node对象可以添加Node类的子类对象作为子节点,那么只要这些子节点是可以显示的,Node本身自然就是可以显示的了。而这就要求我们在学习Node类的子类时需要对以下3个方面加以了解:
❑对象初始化的方式和内容。
❑时间回调函数。
❑重载绘制函数。
这看起来似乎让人觉得莫名其妙,其实只要在接下来学习场景类(Scene)、图层类(Layer)等子类时仔细揣摩它们与Node类之间的区别,就不难理解了。为了能够更快地体会到这种感觉,现在就要赶紧来继续学习场景类(Scene)吧。
2.3.3 场景
被导演所持有的场景对象,是在Cocos2d-x中第一个包含游戏内容的层次,读者可以将它理解成一个容器,但是这个容器对游戏界面的管理发挥了重要的作用。它将游戏中不同的图层(Layer)组织在一起呈现给玩家一个完整的游戏画面。
在游戏中,节点是一个很大的范围,因为无论场景、图层还是精灵,从本质上来说都是节点,因为它们都是节点的子类。但是场景的范围就要相对小得多了,一般来说,场景是作为场景切换的基本单位而存在的。比如在经典的游戏《仙剑奇侠传》中,人物在迷宫中行走是一个场景,而遇到怪物进入到战斗画面又是另一个场景,这与玩家们所认识的场景的概念是完全一致的。
然而,在Cocos2d-x游戏开发中,游戏设置界面、关卡选择界面等实际上也是独立的场景,在这里将Cocos2d-x中的场景分为3类:
❑游戏内容场景:主要展示游戏中的内容如地图、怪物等。
❑选项类场景:如游戏设置界面、关卡选择界面等。
❑展示类场景:如游戏的开场动画、RPG游戏战斗结束后的胜利信息等。
下面再回过头来看一下范例2-2的第05~08行,了解在Cocos2d-x项目中一个场景是怎样被创造出来的。在之前介绍范例2-2时已经介绍过这段代码,那么现在笔者将更加深入地讲解这段代码背后的内容。
在创建场景对象时使用了create方法,这个方法会返回一个Scene类型的对象。根据本节的知识也可以知道Scene是继承自Node类的,因此也可以理解为是返回了一个节点对象。接下来,我们也可以再将第7行中创建的layer对象理解成是一个scene的子节点,当将它添加到场景中时就构成了一个完整的节点场景链接关系,当父节点被绘制时,其中的子节点也会同时被渲染在屏幕上。
提示:由此可见,当一个场景被渲染时,其中所包含的子节点也会被渲染在屏幕上,而屏幕只有一个。因此也就可以认为,在一个项目中,可以存在多个场景但是同时有且仅有一个场景是处于激活状态,即不可能同时有两个场景被显示。
既然场景(Scene)和节点(Node)都没有包含具体显示的独立内容,那么场景相对于节点多出了什么功能呢?可以将场景理解为游戏状态切换的基本单位,因此Cocos2d引擎为场景的切换提供了丰富的切换效果。由于涉及到菜单等要在之后才能介绍的内容,笔者就直接制作了一个用于展示Cocos2d场景切换效果的项目,存放在源文件本章目录下的AnotherScene项目中,其中负责切换场景的代码可以在类MyScene的menuBack方法中找到,所使用的代码如下:
Director::sharedDirector()->replaceScene( HelloWorld::createScene() );
其中HelloWorld是所要切换的场景所在的类,在切换场景时导演会自动地销毁当前场景并创建新的场景来实现切换的效果。除了以上所介绍的方法,还可以使用带有动画效果的场景切换,在类HelloWorldScene中就定义了15个用于场景切换的方法,分别对应屏幕上的15个菜单按钮(如图2-5所示),具体的实现代码类似于:
Director::sharedDirector()->replaceScene(TransitionJumpZoom::create (3.0f, MyWorld::createScene()));
其中3.0f是切换动画完成的时间,MyWorld是所要切换的场景所在的类,TransitionJumpZoom是所选用的切换动画效果,表2-2列出了Cocos2d-x中所提供的切换效果。
表2-2 Cocos2d-x中提供的场景切换效果
注意:由于在Cocos2d中的场景切换效果都是以类为单位来实现的,因此也可以将每种效果看成是一种场景切换类,它们都是继承自Transition类的。其中某些类名结尾处的T、R、B、L字母分别是top、right、bottom、left的缩写,而X、Y则分别代表横竖两个方向。
当运行该项目时将会展开如图2-5所示的场景,可以看到其中包含了15个菜单按钮,点击它们可以通过不同的动画效果切换到如图2-6所示的场景中去。再随意单击任何位置都可以采用不带动画效果的方式再切换回图2-5所示的场景中去。读者可以运行程序,自行体会各种不同的场景切换效果。
图2-5 运行项目后所展示的场景中有15个菜单按钮
图2-6 切换后的场景
2.3.4 图层
介绍完了场景接下来再看看图层类。在介绍场景时曾经提到场景是游戏状态切换的基本单位,而每一个场景都是由一个个图层组成的。可以这么说,玩家所看到的每一个场景实际上都是由若干个图层组合而成的。
比如说在游戏《植物大战僵尸》中,要让僵尸在草地上行走,就起码要用两层:一层用来显示背景的草地,然后在草地上面再加入一层用来显示“僵尸”行走的动画。但是在实际开发中至少要分成三层,因为还要留出一层用来显示分数、消息提示等功能。
提示:之所以要分层是因为在2D游戏中物体之间的遮罩关系是一个比较难以把握的问题,而引入了分层之后就让这一切变得简单了许多。比如在像《梦幻西游》这样的2.5D游戏中,人物在地图中行走时会被一些建筑遮挡,但是在某些位置又会遮挡住建筑,如果仅使用一层无疑需要非常复杂的算法,而如果使用多个层的话则会变得非常简单(具体的实现方法将会在本书第6章介绍)。
下面再来说说图层类在Cocos2d-x开发中究竟要完成哪些任务,主要有以下3类:
❑接收用户操作,比如单击、触摸屏幕等。
❑作为显示内容的容器,比如在图层中显示游戏角色、选项、文字等内容。
❑作为游戏背景使用。
也就是说,相对于场景,图层类增加了对用户交互的处理,并且使多个图层可以在一个场景中共存。此外还需要记住,任何一个图层都必须要加入到场景中才能发生作用,因为Cocos2d是以场景为单位对屏幕进行渲染的,而图层的作用是向用户展示内容,所以脱离了场景的图层是没有意义的。
在本书的后续章节中读者有机会更深入地体会图层的作用,而目前由于知识限制就先介绍这么多。
2.3.5 精灵
终于介绍到精灵了,这才是在Cocos2d-x中玩家所能够看到的最基本的单位。那什么是精灵呢?这与自然世界中物质的定义有些相似。在自然界中,一块石头是一个物质、一块砖头是一个物质、一个大楼是一个物质、一个分子还是一个物质。在游戏中也是类似的、一个妖怪是一个精灵、一件装备是一个精灵、从飞机上发射出来的一个子弹还是一个精灵。
提示:实际上,上面这句话是不太准确的,应该说是一个妖怪、一件装备、一颗子弹都包含了一个精灵。就拿一个妖怪来说吧,实际上在游戏中的妖怪还包括了妖怪的技能、血量、魔法等数据,而妖怪的精灵则仅仅是玩家所能看到的妖怪的图像。但是一般情况下为了方便,说一个妖怪就是游戏中的一个精灵也是可以的。
接下来笔者将结合上一节中图层有关的知识,给出一个使用精灵的例子,可以在源文件本章目录下的SpriteTest项目中查看完整代码。
【范例2-6】在Cocos2d-x中使用精灵。
01 auto *layer1 = Layer::create(); //创建第一层,也是最下方的一层,背景层 02 auto *pBackground = Sprite::create("background.png"); //创建精灵,显示背景图像 03 pBackground->setPosition(180,320); //使背景在屏幕中央显示 04 layer1->addChild(pBackground); //将背景加入到图层中去 05 //创建第二层,用来显示飞机、子弹等内容 06 auto *layer2 = Layer::create(); //第二层 07 auto *pPlane = Sprite::create("plane.png"); //创建精灵,显示飞机图案 08 pPlane->setScale(0.5f); //设置飞机大小 09 pPlane->setPosition(180,80); //设置飞机显示的位置 10 layer2->addChild(pPlane); //将飞机加入到图层中 11 //创建第三层,用来显示游戏分数等内容 12 auto *layer3 = Layer::create(); 13 auto *number = LabelTTF::create("13400","Arial",32); //第三层,在最上方显示 14 number->setPosition(300,600); //设置游戏显示的位置 15 layer3->addChild(number); //将分数在图层中显示 16 //将图层加入到场景中去 17 this->addChild(layer1); //先加入到场景中的图层在底部显示 18 this->addChild(layer2); 19 this->addChild(layer3);
运行后的结果如图2-7所示。当然也可以适当地修改第09行处的代码,来确认游戏分数恰好可以遮盖住飞机以证明图层之间的关系。方法是将第09行中setPosition方法中的参数修改为300和600就可以了。
图2-7 利用精灵和图层显示出飞机在天空飞行
通过本例读者应该了解,可以通过向图层中加入精灵来显示游戏内容,当然也可以加入菜单或者标签等元素(如第13行创建的就是一个标签),然后通过将层次对象添加到场景中来使它们显示出来,如第17、18行所示。
本例第09和14行都使用了setPosition方法来改变节点的位置(精灵和标签都可以看作是节点),实际上层次也是可以当作节点的,可是这里为了使坐标统一通常不会这样使用。
提示:事实上,如果像本例这样仅仅是在屏幕上显示内容而不需要考虑触摸等交互的话,可以直接在场景中加入精灵。这里主要有两个原因,首先是由于层次相对于场景最大的变化就是加入了对交互的处理,因此如果不需要交互对层次就没有太大的需求了;其次是连交互都没有,层次间的逻辑也不会太复杂,使用场景+精灵的组合就完全可以应付了。
此外,在第08行还使用了setScale方法来设置精灵的大小,这与setPosition方法一样都是从节点类继承下来的,也是精灵常用的操作之一。
除此之外,还有菜单、粒子系统等常见的类,不过本章这里就不再介绍了,后面对于其他知识的介绍中再慢慢讲解吧。