在本章之前我们可能已经学习了一些关于Cocos2d-x的知识,但是你想知道更多如何使用Cocos2d-x来制作出自己梦想的游戏。没问题,让我们从现在开始,Let‘s go!
Cocos2d-x是一个跨平台的游戏引擎。什么是游戏引擎?现在不要被这个问题吓到!
一个游戏引擎就是一个可以提供大多游戏常用功能的软件作品。你之前也许听到过将它称之为API或者框架。但在本书中我们会使用游戏引擎这个比较正式的术语。
游戏引擎包括了许多组件,将它们组合使用有利于游戏性能提升和开发周期的缩短。
通常游戏引擎中都包含这些组件,比如:渲染器,2d/3d图形,碰撞检测,物理引擎,声音,控制器,动画等。
游戏引擎通常支持多平台,所以会比较容易对游戏进行移植,将游戏移部署到其它平台只需要一小部分的工作量。
由于Cocos2d-x是一个游戏引擎,它提供了一个简化的API用来开发跨平台的移动和桌面游戏。
通过内置的封装且易于使用的API,你可以专注于开发游戏,不用去关心内部技术的实现。
Cocos2d-x将会尽可能的为游戏开发者提供更大的自由空间。
Cocos2d-x提供了Scene, Transition, Sprite, Menu, Sprite3D, Audio等许多对象。你创建游戏所需要的内容都在这了。
主要组件
看起来好像很复杂,但是开始使用Cocos2d-x是很简单的。
在我们继续进行深入之前,需要理解一些Cocos2d-x中的概念。
Cocos2d-x的核心类为 Scene, Node, Sprite, Menu和Action对象。仔细观察你玩过的游戏你就会发现所有这些部分!
让我们来看一个例子。这看起来像一个很流行的游戏,你也许玩过:
你能找出这些部分吗?让我们来分析一下:
也许你对自己的游戏有一个大概的描绘?和上面的例子对比一下你的游戏中都包括了哪部分。
导演类
Cocos2d-x使用Director(导演)的概念。是的,就像拍电影一样!
Director类控制着游戏整体并通知游戏接下来需要做什么。
把你自己当成影片的监制人,你肯定会通知导演(Director)该如何做!
Director导演的一个通常作用是控制Scene 切换和切换效果。
导演(Director)是一个共享的单例对象,你可以从代码的任何地方调用。
这里是一个典型游戏的流程图。导演 (Director)通过这个流程图来对游戏进行调渡并决定了游戏的标准:
你就是游戏的导演。你决定了游戏中将会发生什么,什么时候发生,如何发生。
这些你都要负责!
场景
游戏中你可能需要一个主菜单,几个关卡和一个结束场景。
你该如何将这些内容单独的分开来呢?是的,这就需要Scene。
回想一下你喜欢的电影,你会发现它很显示的划分出了一些场景,或者单独的故事剧情。
如果我们按照这种思路来处理游戏,不管游戏有多么简单但我们应该想出至少有几个场景。
看下这张图片:
这是一个独立的主界面场景Scene。这个场景是将组件组合在一起而形成一个最终的场景。
场景都由Renderer进行绘制。Renderer可以用来绘制精灵或者其它对象到场景中。为了更好的理解这点我们需要了解一些关于_Scene Graph_的知识。
场景图
_scene graph_是一个用来存储场景图形的数据结构。
_scene graph_由树节点构成。(它虽然叫screne graph,但它实际上是用树形结构来存储)。
这看起来好像很复杂。我敢肯定你会问为什么我们要了解这些底层技术,这不是和cocos2d-x的原则相违背吗?实际上,明白场景是如何来进行绘制的这一点恨得很有必要。当你在游戏中添加节点,精灵或者动画的时候,你肯定希望最终的表现结果是你所预期的。但如果没有达到预期效果呢?
如果你添加的精灵对象在背景层但你希望它们在最前面该怎么办?回到上一步通过将场景放到一个背景上然后在运行,我敢保证你很快会发现自己所犯的错误。
由于_Scene Graph_是树形结构;你可以对它进行遍历。Cocos2d-x使用了_in-order walk_算法。_in-order walk算法的流程如图所示,从根节点开始,然后到右边的树。由于右边的节点是最后绘制的,所以它会首先被显示到场景中。
_scene graph_很容易理解,让我们来对图中所示的场景进行分解:
将上面的场景表示为一个树形,可以简化为如下:
另一个需要注意的地方是,_z-order_Z轴为负数的元素会出现在树的左侧,而_z-order_Z轴为正数的会出现在树的右侧。
当在场景中添加结点的时候需要注意这一点。当然你可以在任意的地方添加节点元素它们会被按钮Z轴的大小自动排序。
在这个概念的基础上,我们可以把Scene的视为一个Node 对象。
先暂时不看上一个场景,来看一下_scene graph_如何利用_z轴_来布局Scene:
图中左边的场景是由许多节点对象组成的,但每个对象都处在不同的_z-order_Z轴上。
在Cocos2d-x中,可以通过API中的addChild()方法来创建_scene graph_场景。
// Adds a child with the z-order of -2, that means
// it goes to the "left" side of the tree (because it is negative)
scene->addChild(title_node, -2);
// When you don't specify the z-order, it will use 0
scene->addChild(label_node);
// Adds a child with the z-order of 1, that means
// it goes to the "right" side of the tree (because it is positive)
scene->addChild(sprite_node, 1);
精灵
所有的游戏都有_Sprites_精灵,你可能知道或者不知道它们是什么。精灵就是游戏中在场景里进行移动的对象。
你可以操纵它们。精灵可能是游戏中最主要的角色。我知道你在想什么-难道每一个图形对象都是精灵Sprite吗?
当然不是!为什么?当你操纵一个精灵的时候,它就是一个精灵。如果你不对它进行操作,那它就是一个节点Node。
看一下图片,我们来说明一下什么是精灵,什么是节点:
精灵是每一个游戏的关键所在。编写一个游戏,你可能需要使用一些带有共同特性的图像。这就是一个Sprite精灵。
_Sprites_精灵很容易创建,它具有很多属性,比如:坐标position,翻转rotation,缩放scale,透明度opacity,颜色color等。
This is how to create an sprite
auto mySprite = Sprite::create("mysprite.png");
// this is how to change the properties of the sprite
mySprite->setPosition(Vec2(500, 0));
mySprite->setRotation(40);
mySprite->setScale(2.0); // sets scale X and Y uniformly
mySprite->setAchorVec2(0, 0);
下面我们来解释每个属性的作用,通过本章的示例代码来思考下面的截图:
通过代码 mySprite->setPosition(Vec2(500, 0));将坐标进行重新设置,如图:
看一下发生了什么。Sprite的坐标从它原有的坐标被移动到了我们设定的新坐标。
通过代码mySprite->setRotation(40);对精灵的翻转进行设定,如图:
看一下发生了什么。Sprite精灵被翻转了我们所设定的角度。
通过代码mySprite->setScale(2.0);将精灵进行缩放,如图:
同样地,我们可以发现精灵的大小被改变了。
最后,所有的节点Node对象(注意Sprite精灵类是Node节点类的子类)都有一个称为锚点的值。我们之前还没有提过这个概念,现在时机正好。你可以将锚点想象为在对精灵进行坐标点设定的时候精灵所自身使用的坐标点。
通过代码mySprite->setAchorVec2(0, 0);将游戏中的精灵锚点设定为(0,0)坐标,那么所有使用代码setPosition()进行坐标设定的精灵都会以自身的左下角来进行对齐。让我们来试一下:
注意观察每个图像中的红点。这个红点就是精灵的锚点位置!
你会发现锚点对节点来说是非常有用的。你甚至可以通过调整精灵的锚点来模拟动态效果。
现在我们已经可以很好地使用精灵了。那么该如何让这些精灵按照一定的时间间隔自动的进行播放呢?继续向下看。
动作
创建Scene场景,添加Sprite精灵对象到屏幕上只是其中一部分。
游戏之所以称为游戏就是我们需要让精灵运动起来!Action动作游戏中的一部分。_Actions_动作类可以让Node节点对象按时间进行运动。
希望将一个Sprite精灵从一个坐标点移动到另一个坐标并在结束时调用回调函数?没有问题!
你可以创建一个Actions动作序列Sequence并且按顺序播放。你可以通过改变Node节点属,坐标,角度,缩放。比如说这些动作:MoveBy,Rotate, Scale。所有的游戏都使用动作类Actions。
查看本章的示例代码,下面就是_Actions_的演示:
5秒过后,精灵会移动到新的坐标点:
_Actions_很容易创建使用:
auto mySprite = Sprite::create("Blue_Front1.png");
// Move a sprite 50 pixels to the right, and 10 pixels to the top over 2 seconds.
auto moveBy = MoveBy::create(2, Vec2(50,10));
mySprite->runAction(moveBy);
// Move a sprite to a specific location over 2 seconds.
auto moveTo = MoveTo::create(2, Vec2(50,10));
mySprite->runAction(moveTo);
序列和Spawns
让精灵在屏幕中进行移动就是我们想要的最终结果了吗?当然不是。可不可以运行多个动作呢?是的,没问题,Cocos2d-x通过几个方式来支持这种操作。
就如同它的名字,一个序列Sequence就是多个动作按钮一定顺序进行排列。需要按反方向来播放序列动作?也没问题,Cocos2d-x也支持这个操作。
看一下面的例子,通过序列Sequence逐步移动一个精灵Sprite:
这个Sequence很容易创建:
auto mySprite = Node::create()
// move to point 50,10 over 2 seconds
auto moveTo1 = MoveTo::create(2, Vec2(50,10));
// move from current postion by 100,10 over 2 seconds
auto moveBy1 = MoveBy::create(2, Vec2(100,10));
// move to point 150,10 over 2 seconds
auto moveTo2 = MoveTo::create(2, Vec2(150,10));
// create a delay
auto delay = DelayTime::create(1);
mySprite->runAction(Sequence::create(moveTo1, delay, moveBy1, delay->clone(), moveTo2, nullptr));
本例中按顺序播放序列中的每一个动作,如何同步一起运行这些动作?Cocos2d-x也支持这个操作,它称做Spawn。Spawn会在同一时间播放所有指定的动作。有一些可能比别的稍长一些,所以出现这种情况动作的播放不会在同一时间完成。
auto myNode = Node::create()
auto moveTo1 = MoveTo::create(2, Vec2(50,10));
auto moveBy1 = MoveTo::create(2, Vec2(100,10));
auto moveTo2 = MoveTo::create(2, Vec2(150,10));
myNode->runAction(Spawn::create(moveTo1, moveBy1, moveTo2, nullptr));
为什么要使用Spawn呢?有什么理由吗?当然!当游戏的主角获得能力提升时要播放多个动作的时候。当BOSS战的最后阶段需要同时播放多个动作来终结的时候。
父类和子类之间的继承关系
Cocos2d-x使用Parent and Child继承。也就是说父类中的属性也适用于他们的子类。考虑一个Sprite对象和它的子类对象Sprite:
当改变父类中的精灵角度时,子类的角度也会随之进行变化:
auto myNode = Node::create();
// rotating by setting
myNode->setRotation(50);
不光是角度,如果你将父类进行缩放,那么子类的精灵也会跟着缩放:
auto myNode = Node::create();
// scaling by setting
myNode->setScale(2.0); // scales uniformly by 2.0
总结
我们已经介绍了一些Cocos2d-x中的概念。放轻松呢,不要太担心。按照自己的想法一步一个脚印的来。Cocos2d-x和编程并不是一种可以通过熬夜就可以学会的技能。这些都需要练习和思考。
有什么问题可以去论坛与大家进行交流。