Cocos2d-x 开发者指南02:Cocos2d-x中的基础概念

2015年03月20日 11:10 0 点赞 0 评论 更新于 2025-11-21 13:30

在本章之前,我们可能已经学习了一些关于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(3D精灵)、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_是一种用于存储场景图形的数据结构,它由树节点构成(尽管名称中包含“graph”,但实际上采用树形结构存储)。

你可能会疑惑,为什么要了解这些底层技术,这是否与Cocos2d-x的设计原则相悖?实际上,了解场景的绘制原理非常必要。当你在游戏中添加节点、精灵或动画时,你肯定希望最终的显示效果符合预期。但如果实际效果与预期不符,比如你添加的精灵对象本应显示在最前面,却出现在了背景层,此时了解场景图的结构就能帮助你快速定位问题。

由于_Scene Graph_是树形结构,你可以对其进行遍历。Cocos2d-x使用了_in-order walk_算法,该算法从根节点开始,然后遍历右侧的树。由于右侧的节点最后绘制,所以会最先显示在场景中。

下面我们对图中所示的场景进行分解,将其表示为树形结构可以简化为如下形式。需要注意的是,z-order(Z轴)为负数的元素会出现在树的左侧,而_z-order_为正数的元素会出现在树的右侧。在场景中添加节点时,需要考虑这一点。不过,你可以在任意位置添加节点元素,它们会根据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(精灵),你可能对它们有所了解,也可能不太清楚。简单来说,精灵是游戏中在场景里可以移动的对象,你可以对其进行操作。不过,并非所有图形对象都是精灵,当你对一个对象进行操作时,它才成为精灵;若不进行操作,则可将其视为节点Node。

下面通过一张图片来说明什么是精灵,什么是节点。精灵是游戏的关键元素,在编写游戏时,你可能会使用一些具有共同特性的图像,这些图像就可以抽象为一个Sprite精灵。

_Sprites_精灵创建起来非常容易,它具有许多属性,如坐标position、翻转rotation、缩放scale、透明度opacity、颜色color等。以下是创建精灵并修改其属性的示例代码:

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(动作)类就是实现这一功能的关键。Action类可以让Node节点对象按照时间进行运动。

例如,你希望将一个Sprite精灵从一个坐标点移动到另一个坐标点,并在移动结束时调用回调函数,这是可以实现的。你可以创建一个Actions动作序列Sequence,并按顺序播放这些动作。通过改变Node节点的属性,如坐标、角度、缩放等,你可以实现各种动作,如MoveBy、Rotate、Scale等。几乎所有的游戏都会使用动作类Actions。

以下是_Actions_的演示示例代码,5秒过后,精灵会移动到新的坐标点:

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的示例代码:

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对象及其子类对象为例,当改变父类中精灵的角度时,子类的角度也会随之改变:

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和编程不是一蹴而就的事情,需要通过不断的练习和思考来掌握。

如果你在学习过程中遇到问题,可以去论坛与其他开发者进行交流。