地球人己阻止不了程序猿们学习cocos2d-x了 (第二篇)

  • 本帖最后由 dr_watson 于 2012-8-8 10:03 编辑 *

(内容重点: 动画, CCAnimation, CCAnimate, CCSpriteFrameCache)

一个?戏的角色如果只是静止的, 相信不会太招人喜欢, 动作流畅自然的?戏角色, 更能让?戏大放异彩, 搞不好受欢迎了还可以学小鸟去出毛娃娃… 扯远了, 这次让我们看一下在 cocos2d-x 上怎样处理角色的动画吧.

上一次我们提到在?戏里要用纹理地图(texture atlas), 除非有其他特殊情况, 不然这个基本上是必然的选择, 所以我们以这个为前提条件下看看怎样弄动画.

(这次用的动画借用了老G cocos2d-x 教程里的小女孩, 这里向大家推荐一下老G的博客和教程http://4137613.blog.51cto.com/, 内容非常多, 值得去看一下. 另外, 如果大家对自己搞动画不想知道得太多, 可以试试老G的AnimatePacker)

下边就是这次我们会用到的图像:

179

如果大家有留意上次的教程, 我们在载入纹理地图时是用了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));

178

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;
        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个了!
186

首先让我们看一下 CCSpriteFrame 到底是什麽, CCSpriteFrame 基本上就是提供了一个图像在拼进纹理地图後它的位置和本身的大小, 比如我们拿girl1.png 的CCSpriteFrame 来看一下:

CCSpriteFrame *frame = cache->spriteFrameByName("girl1.png");

可以看到它在纹理地图里的位置是(194, 371) 而它的大小是 32x48:
182
181

再看一下 walk_left.png 的帧, 它的大小是 128x48:

184

(这里我做了一个艰难的决定, 上一次我们提到在用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" 这样的姿态躺著出现… 杯具了吧?

183

我们只好把行和列也计算在内, 写个函数来统一处理了:

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);

180

这次就写到这里了, 有时间再继续.

题外话:
我是刚开始学习 cocos2d-x 的, 所以有什麽地方写错了或写的不好, 欢迎大家指正赐教, 互相交流学习!

353

新手学习了,顶

现在用的2.04,发现有好几个函数变了啊,cocos2dx版本真快
楼主教程不错啊

沙发,哈哈,期待继续更新:lol

顶~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

mark 学习~~

期待更新:)

学习哈哈。给力的

学习哈哈。期待后续文章

CCMutableArray<CCSpriteFrame*> animFrames = new CCMutableArray<CCSpriteFrame>(count);

  1.    CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();
    
  2.    CCSpriteFrame *strip = cache->spriteFrameByName(name);
    
  3.    CCTexture2D *texture = strip->getTexture();
    

CCTexture2D *texture = strip->getTexture();
这里报错 好像 strip 对象是空的?什么情况?

你一定没有见过这么标准的15字

— Begin quote from ____

高负债 发表于 2012-5-22 15:17 url

CCMutableArray *animFrames = new CCMutableArray(count);

— End quote

是?行animation = SpriteHelper::animationWithStrip(“girl.png”, 4, 0.1f, 4, 4, 3); ?出了???

我?里???呢, 可否提供???的?料?
187

??大家的支持! :lol

  • 本帖最后由 高负债 于 2012-5-22 16:00 编辑 *

— Begin quote from ____

dr_watson 发表于 2012-5-22 15:44 url

是?行animation = SpriteHelper::animationWithStrip(“girl.png”, 4, 0.1f, 4, 4, 3); ?出了???

我? …

— End quote

我这边是样子CCAnimation* animation = animationWithStrip(“girl.png”, 4, 0.1f, 4, 4, 3);
cache->spriteFrameByName(name); 返回的 CCSpriteFrame 对象有问题
我跟进 spriteFrameByName 方法
查看 *frame 有问题
188

是"girl.png"图片没找到吗?我已经把图片拷贝到 cocos2dDebug.win32 目录下了。

好像也不是 图片没找到 我直接把图片贴到 CCSprite 上是可以显示出来的

  • 本帖最后由 dr_watson 于 2012-5-22 16:53 编辑 *

— Begin quote from ____

高负债 发表于 2012-5-22 15:50 url

我这边是样子CCAnimation* animation = animationWithStrip(“girl.png”, 4, 0.1f, 4, 4, 3);
cache->spri …

— End quote

你?有用我放在附件的VC?目?? 我不是用?立的 girl.png, 是用了?理地?: images.png, images.plist, 在 Resources 里.

— Begin quote from ____

dr_watson 发表于 2012-5-22 16:52 url

你?有用我放在附件的VC?目?? 我不是用?立的 girl.png, 是用了?理地?: images.png, images.plist, 在 …

— End quote

看你的代码
// using SpriteHelper::animationWithStrip to create animation
animation = SpriteHelper::animationWithStrip(“girl.png”, 4, 0.1f, 4, 4, 3);

	pSprite = CCSprite::spriteWithSpriteFrameName("girl1.png");
	pSprite->setPosition(ccp(130, size.height/2));
	pSprite->runAction(CCRepeatForever::actionWithAction(CCAnimate::actionWithAnimation(animation, false)));
	this->addChild(pSprite);

好像没用到plist

在研究你的代码中,非常感谢
我刚开始搞游戏。

明白了,是从 CCSpriteFrameCache::sharedSpriteFrameCache(); 里面取得 plist

— Begin quote from ____

高负债 发表于 2012-5-22 17:40 url

明白了,是从 CCSpriteFrameCache::sharedSpriteFrameCache(); 里面取得 plist

— End quote

是的, 在一??:

CCSpriteFrameCache *cache = CCSpriteFrameCache::sharedSpriteFrameCache();
cache->addSpriteFramesWithFile(“images.plist”, “images.png”);

整?例子是用了?理地?.