在游戏编程中,动画可谓必不可少,今天就向大家介绍cocos2d里面如何使Sprite加载动画,同时我们利用了SpriteSheet技术来简化操作及提高效率。
本文是基于大神子龙山人翻译的博客修改而成,但原文是基于cocos2d 2.x版本及object-c语言的,因此我将它进一步加工成了C++语言,并尽量基于cocos2d 3.0的实现版本。由于本人也是cocos2d的初学者,因此文中有任何不妥之处,尽管拍砖!
子龙山人博客:http://www.cnblogs.com/andyque/archive/2011/04/11/2012770.html
英文原版地址:http://www.raywenderlich.com/1271/how-to-use-animations-and-sprite-sheets-in-cocos2d
废话少说,让我们开始吧。
什么是SpriteSheet
SpriteSheet,翻译为“精灵表单”。好吧,这个翻译也许并不那么直白。SpriteSheet的定义,见原文翻译:
“如果你从来没有使用过spritesheet,你可以把它看作是一张巨大的图片,你可以把许许多多的sprite放进去。与spritesheet对应的,还有一个plist文件,这个文件指定了每个独立的sprite在这张“大图”里面的位置和大小,当你在代码之间需要使用这个sprite的时候,就可以很方面地使用plist文件中的这些信息来获取sprite。”
SpriteSheet能够方便的帮我们管理众多Sprite,比如,你的游戏人物有一个射击的动作,你将射击分解为了4个帧的画面,每个画面,你可能都要创建一个精灵并依次渲染它们,这样,共渲染4次。而SpriteSheet却将4帧画面整合为一个SpriteSheet,当你需要显示连续的射击动画时,这4个画面会被当做一个大的SpriteSheet画面渲染1次!你需要做的仅是将存储在SpriteSheet中的帧画面取出并组成Action,并让你的Sprite来run这个action。感觉出来了吗,没有SpriteSheet,你需要渲染4次Sprite画面,而现在你只要渲染一次,大大提高了效率!原文解释如下。
“为什么这会提高效率呢?因为cocos2d对它进行了优化!如果你使用spritesheet来获取sprite,那么当场景中有许多sprite的时候,如果这些sprite共享一个spritesheet,那么cocos2d就会使用一次OpenGL ES调用来渲染这些sprite。但是,如果是单个的sprite的话,那么就会有N次OpenGL ES call,这个代价是相当昂贵的”
如何运用SpriteSheet
好了,既然SpriteSheet这么牛逼,那么我们具体应该如何使用它那?
首先,SpriteSheet作为cocos2d的一种技术,需要有特殊的资源文件配合其实现。什么特殊资源文件呢,两部分:
1、整合了所有帧画面的大png图片。大png图片如下所示,包含了熊散步动画的所有帧画面。
2、描述大png图片信息的plist文件(plist文件包含描述信息,里面包含了图片在spritesheet中的位置、大小和名字等信息)。
那我们怎么获取这两个文件呢,我们有工具 Zwoptex!
工具不知怎么上传不到云盘,是mac版的,有需要可以留邮箱发送。
具体怎么使用工具?看原文博客,有详细说明。但原文软件版本和我的版本并不一致,操作上略有不同。具体细节大家自己摸索咯(偷个懒)。
OK,有了资源文件,我们就可以新建工程,来让我们的熊出来散散步吧~
顺便介绍一下我的开发环境
Xcode5-DP and cocos2d-x-3.0beta2.
简单动画
让我们先创建一个叫做WalkBear的cocos2d工程(具体创建方法可询问度娘或现在正关小黑屋的谷哥)。
在HelloWorldScene.h 里面添加如下属性。
CC_SYNTHESIZE_RETAIN(cocos2d::Sprite*, _bear, BearSprite);
CC_SYNTHESIZE_RETAIN(cocos2d::Action*, _walkAction, WalkAction);
CC_SYNTHESIZE_RETAIN(cocos2d::Action*, _moveAction, MoveAction);
啥?没看懂上面是什么意思?别忘了,cocos2d可是开源的,让我们一起看看CC_SYNTHESIZE_RETAIN宏定义(cocos2d的注释写的很详细,有不明白的地方可以直接看源码、注释)。
#define CC_SYNTHESIZE_RETAIN(varType, varName, funName) \
private: varType varName; \
public: virtual varType get##funName(void) const { return varName; } \
public: virtual void set##funName(varType var) \
{ \
if (varName != var) \
{ \
CC_SAFE_RETAIN(var); \
CC_SAFE_RELEASE(varName); \
varName = var; \
} \
}
这下明白了吧,其实我们运用宏
CC_SYNTHESIZE_RETAIN(cocos2d::Sprite*, _bear, BearSprite);
相当于写了如下一堆代码,
private: cocos2d::Sprite* _bear;
public: virtual cocos2d::Sprite* getBearSprite(void) const { return _bear; }
public: virtual void setBearSprite(cocos2d::Sprite* var)
{
if (_bear != var) // 注意,这里只有当原值与新值不相等时,才会赋值
{
CC_SAFE_RETAIN(var);
CC_SAFE_RELEASE(_bear);
_bear = var;
}
}
我的感觉是加上RETAIN(汉语为”保留“)表示告诉cocos2d的内存处理机制,这个对象是属于我的,你cocos2d在自动清理空闲内存的时候,别给我自动清了,哥留着有用。如果不加上retain,可能会导致cocos2d自动将你生成的对象认为没有用了而清理掉。这样你再想用它的时候,可就找不回来了。当然,这是我个人浅薄的理解半猜性质,希望能有大神进一步解释。
在HelloWorld的构造函数里,我们初始化这三个变量为空指针。(nullptr是C++11标准中代替NULL的,记得加入头文件memory.h)
HelloWorld():_bear(nullptr), _walkAction(nullptr), _moveAction(nullptr), _walking(false){}
好了,我们的演员(_bear)、剧本(_walkAction、_moveAction,当然现在时“空剧本”),都到位了,就让我们的熊动起来吧!
初级阶段 简单的动画
在初级阶段,我们要实现熊的走路动画,当然,是向着一个方向傻傻的走,在高级点阶段里面,我们将实现触屏来指引熊移动方向的功能。
在初级阶段里面,我们在init()方法里面添加代码。共分为5个步骤。
1)缓冲sprite帧和纹理
SpriteFrameCache* shareSpriteBearFrameChache = SpriteFrameCache::getInstance();
shareSpriteBearFrameChache->addSpriteFramesWithFile(“AnimBear.plist”);
在上面的代码里面,我们建立了一个SpriteFrameCache对象实例,同时,调用了其addSpriteFramesWithFile方法。该方法完成两件事情:
(1)寻找工程目录下面和输入的参数名字一样,但是后缀是.png的图片文件。然后把这个文件加入到共享的TextureCache中。(这我们这个例子中,就是加载AnimBear.png)
(2)解析plist文件,追踪所有的sprite在spritesheet中的位置,内部使用SpriteFrame对象来追踪这些信息
2) 创建一个精灵批处理结点
SpriteBatchNode* spriteSheet = SpriteBatchNode::create("AnimBear.png");
this->addChild(spriteSheet);
精灵批处理节点的说明,请看原文,其中的2.0的类名称,各位看官可自行对比。
“接下来,创建CCSpriteBatchNode对象,把spritesheet当作参数传进去。spritesheet在cocos2d中的工作原理如下:
• 你创建一个CCSpriteBatchNode对象,通过传递一个包含所有sprite的spritesheet的名字作为参数,并把它加入到当前场景之中。
• 接下来,你从spritesheet中创建的任何sprite,你应该把它当作CCSpriteBatchNode的一个孩子加进去。只要sprite包含在spritesheet中,那么就没问题,否则会出错。
• CCSpriteBatchNode可以智能地遍历它的所有的孩子结点,并通过一次OpenGL ES call来渲染这些孩子,而不是以前每个sprite都需要一个OpenGL call,这样渲染速度就会更快。
3) 收集帧列表
cocos2d::Vector walkAniFrams;
for (int i = 1; i <=8; i++) {
std::ostringstream os;
os << "bear" << i << ".png";
std::string strFrameNmae = os.str();
walkAniFrams.pushBack(shareSpriteBearFrameChache->getSpriteFrameByName(strFrameNmae));
}
4) 创建动画对象
Animation* walkAnim = Animation::createWithSpriteFrames(walkAniFrams, 0.1);
简单,只有一句话,我们将上一步存储动画帧的vector当做参数传进去,并指定了动画帧之间的播放间隔0.1秒(单位是秒吧)
5) 创建sprite并且让它run动画action
创建精灵并显示
_bear = Sprite::createWithSpriteFrameName("bear1.png");
_bear->setPosition(Point(visibleSize.width/2, visibleSize.height/2));
_walkAction = RepeatForever::create(Animate::create(walkAnim));
_walkAction->retain();
_bear->runAction(_walkAction);
spriteSheet->addChild(_bear);
在上面的代码里,我们首先创建了一个精灵对象。注意,我们这里创建精灵的方法,并没有使用creata方法,而是用了createWithSpriteFrameName。对于该方法,cocos2d里面是这样说明的
/**
* Creates a sprite with an sprite frame name.
*
* A SpriteFrame will be fetched from the SpriteFrameCache by spriteFrameName param.
* If the SpriteFrame doesn't exist it will raise an exception.
*
* @param spriteFrameName A null terminated string which indicates the sprite frame name.
* @return A valid sprite object that is marked as autoreleased.
*/
这里就是说,我们用spriteframe当做初始化模板,创建了一个精灵。而这个当做模板的spriteframe,必须是已经存在于SpriteFrameCache中的。而这个SpriteFrameCache,就是我们之前在第一步中创建的shareSpriteBearFrameChache,它存储了关于熊的spritefame。
创建好sprite对象之后,我们接着又创建了一个让熊表演的action对象,同时让熊精灵来run这个action。
注意,在最后一步,我们把熊加个场景中—把它当作spritesheet的孩子加到spritesheet中去。注意,如果在这里我们没有把它加到spritsheet中,而是加到当前层里面的话。那么我们将得不到spritesheet为我们带来的性能提升!!!
OK!编译运行吧,一只熊就在你手机上运行了!
高级阶段 让熊按照你的旨意行动
(待续)