cocos2d-x学习之路(三)——精灵与动作

这里我们来看看所有游戏引擎中都会出现的一个重要的概念——精灵🧚‍♀️,及其使用方法。我还将介绍如何通过“动作”来控制精灵。


精灵的概念

在各大游戏引擎中,精灵一般都是载有图像,可以实现某些动作(比如移动,旋转,甚至高级一点的跳跃和碰撞)的一种类。在cocos2dx里面,官方文档对精灵的定义是:精灵是您在屏幕上移动的对象,它能被控制。你喜欢玩的游戏中主角可能就是一个精灵,我知道你在想是不是每个图形对象都是一个精灵,不是的,为什么? 如果你能控制它,它才是一个精灵,如果无法控制,那就只是一个节点(Node)。

简单的说就是“可以被玩家控制的就是精灵(比如游戏里的角色),不能被玩家控制的就是节点”。


精灵的创建

cocos2dx里创建精灵的方法有多种。首先最简单的是使用create静态函数来创建:

auto spriteName = Sprite::create(path);

这里path是图像所在的路径名称。这里提示一下:要加载的图片最好是png格式的。我常常加载jpg和bmp加载失败。

你可能会有疑问:为什么使用的是create静态方法?我在其他游戏引擎里(比如pygame)都是使用构造函数创建啊,这里使用create函数不是加多了代码量吗?

这里我觉得有着两种原因:

  • 这里使用静态方法可以方便Sprite类管理已经生成的精灵。而如果使用构造函数的话类对产生的对象的管理会比较麻烦(我以前就是常常使用构造函数,的确会加大管理的难度(也可能是我本身水平的问题吧🙂))。
  • 不知道你有没有发现,这个create函数使得Sprite像是工厂一样。没错,这里的Sprite的create是工厂方法(工厂方法详见设计模式),这样实现对拓展开放,对修改关闭,在代码设计上也是杠杠的。

后面我们还会接触到很多很多的工厂函数。可以说cocos2dx创建所有的东西都是使用工厂方法。后面我们会看到工厂方法在cocos里的广泛运用。

 

这里你还可以使用一些优化的方法来通过图像导入精灵,首先是使用图集(.plist)文件来导入图片并创建精灵。要是想使用图集,你可以将图集加载到“精灵帧隐藏区(SpriteFrameCache)”,SpriteFrameCache是一个单例对象。在cocos2dx里面获得单例对象基本上都是使用getInstance()方法:

auto spritecache = SpriteFrameCache::getInstance();
spritecache->addSpriteFramesWithFile("sprites.plist");

第一行获得SpriteFrameCache的单例,第二行读取了sprites.plist图集。

那么什么是图集呢?

图集就是将多个图片变成一个大图片,就和图片合并差不多。将最后生成图片的信息放到plist文件里面。

那么这有什么好处呢?

好处有以下几点:

  • 读取图片的次数少了。以前要读取一大堆图片,现在只需要读取一个plist文件就行了。
  • 减少OpenGL的渲染次数 

 这样就加快了图像的读取速度。

那么怎么制作一个图集呢?

cocos官方提供了四种工具来创建图集。其中比较推荐的是Texture Packer和Zwoptex两个工具。

我使用的是Texture Packer工具。这个是官方最推荐的一个: 

这里是将我自己绘制的主角生成了图集(不要吐槽主角的画风以及主角衣服上的字......)。然后点击上面菜单栏的“Publish sprite sheet”来生成一个.plist和一份图像文件(默认png文件,就是在软件里面显示的这个图片)。

生成的.plist文件是使用xml作为描述的文件。打开可以看到如下信息: 

这里因为我的是mac系统,打开之后直接就帮我解析了XML文件了。

可以看到文件里面分为frames和metadata两个标签。其中frames是保存我们图片信息的。其中图片的名字信息是图片的名字,也就是说如果你对OKNinja.bmp生成图集,那么OKNinja.png对应的名字就是OKNinja.bmp了。

然后比较重要的是textureRect标签,里面有一个列表,表示的是这个图片在总图片中的左上角坐标以及大小,即{{x,y},{w,h}}。

然后textureRotated表示是否旋转了图片

metadata包含了一些关于图片格式的信息。可以看出产生的图片名字在标签realTextureFileName下,我这里是test.png。然后总图片的大小在size标签下,我这里是740*200的。然后还可以在pixelFromat标签下看到颜色类型,这里是RGBA8888。

 

有了图集,并且加载了之后,我们就可以从图集里面读取图片了。使用createWithSpriteFrameName()函数。

auto mysprite = Sprite::createWithSpriteFrameName(spriteName);

这里spriteName为你的图片名称,而且必须是带后缀的全称。名称可以在.plist文件里面看到(其实就是原图图像名)。我这里加载一个OKNinja.bmp出来:

SpriteFrameCache::getInstance()->addSpriteFramesWithFile("res/test.plist");
auto sprite = Sprite::createWithSpriteFrameName("OKNinja.bmp");

这里我将总图像和.plist放在res文件夹下了,所以路径里面要加上"res/"。


 控制精灵和放置精灵

创建了精灵之后我们当然是将其显示在我们的窗口上了啦。首先介绍一些控制精灵的函数,这些函数都是精灵对象的成员函数:

  • setPosition()设置精灵的位置
  • setScale()设置精灵缩放,你也可以使用setScaleX(),setScaleY()来只针对x或y方向缩放
  • setOpacity()设置精灵的透明度。0为完全透明,255位完全不透明。
  • setRotateion()设置精灵旋转。顺时针为正角度
  • setSkew()设置倾斜,你也可以使用setSkewX(),setSkewY()
  • setColor()设置颜色。里面的参数是Color3B指定的颜色。

