转自http://cocos2d.cocoachina.com/bbs/forum.php?mod=viewthread&tid=805&extra=page%3D1
(内容重点: 动画, CCAnimation, CCAnimate, CCSpriteFrameCache)
一个遊戏的角色如果只是静止的, 相信不会太招人喜欢, 动作流畅自然的遊戏角色, 更能让遊戏大放异彩, 搞不好受欢迎了还可以学小鸟去出毛娃娃... 扯远了, 这次让我们看一下在 cocos2d-x 上怎样处理角色的动画吧.
上一次我们提到在遊戏里要用纹理地图(texture atlas), 除非有其他特殊情况, 不然这个基本上是必然的选择, 所以我们以这个为前提条件下看看怎样弄动画.
(这次用的动画借用了老G cocos2d-x 教程里的小女孩, 这里向大家推荐一下老G的博客和教程http://4137613.blog.51cto.com/, 内容非常多, 值得去看一下. 另外, 如果大家对自己搞动画不想知道得太多, 可以试试老G的AnimatePacker)
下边就是这次我们会用到的图像:
如果大家有留意上次的教程, 我们在载入纹理地图时是用了CCSpriteFrameCache:
- CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();
- cache->addSpriteFramesWithFile("images.plist", "images.png");
复制代码
由此我们可以推论出这些图像的"存在"其实己被变成了动画帧(CCSpriteFrame), 这样对於我们弄动画可谓是非常方便, 就拿 girl1.png ~ girl4.png 来说, 我们可以用下边这几行代码把它们组成一个动画:
- CCMutableArray<CCSpriteFrame*> *animFrames = new CCMutableArray<CCSpriteFrame*>(4); // 一共有4幀
-
- CCSpriteFrame *frame = cache->spriteFrameByName("girl1.png"); // 加進第1幀
- animFrames->addObject(frame);
-
- frame = cache->spriteFrameByName("girl2.png"); // 加進第2幀
- animFrames->addObject(frame);
-
- frame = cache->spriteFrameByName("girl3.png"); // 加進第3幀
- animFrames->addObject(frame);
-
- frame = cache->spriteFrameByName("girl4.png"); // 加進第4幀
- animFrames->addObject(frame);
-
- CCAnimation *animation = CCAnimation::animationWithFrames(animFrames, 0.1f); // 用剛才的動畫幀生成一個動畫, 每一幀的播放長度是0.1秒
复制代码
接下来我们可以把这个动画放进一个 CCSprite 里播放:
- pSprite = CCSprite::spriteWithSpriteFrameName("girl1.png");
- pSprite->setPosition(ccp(size.width/5, size.height/2));
-
- pSprite->runAction(CCAnimate::actionWithAnimation(animation, false));
复制代码
actionWithAnimation 的第二个参数是 bRestoreOriginalFrame, 这个是什麽意思呢? 这是指当整套动画播完後, 是不是显示回CCSprite本来的图像, 即是我们调用 CCSprite::spriteWithSpriteFrameName("xxx") 时所提供的那个, 我们可以把 spriteWithSpriteFrameName("girl1.png") 的 "girl1.png" 换成 "CloseNormal.png", 再把 false 变成 true 来试一下. 那这个安排有什麽用呢, 一个例子是我们的主角有一个静止动态和一个踢腿动画, 当我们把静止动态设定为CCSptite 原图像, 在播放完踢腿动画後就可以自动换回静止动态了.
我们也可以不断的重複播放动画:
- pSprite->runAction(CCRepeatForever::actionWithAction(CCAnimate::actionWithAnimation(animation, false)));
复制代码
当然每次弄一个动画都这麽大费周章就太烦了, 所以我们可以写一个常用函数方便一下:
- CCAnimation *SpriteHelper::animationWithSingleFrames(const char *name, int count, float delay)
- {
- CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();
-
- CCMutableArray<CCSpriteFrame*> *animFrames = new CCMutableArray<CCSpriteFrame*>(count);
-
- char str[80];
- for(int k = 0; k < count; k++)
- {
- sprintf(str, "%s%d.png", name, (k+1));
- CCSpriteFrame *frame = cache->spriteFrameByName(str);
- animFrames->addObject(frame);
- }
-
- CCAnimation *animation = CCAnimation::animationWithFrames(animFrames, delay);
-
- return animation;
- }
复制代码
我们预设的条件是每个动画帧名字的编号, 都是由1开始顺序的数下去, 因此像刚才的小女孩走路动画, 可以用下面这句代码生成:
- CCAnimation *animation = SpriteHelper::animationWithSingleFrames("girl", 4, 0.1f);
复制代码
如果一个动画的每个帧都是像girl1.png ~ girl4.png 这样子般独立的, 那麽只要用上面的代码就可以搞定, 但有时美术人员给我们的动画可能是像 walk_left.png 这样, 是连在一起的, 那该怎麽办呢? 因为CCSpriteFrameCache 只会给我们一个动画帧, 我们需要的郤是4个, 这个情况下我们只好自己做点手脚把它拆成4个了!
首先让我们看一下 CCSpriteFrame 到底是什麽, CCSpriteFrame 基本上就是提供了一个图像在拼进纹理地图後它的位置和本身的大小, 比如我们拿girl1.png 的CCSpriteFrame 来看一下:
- CCSpriteFrame *frame = cache->spriteFrameByName("girl1.png");
复制代码
可以看到它在纹理地图里的位置是(194, 371) 而它的大小是 32x48:
再看一下 walk_left.png 的帧, 它的大小是 128x48:
(这里我做了一个艰难的决定, 上一次我们提到在用TexturePacker 制做纹理地图时可以选用 trim/crop 来拿掉图像周围的透明像素用以节省空间, 但这样一来在分拆动画帧时就会带来麻烦, 所以这里我以一点空间浪费换取程序上的方便, 没有用 trim/crop)
它也是4个动画帧, 即是每个帧也是 32x48! 於是我们可以根据这些资料, 把它分拆成4个动画帧来生成 CCAnimation.
- int col = 4;
- CCSpriteFrame *strip = cache->spriteFrameByName("walk_left.png");
- CCTexture2D *texture = strip->getTexture();
- CCSize originalSize = strip->getOriginalSizeInPixels();
- CCRect stripRect = strip->getRect();
-
- float w = originalSize.width/col;
- float h = originalSize.height;
-
- float x = stripRect.origin.x;
- float y = stripRect.origin.y;
-
- CCMutableArray<CCSpriteFrame*> *animFrames = new CCMutableArray<CCSpriteFrame*>(col);
-
- for (int j=0;j<col;j++)
- {
- CCSpriteFrame *frame = CCSpriteFrame::frameWithTexture(texture, CCRectMake(x, y, w, h));
- animFrames->addObject(frame);
- x += w;
- }
-
- CCAnimation *animation = CCAnimation::animationWithFrames(animFrames, 0.1f);
复制代码
当然可爱的美术人员除了会把一个动作的动画拼在一起, 有时还会把同一个角色的所有动画放在一张图像里给我们, 再加上TexturePacker 的"Allow Rotation" 功能, 我们最後得到的动画帧在纹理地图可能是以"girl.png" 这样的姿态躺著出现... 杯具了吧?
我们只好把行和列也计算在内, 写个函数来统一处理了:
- CCAnimation *SpriteHelper::animationWithStrip(const char *name, int count, float delay, int col, int row, int startingRow)
- {
- CCMutableArray<CCSpriteFrame*> *animFrames = new CCMutableArray<CCSpriteFrame*>(count);
-
- CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();
-
- CCSpriteFrame *strip = cache->spriteFrameByName(name);
- CCTexture2D *texture = strip->getTexture();
- CCSize originalSize = strip->getOriginalSizeInPixels();
- bool rotated = strip->isRotated();
- CCRect stripRect = strip->getRect();
-
- float w = originalSize.width/col;
- float h = originalSize.height/row;
-
- float x = stripRect.origin.x;
- float y = stripRect.origin.y;
-
- int n = 0;
- bool done = false;
-
- float xx;
- float yy = y;
-
- for (int i=startingRow;i<row && !done;i++)
- {
- if (rotated)
- xx = (x+originalSize.height)-(i+1)*h;
- else
- xx = x;
-
- for (int j=0;j<col && !done;j++)
- {
- CCSpriteFrame *frame = CCSpriteFrame::frameWithTexture(texture, CCRectMake(xx, yy, w, h));
- frame->setRotated(rotated);
- animFrames->addObject(frame);
-
- if (rotated)
- {
- yy += w;
- }
- else
- xx += w;
-
- n++;
- if (n >= count)
- done = true;
- }
-
- if (rotated)
- yy = y;
- else
- yy += h;
-
- }
-
- CCAnimation *animation = CCAnimation::animationWithFrames(animFrames, delay);
-
- return animation;
-
- }
复制代码
col 是指原图像共有多少列, row是指原图像共有多少行, 而 startingRow 则是指由那一行开始拿动画帧, 比如我们想要背对著我们的小女孩走路动画, 就用:
- CCAnimation *animation = SpriteHelper::animationWithStrip("girl.png", 4, 0.1f, 4, 4, 3);
复制代码
这次就写到这里了, 有时间再继续.
题外话:
我是刚开始学习 cocos2d-x 的, 所以有什麽地方写错了或写的不好, 欢迎大家指正赐教, 互相交流学习!