比如说我们想要放置精灵在窗口,就得先设置精灵的坐标:

sprite->setPosition(Vec2(200,200));

 这里通过Vec2类的构造函数来创建一个二维向量指定位置为(200,200)。后面在进入3D的时候我们还会遇到Vec3来表示空间向量。

 

这里还要着重说一下的是锚点的概念。你可以使用成员函数setAnchorPoint()函数来设置锚点。

所谓锚点就是图像的参考点,用物理的话说就像是质点一样。锚点代表了真个图像的坐标信息,所有需要图像坐标的函数都是获取锚点来作为图像的坐标的。比如我们旋转的时候,你说它是按照图像上那个点进行旋转呢?答案就是按照锚点旋转。默认锚点在图像的中心,也就是(0.5,0.5)处。这里说明一下,你在设置锚点时(1,1)是图像最右上方,(0,0)为图像的最左下方。所以(0.5,0.5)就是图像中心啦。

 

最后我们需要放置精灵到场景中。使用场景的addChild方法:

this->addChild(sprite);

 

这里由于我是在场景HelloWorldScene里面直接放置精灵的,所以使用this来指代HelloWorldScence场景。

 

最后效果如下:  


 

cocos坐标系

在说控制之前首先要了解cocos的坐标系。首先是UI坐标,也就是一般软件设计UI界面时使用的坐标。这种坐标X轴向右,Y轴向下,原点在窗口的左上角,是个常见的计算机坐标系。

接下来是OpenGL坐标。这是标准的笛卡尔坐标系:X轴向右,Y轴向上,原点在窗口左下角。

你可以使用convertToGL()函数将UI坐标转换为OpenGL坐标。

cocos默认为OpenGL坐标。


精灵的动作控制

有了精灵,也将精灵放在窗口中了,接下来我们就要对精灵进行一些控制了。这一部分的函数都是大同小异的,比较简单。

首先来看看如何移动精灵。这里有两种移动方式:

  • 使用MoveBy对象
  • 使用MoveTo对象

你说,诶这里使用对象来移动是什么意思?我以前都是使用类的成员函数移动的呀。

这个也可以说是cocos的特点了吧。cocos将所有的动作(移动,旋转,缩放,跳跃等)封装成对象,当你要使用这些动作的时候你可以通过对象构建动作序列来让人物完成一系列动作。是不是很机智?

那你又说,MoveBy和MoveTo有什么区别呢?

所有的动作函数里,后缀为By的都是指从当前状态开始执行,而To则是不管当前状态直接执行。也就是说如果我MoveBy(200,200),这样是从当前的位置向上移动200,向右移动200。但是如果是MoveTo的话,就移动到坐标为(200,200)的地方去了(直线移动)。

 

那么如何创建一个动作呢?答案就是使用对应类的工厂方法啦:

auto mv=MoveBy::create(time,Vec2(x,y));

 

这里time是需要在多长时间内完成这个动作,单位为秒。 然后是由Vec2生成的目的地坐标(或者是偏移坐标,这个取决于你是使用By还是To)。

 

很多动作都是这样创建的,而且也都有time参数(都是在第一个),例如旋转:

auto rotate1=RotateBy::create(time,angle);

 

这里angle为角度值,以角度制衡量。

 

这里介绍一个比较特殊的动作:DelayTime。这个动作只是延时一段时间而已:

auto delay=DelayTime(time);

 

 

有了一些方法对象之后,我们还需要将方法对象变成序列(Sequence)。那么怎么变成序列呢?想必你已经想到了:使用工厂方法呗;

atuo seq=Sequence::create(action1,action2,...,nullptr);

 

 这里动作序列里面可以传入任意长的动作对象。最后使用nullptr或者NULL来表示序列结束。

我这里创建了这样的动作序列:

auto delay=DelayTime::create(1);
auto rotate1=RotateBy::create(2, 90.0f);
auto scale1=ScaleBy::create(2,2);
auto seq=Sequence::create(delay(),rotate1,rotate1->clone()->reverse(),delay->clone(),MoveTo::create(2, Vec2(500,300)),delay->clone(),scale1, nullptr);

 

这里的FadeIn是淡入,FadeOut是淡出。需要注意的是,如果你要使用FadeIn的话,精灵必须是有一些透明的(透明值的大小会影响你的FadeIn效果)。同样FadeOut的话必须是不完全透明的。ScaleBy是缩放啦。

还要注意的一点是:所有的动作对象都有自己的内部状态。如果你在一个序列里面使用多次同一个动作对象的话,可能会因为内部状态导致奇葩的错误。这个时候你需要调用动作对象的成员函数clone()来获得其副本。这里序列的delay就做到了这一点。

然后rotate1的reverse()函数是用来将动作反转的。

 

最后需要让精灵使用这个动作序列,以此来完成动作。使用精灵的成员函数runAction()就行:

sprite->runAction(seq);

 

 结果如下:

 

这里还要介绍一个结构,是和Sequence同样用来存储动作的结构:Spawn。与Sequence不一样的是,Spawn会同时执行其存储的动作对象。Spawn的创建和使用方法完全和Sequence一模一样。

最后要说的是,Sequence里可以嵌套Spawn或者Sequence。Spawn里面也可以嵌套Sequence和Spawn。只要将其传入参数就行了:

auto spawn = Spawn(Moveby::create(2,Vec2(10,l00),Rotate::create(2,30)));
auto seq=Sequence::create:(ScaleBy::create(2,2,2),DelayTime::create(3),spawn,Flipx::create(2));

 

总结

最后我们来总结一下创建精灵以及控制精灵的方法: 

 

posted @ 2018-09-13 23:21  VisualGMQ  阅读(930)  评论(0编辑  收藏  举报