Cocos2d-x 精灵教程

2014.10.31 技术干货 by cocos

前言

朋友们,欢迎回来!今天,我们将要征服Cocos2d里面的精灵。这个过程并不会像你想像中那么难,接下来的教程,我就会证明给你看。首先,我们有N种方法在屏幕上显示一张图片。。。其实,我们在《Cocos2d-x 菜单教程:第三部分》就已经知道一种显示图片的方式了。

那么,我们今天将学习哪些内容呢?我们将学习有关 “Sprite”, “SpriteSheets”, “SpriteFrame”,以及“Texture2d” 和 “TextureCache”的一切!在这篇教程的最后,我们将有一条龙在一个简单的背景地形上面飞,路径由用户的手指滑动touch决定。很酷吧?

这里有本教程的完整源代码

在这个教程中,我们还是学习一些基础知识–不过没有菜单啦,我们主要关心的是精灵(sprite)。先看一看整个教程最后的产品是什么样子吧!如下图所示:

image

是否有点激动了呢?我敢打赌你现在很激动。。。不管怎么说,直接切入主题吧。。。

首先,我们使用上个教程的 SceneManager.h/.cpp ,这里我们只创建“PlayLayer”类。

我们首先要做的就是把“dragon.png”图片拖到Resources文件夹下去,它是一张750×560的图片,我把它缩小显示在下图。你可以从这里下载完整大小的图片资源。

那里面还有一张地形背景图,它的大小为480×320,我也把它缩小显示出来了:

image

像之前的菜单教程一样,把这里图片添加进resources分组下面去–你按住ctrl-click在“Resources”文件夹里面挑选你想要添加进工程的图片,记住,如果图片不是放在Reources文件夹下,那么最好选中 “copy to directory”复选框。

好了,一旦你添加进去以后,它看起来的结构如下图所示:

image

我们的工程将包含两个类(delegate类除外): SceneManager 和 PlayLayer。我们已经知道SceneManger是如何工作的了,这里就不再啰嗦了。(可以参考《Cocos2d菜单教程:第一部分》),但是,下面是我移除掉一些菜单选项后的SceneManager类。

SceneManager.h

#pragma once
#include "cocos2d.h"
#include "PlayLayer.h"

USING_NS_CC;

class SceneManager: public Object 
{
public:
    static void goPlay();
    static void go(Layer* layer);
    static Scene* wrap(Layer* layer);
};

SceneManager.cpp

#include "SceneManager.h"

void SceneManager::goPlay()
{
    auto layer = PlayLayer::create();
    SceneManager::go(layer);
}

void SceneManager::go(Layer* layer)
{
    auto director = Director::getInstance();
    auto newScene = SceneManager::wrap(layer);

    if (director->getRunningScene())
    {
        director->replaceScene(newScene);
    }
    else
    {
        director->runWithScene(newScene);
    }
}

Scene* SceneManager::wrap(Layer* layer)
{
    auto newScene = Scene::create();
    newScene->addChild(layer);
    return newScene;
}

一定要记得,把delegate类里面的Director replaceScene调用,改成 “[SceneManager goPlay];”调用。

好,如果你们都看了菜单教程的话,那么看到这里,你们可能会觉得烦了。所以,来点新鲜的吧:

PlayLayer.h

#pragma once
#include "cocos2d.h"
#include "SceneManager.h"

USING_NS_CC;

class PlayLayer: public Layer
{
public:
    Vector<Action*>* flyActionArray;
    Sprite* dargon;
    Action* flyAction;
    Action* moveAction;
    bool moving;

    CREATE_FUNC(PlayLayer);
    bool virtual init();
};

那么,这里做了些什么事呢?我们创建了三个实例变量:_dragon, _flyAction and _moveAction.从名字差不多也可以看出来它们到底是干什么用的,_dragon是我们的主角精灵,_flyAnimmation负责处理dragon的煽动翅膀的动画,而_moveAction负责处理从一点移动到另一个点。

因此,Sprite非常重要,但是,你可能会问你自己,我到底是应该继承CCSprite,还是包含一个Sprite实例呢?我这里不使用一个Dragon类来继承Sprite的原因是你可以从文件中加载一张图片来初始化它。这里有一个非常著名的问题:“Is-a”还是“has-a”?好吧,我的喜好是派生至Node,然后把所有的Sprite当作它的属性。因为,我相信这样做会给你最大的灵性性。我承认,如果从Sprite继承的话,刚开始会有许多方便之处,比如可以直接添加到BatchNode等。但是,我还是坚信,把CCSprite当作一个属性的话,你可以在以后的编程中获得巨大的好处。

PlayLayer.cpp

#include "PlayLayer.h"

enum {ktagSpriteSheet = 1,};

bool PlayLayer::init()
{
    if (!Layer::init())
    {
        return false;
    }

    auto background = Sprite::create("Terrain.png");
    background->setPosition(ccp(160,240));
    this->addChild(background);

    auto texture = TextureCache::getInstance()->addImage("dragon.png");
    auto sheet = SpriteBatchNode::create("dragon.png",10);
    this->addChild(sheet, 0, ktagSpriteSheet);
    Size s = Director::getInstance()->getWinSize();

    Vector<SpriteFrame*> animFrames;
    for (int i = 0; i < 8; i++)
    {
        animFrames.clear();
        for (int j = 0; j < 10; j++)
        {
            auto frame = SpriteFrame::createWithTexture(texture, Rect(j * 75, i * 70, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));
            animFrames.pushBack(frame);
        }

        auto animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
        auto animate = Animate::create(animation);
        auto seq = Sequence::create(animate, NULL);

        this->flyAction = RepeatForever::create(seq);
        flyActionArray->pushBack(this->flyAction);
    }

    auto frame1 = SpriteFrame::createWithTexture(texture, Rect(0, 0, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));

    this->dargon = Sprite::createWithSpriteFrame(frame1);
    dargon->setPosition(ccp(s.width/2-80,s.height/2));

    sheet->addChild(dargon);

    this->flyAction = flyActionArray->at(0);
    dargon->runAction(flyAction);

    return true;
}

ok,上面的代码看起来比较长,不过没关系,让我们分别解释下:

首先是加载背景图片

auto background = Sprite::create("Terrain.png");
background->setPosition(ccp(160,240));
this->addChild(background);

背景图片加载后放置在屏幕的中心(默认情况下,图片的anchorPoint是图片的中心,你可以使用anchorPoint属性来改变它–举个例子:把背景的anchorPoint从中心点

background->setAnchorPoint(cpp(0.5,0.5));

改成图片的左下角:

background->setAnchorPoint(cpp(0.0,0.0));

接下来:

auto texture = TextureCache::getInstance()->addImage("dragon.png");
auto sheet = SpriteBatchNode::create("dragon.png",10);
this->addChild(sheet, 0, ktagSpriteSheet);

TextureCache和Texture2D是n种方式中的一种,可以把一张图片加载到Sprite中去。我是认真的,真的有n种方法可以做这个事情。。。现在,我不想让你头晕,所以先不列举其它方法了。。。让我们先看看这段代码都做了些什么吧。首先,加载一张dragon.png图片到texture对象中。

因此,接下来,好多人都对SpriteBatchNode的使用感到迷惑不解。一个SpriteBatchNode是一种效率比较高的渲染精灵的方式。比如,你把Sprite加到CCLayer中,那么sprite的draw函数在每一帧调用时都会执行7个opengl 调用来完成sprite的渲染。一个精灵的时候当然没问题,但是,当你在屏幕上有200个精灵的时候,那么就会有200×7次opengl调用。。。而SpriteBatchNode可以“批处理”它的孩子精灵的draw调用。这意味着,当把200个精灵加到SpriteBatchNode中去的时候,只要使用7个opengl调用就可以完成200个孩子的渲染了。在本例中,我们看到dragon类也有许多精灵,所以我们要使用SpriteBatchNode。

更新:关于CCSpriteBatchNode的误解,请参看泰然论坛这个帖子

1.通过使用纹理集(texture atlas),你可以加快游戏的加载时间,同时减少耗费的内存资源。

2.通过使用SpriteSheet(SpriteBatchNode),你可以提供渲染的性能,(具体查看Performance Tests)

3.通过使用TextureCache和SpriteSheet,那么你可以同时获得加载时间的性能和渲染的性能提升。

当然,现在只有一只龙,可能看不出明显的效果提升。但是,这是最佳实践,你最好一开始就按照这种方式去做,它会为你以后省去很多麻烦事。

Vector<SpriteFrame*> animFrames;
for (int i = 0; i < 8; i++)
{
    animFrames.clear();

    for (int j = 0; j < 10; j++)
    {
        auto frame = SpriteFrame::createWithTexture(texture, Rect(j * 75, i * 70, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));
        animFrames.pushBack(frame);
    }

    auto animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
    auto animate = Animate::create(animation);
    auto seq = Sequence::create(animate, NULL);

    this->flyAction = RepeatForever::create(seq);
    flyActionArray->pushBack(this->flyAction);
}

上面这个部分似乎做了很多事情–但是,实际上很简单。我们使用SpriteFrame来创建精灵动画帧,一个SpriteFrame就是从texture里面抠出来的一块小图片区域,需要指定矩形区域。它并不包含图片本身,而是更像一帧图像。每一次,动画运行的时候,就会运行一系列的SpriteFrames,而每个SpriteFrames指向texture里的一小块图片。

因为我们的dragon每个动画都有10张图片,总共有8个方向飞行的动画(对应东南西北8个方向的动画)。我们创建了80个帧,然后把每一个方向飞行的动画都存到Vector(SpriteFrame*)里面。然后,我们把所有的flyAction再添加到flyActionArray里面去。

auto frame1 = SpriteFrame::createWithTexture(texture, Rect(0, 0, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));

this->dargon = Sprite::createWithSpriteFrame(frame1);
dargon->setPosition(ccp(s.width/2-80,s.height/2));

sheet->addChild(dargon);

this->flyAction = flyActionArray->at(0);
dargon->runAction(flyAction);

因为dragon刚开始时需要有一张图片作为初始状态,所以,我们从animation里面取出第一帧(位置在0,0,宽度是75,高度是70)。我们把dragon精灵初始位置设在屏幕中间偏左一点,然后把它加到spritesheet中,目的是为了获得更好的性能提升。

然后,我们开始运行第一个动画。。。现在,我们可以飞啦!

下篇教程见!


后记:这里使用的方法,说实话,真的过时了。:)不过我们了解了也是有好处的。之前翻译的ray的教程里面,都是使用texturePacker生成pvr.ccz和plist文件来处理的。获得动画帧也很简单,直接spriteFrameByName就可以了。

原文链接地址:http://www.iphonegametutorials.com/2010/09/10/cocos2d-sprite-tutorial/

 

上一篇教程中,我们留下了我们孤独的dragon在屏幕中间。。。然而,90%的动画并没有应用到,那可是我们花了很大力气才建立好的呀!太遗憾了!所以,我们这篇教程要弥补这个缺憾。我们将添加touch控制,以此来捕获用户的输入,并根据用户手指的方向来选择一个合适的动画给dragon播放。你就可以用手在屏幕上滑动来指挥dragon移动了。

这里有本教程的完整源代码

这里介绍一下Cocos2d-x 3.0新的触摸机制:

首先我们需要让layer能接收touch事件。Cocos2d-x 3.0增加了新的事件分发机制,并且让setTouchEnabled为deprecated的方法。对某个方法和类标注deprecated的意思就是这个方法或类不再建议使用。所以我们继承虚函数onEnter,并重写:

void PlayLayer::onEnter()
{
    Layer::onEnter();

    auto listener = EventListenerTouchOneByOne::create();
    listener->setSwallowTouches(true);

    listener->onTouchBegan = CC_CALLBACK_2(PlayLayer::onTouchBegan, this);
    listener->onTouchMoved = CC_CALLBACK_2(PlayLayer::onTouchMoved, this);
    listener->onTouchEnded = CC_CALLBACK_2(PlayLayer::onTouchEnded, this);

    auto dispatcher = Director::getInstance()->getEventDispatcher();
    dispatcher->addEventListenerWithSceneGraphPriority(listener, this);

这里我们将要声明 “onTouchBegan” 和“onTouchEnded” –第一个方法当第一个touch事件开始的时候被调用,另外一个是touch结束的时候被调用。还有一个 “onTouchMoved”方法,它是你的手一直按在屏幕上面的时候被调用的,在Cocos2d-x 3.0 中,响应者三种事件的函数可以自己绑定自己写好的函数。代码如下,

listener->onTouchBegan = CC_CALLBACK_2(PlayLayer::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(PlayLayer::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(PlayLayer::onTouchEnded, this);

因此,现在我们有工具和函数了,为什么不编写一些代码呢?

Point moveVector = touchLocation - dragon->getPosition();

float distanceToMove = ccpLength(moveVector);
float moveAngle = ccpToAngle(moveVector);
float cocosAngle = CC_RADIANS_TO_DEGREES(-1 * moveAngle);

float dragonVelocity = 480.0 / 3.0;
float moveDuration = distanceToMove / dragonVelocity;

cocosAngle += 23;
if (cocosAngle < 0)
{
    cocosAngle += 360;
}

int runAnim = (int)((cocosAngle)/45);

因此,现在我们需要计算出如何旋转dragon:

  1. 计算touch点到dragon当前位置的向量
  2. 计算这个向量的长度(这里是像素长度)
  3. 计算move向量的弧度
  4. 把弧度转换成角度(因为Cocos2d-x使用的是角度)

为了得到统一的移动方法,而不用管dragon目前处在屏幕的哪个位置,我们设置了一个变量,保存dragon移动的速度常量(通过距离/时间计算得到的)。然后,我们把move向量的长度除以这个速度,就可以得到MoveTo action所需要的duration了)

这里我还做了一些有趣的事–因为得到的度数范围是-180到180,所以我把它加上360,这样的话,就不会有负数。而当我把这个度数再除以45的时候,就可以得到一个整数,这个整数刚好可以满足我之前定义的8(360/45 = 8)个方向的动画数组。也就是通过这个整数取得一个动画,然后给dragon播放。我这里还加了一个23,因为,当我在dragon的东边点击的时候,我不想让它在0~8这8个动画挑选的时候很迷茫。通过加上这个小的偏移,我可以计算得到精确的动画索引。(比如,第0个动画,当-23到23度的时候,我们都得到的是0)。如果你还是觉得很迷惑的话,不妨把“cocosAngle += 23″这句代码注释掉,然后看一看这句代码到底干了一件什么事。

dragon->stopAction(flyAction);
this->flyAction = flyActionArray.at(runAnim);
dragon->runAction(flyAction);

this->moveAction = Sequence::create(MoveTo::create(moveDuration,touchLocation),NULL);
dragon->runAction(moveAction);

最后,我们停止先前的动画,然后基于前面计算的结果“int runAnim = (int)((cocosAngle)/45);”来选择一个新的动画让dragon来run。这样的话,当我们在屏幕上面点击的时候,就可以让dragon朝着正确的方向移动,并且播放正确的动画了。第2部分教程到这里就结束了,让我们第3部分教程见!

原文链接地址:http://www.iphonegametutorials.com/2010/09/10/cocos2d-sprite-tutorial-part-2/

 

我们在第2部分教程中已经介绍了如何让dragon沿着8个不同的方向移动,并且播放相应的动画,同时,移动过程可以由用户touch屏幕来控制。Cocos2d-x 很酷吧!好了,今天我们将多干点活,我们将创建一大批村民–实际上是N个村民。我们会使用我们已经学习过的技术,从spritesheet里面加载精灵,同时建立相应的精灵动画。

这里有本教程的完整源代码

那么,我们到底要做成什么样子呢—看了下面的图就明白了:

image

上面加载了好多村民,但是,屏幕的帧速率仍然是60 fps。这是因为我们做了优化。那么,究竟是如何做的呢?让我们马上开始学习吧。

首先,我们要创建我们的adventurer (冒险者)类。—它里面存储了我们的移动和行走动画的精灵实例。在屏幕上每一个冒险家,我们都会为之创建一个adventurer 类的实例。

Adventurer.h

#include "cocos2d.h"

USING_NS_CC;

class Adventurer: public Node {
    Sprite* charSprite;
    Action* walkAction;
    Action* moveAction;
    bool moving;
};

如果你愿意的话,你也可以从Sprite继承,然后我们可以调用initWithFile方法来初始化我们的Adventure 类。但是,我更喜欢从Node继承,然后包含一个Sprite的实例。

Adventurer.cpp

#include "Adventurer.h"

bool Adventurer::init()
{
    if (!Node::init())
    {
        return false;
    }

    return true;
}

很简单的init函数,上面这段代码,我们再熟悉不过了。这里创建了一个非常简单的类,但是,也给我们一些提示,如何为游戏主角创建class。

现在,我们拥有角色了,让我们来使用之。。。先回到“PlayLayer.h” ,然后做下面一些变更:

#pragma once
#include "cocos2d.h"
#include "SceneManager.h"

USING_NS_CC;

class PlayLayer: public Layer
{
public:
    Vector<Action*> flyActionArray;
    Texture2D* texture;
    SpriteBatchNode* spritesheet;
    Vector<char*> charArray;
    Sprite* dragon;
    Action* flyAction;
    Action* moveAction;
    bool moving;

    CREATE_FUNC(PlayLayer);

    bool virtual init();
    void virtual onEnter();
    bool onTouchBegan(Touch *touch, Event *event);
    void onTouchMoved(Touch *touch, Event *event);
    void onTouchEnded(Touch* touch, Event* event);
};

我们先导入 “Adventurer.h”,然后定义了3个实例变量。第一个变量 “texture”用来加载adventurer 精灵表单。第二变量 “spritesheet”是把我们将要创建的精灵都进行“批处理”,使之提高效率。最后,我们想要追踪所有的adventurers,所以,我们定义了一个“charArray”.数组。同时我们为每一个实例变量都声明了属性,这样我们就可以在PlayLayer.m间接使用了。(另一种方法是定义tag,在init方法里面指定tag,然后在其它方法里面就可以用self getChildByTag:tag来获得想要的孩子了)

OK,现在我们有一堆类了。不过别担心,我们会在后面把它逐步分开讲解–首先,先让我们实现

PlayLayer.cpp:

#include "PlayLayer.h"
enum {ktagSpriteSheet = 1,};

bool PlayLayer::init()
{
    if (!Layer::init())
    {
        return false;
    }

    auto background = Sprite::create("Terrain.png");
    background->setPosition(ccp(160,240));
    this->addChild(background);

    auto texture = TextureCache::getInstance()->addImage("dragon.png");
    auto sheet = SpriteBatchNode::create("dragon.png",10);
    this->addChild(sheet, 0, ktagSpriteSheet);

    Size s = Director::getInstance()->getWinSize();
    Vector<SpriteFrame*> animFrames;
    for (int i = 0; i < 8; i++)
    {
        animFrames.clear();
        for (int j = 0; j < 10; j++)
        {
            auto frame = SpriteFrame::createWithTexture(texture, Rect(j * 75, i * 70, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));
            animFrames.pushBack(frame);
        }

        auto animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
        auto animate = Animate::create(animation);
        auto seq = Sequence::create(animate, NULL);

        this->flyAction = RepeatForever::create(seq);
        flyActionArray.pushBack(this->flyAction);
    }

    auto frame1 = SpriteFrame::createWithTexture(texture, Rect(0, 0, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));
    this->dragon = Sprite::createWithSpriteFrame(frame1);
    dragon->setPosition(ccp(s.width/2-80,s.height/2));

    sheet->addChild(dragon);
    this->flyAction = flyActionArray.at(0);
    dragon->runAction(flyAction);

    texture = TextureCache::getInstance()->addImage("adventurer.png");
    spriteSheet = SpriteBatchNode::createWithTexture(texture,100);
    this->addChild(spriteSheet,0,ktagSpriteSheet);
    this->schedule(schedule_selector(PlayLayer::gameLogic), 1.0f);

    return true;
}

void PlayLayer::addAdventurer()
{
    Vector<SpriteFrame*>animFrames;
    animFrames.clear();

    for (int i = 0; i < 9; i++) 
    {
        auto *frame = SpriteFrame::createWithTexture(this->texture, Rect(i * 16, 0, 16, 29));
        animFrames.pushBack(frame);
    }

    auto adventurer = Adventurer::create();
    if (adventurer != NULL) {
        auto *frame1 = SpriteFrame::createWithTexture(this->texture, Rect(0, 0, 19, 29));
        adventurer->charSprite = Sprite::createWithSpriteFrame(frame1);

        Size s = Director::getInstance()->getWinSize();
        int minY = adventurer->charSprite->getContentSize().width/2;
        int maxY = s.height - adventurer->charSprite->getContentSize().height / 2;
        int rangeY = maxY - minY;
        int actualY = (CCRANDOM_0_1() * rangeY) + minY;

        int minX = -300;
        int maxX = 0;
        int rangeX = maxX - minX;
        int actualX = (CCRANDOM_0_1() * rangeX) + minX;

        adventurer->charSprite->setPosition(ccp(actualX, actualY));
        auto *animation = Animation::createWithSpriteFrames(animFrames,0.2);
        auto *animate = Animate::create(animation);
        auto *seq = Sequence::create(animate,NULL);
        adventurer->walkAction = RepeatForever::create(seq);

        auto actionMove = MoveTo::create(10.0f,ccp(s.width + 200, actualY));
        auto actionMoveDone = CallFuncN::create(CC_CALLBACK_1(PlayLayer::spriteMoveFinished,this,(void*)adventurer));
        adventurer->moveAction = Sequence::create(actionMove,actionMoveDone,NULL);

        adventurer->charSprite->runAction(adventurer->walkAction);
        adventurer->charSprite->runAction(adventurer->moveAction);

        this->addChild(adventurer->charSprite)
        charArray.pushBack(adventurer);
    }
}

void PlayLayer::spriteMoveFinished(Node* sender, void* adv)
{
    auto adventurer = (Adventurer*)adv;
    Size s = Director::getInstance()->getWinSize();

    int minY = adventurer->charSprite->getContentSize().height / 2;
    int maxY = s.height - adventurer->charSprite->getContentSize().height / 2;
    int rangeY = maxY - minY;
    int actualY = (CCRANDOM_0_1() * rangeY) + minY;

    int minX = -300;
    int maxX = 0;
    int rangeX = maxX - minX;
    int actualX = (CCRANDOM_0_1() * rangeX) + minX;

    adventurer->charSprite->setPosition(ccp(actualX, actualY));
    adventurer->stopAction(adventurer->moveAction);
    adventurer->charSprite->runAction(adventurer->moveAction);
}

void PlayLayer::gameLogic(float dt)
{
    this->addAdventurer();
}

void PlayLayer::onEnter()
{
    Layer::onEnter();

    auto listener = EventListenerTouchOneByOne::create();
    listener->setSwallowTouches(true);

    listener->onTouchBegan = CC_CALLBACK_2(PlayLayer::onTouchBegan, this);
    listener->onTouchMoved = CC_CALLBACK_2(PlayLayer::onTouchMoved, this);
    listener->onTouchEnded = CC_CALLBACK_2(PlayLayer::onTouchEnded, this);

    auto dispatcher = Director::getInstance()->getEventDispatcher();
    dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}

void PlayLayer::onTouchEnded(Touch* touch,Event* event) 
{
    Point touchLocation = this->convertToWorldSpace(this->convertTouchToNodeSpace(touch));
    Point moveVector = touchLocation - dragon->getPosition();

    float distanceToMove = ccpLength(moveVector);
    float moveAngle = ccpToAngle(moveVector);
    float cocosAngle = CC_RADIANS_TO_DEGREES(-1 * moveAngle);

    float dragonVelocity = 480.0 / 3.0;
    float moveDuration = distanceToMove / dragonVelocity;

    cocosAngle += 23;
    if (cocosAngle < 0)
    {
        cocosAngle += 360;
    }

    int runAnim = (int)((cocosAngle)/45);

    dragon->stopAction(flyAction);
    this->flyAction = flyActionArray.at(runAnim);
    dragon->runAction(flyAction);

    this->moveAction = Sequence::create(MoveTo::create(moveDuration,touchLocation),NULL);
    dragon->runAction(moveAction);
}

OK,千万别头疼–接下来我会一点点分解:

bool PlayLayer::init()
{
    if (!Layer::init())
    {
        return false;
    }

    auto background = Sprite::create("Terrain.png");
    background->setPosition(ccp(160,240));
    this->addChild(background);

    auto texture = TextureCache::getInstance()->addImage("dragon.png");
    auto sheet = SpriteBatchNode::create("dragon.png",10);
    this->addChild(sheet, 0, ktagSpriteSheet);

    Size s = Director::getInstance()->getWinSize();
    Vector<SpriteFrame*> animFrames;
    for (int i = 0; i < 8; i++)
    {
        animFrames.clear();
        for (int j = 0; j < 10; j++)
        {
            auto frame = SpriteFrame::createWithTexture(texture, Rect(j * 75, i * 70, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));
            animFrames.pushBack(frame);
        }

        auto animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
        auto animate = Animate::create(animation);
        auto seq = Sequence::create(animate, NULL);

        this->flyAction = RepeatForever::create(seq);
        flyActionArray.pushBack(this->flyAction);
    }

    auto frame1 = SpriteFrame::createWithTexture(texture, Rect(0, 0, 75, 70), false, Director::getInstance()->getVisibleOrigin(), Size(75, 70));

    this->dragon = Sprite::createWithSpriteFrame(frame1);
    dragon->setPosition(ccp(s.width/2-80,s.height/2));
    sheet->addChild(dragon);

    this->flyAction = flyActionArray.at(0);
    dragon->runAction(flyAction);

    texture = TextureCache::getInstance()->addImage("adventurer.png");
    spriteSheet = SpriteBatchNode::createWithTexture(texture,100);
    this->addChild(spriteSheet,0,ktagSpriteSheet);

    this->schedule(schedule_selector(PlayLayer::gameLogic), 1.0f);
    return true;
}

好,首先看到“init”函数,它和我们之前的adventurer 类一样,先调super init,如果失败的话,就直接返回nil。然后我们添加了一张背景图片叫做”Terrain.png”并把它放置在屏幕的中心(因为我们知道图片的默认中心点anchorPoint是0.5,0.5)。然后直接把它加到当前层中。

接下来,我们初始化纹理和SpriteBatchNode–首先把”adventurer.png”加载到CCTexture2D变量中,然后使用createWithTexture来建立一个精灵表单。(我们也可以用create这个函数来建立SpriteBatchNode,但是,我想向你展示另外一种方法)。然后,我们把spritesheet加到Layer中。之后,我们所有的精灵,如果作为孩子加到SpriteBatchNode中的话,就可以得到“批处理”。

最后,我们触发一个回调函数gamelogic,它会每隔1秒钟回调一次。函数如下所示:

void PlayLayer::gameLogic(float dt)
{
    this->addAdventurer();
}

我们将使用这个函数,每隔一秒钟创建一个新的adventurer 对象。

接下来,看看AddAventurer函数。这个函数不仅仅创建一个新的角色,同时还会使之移动并播放相应方向行走的动画。

void PlayLayer::addAdventurer()
{
    Vector<SpriteFrame*>animFrames;
    animFrames.clear();

    for (int i = 0; i < 9; i++) 
    {
        auto *frame = SpriteFrame::createWithTexture(this->texture, Rect(i * 16, 0, 16, 29));
        animFrames.pushBack(frame);
    }

上面的代码我们之前已经见过了,我们只是为walking动画存储了9个动画帧(SpriteFrames)。

    auto adventurer = Adventurer::create();
    if (adventurer != NULL) 
    {
        auto *frame1 = SpriteFrame::createWithTexture(this->texture, Rect(0, 0, 19, 29));
        adventurer->charSprite = Sprite::createWithSpriteFrame(frame1);

接下来,我们创建一个新的adventurer 实例,然后把charSprite成员初始化为第一个动画帧,调用的方法是createWithSpriteFrame。

        Size s = Director::getInstance()->getWinSize();

        int minY = adventurer->charSprite->getContentSize().width/2;
        int maxY = s.height - adventurer->charSprite->getContentSize().height / 2;
        int rangeY = maxY - minY;
        int actualY = (CCRANDOM_0_1() * rangeY) + minY;

        int minX = -300;
        int maxX = 0;
        int rangeX = maxX - minX;
        int actualX = (CCRANDOM_0_1() * rangeX) + minX;

        adventurer->charSprite->setPosition(ccp(actualX, actualY));

好了,即使我们的精灵按照粒子数量去增加,所有的精灵刚开始的位置都是在左下角,除非我们人为改变它们的位置。因此,上面的代码就是产生一个随机坐标,同时又要保证这个随机坐标在屏幕范围之内。然后把这个随机坐标点赋值给adventurer。

        auto *animation = Animation::createWithSpriteFrames(animFrames,0.2);
        auto *animate = Animate::create(animation);
        auto *seq = Sequence::create(animate,NULL);
        adventurer->walkAction = RepeatForever::create(seq);

        auto actionMove = MoveTo::create(10.0f,ccp(s.width + 200, actualY));
        auto actionMoveDone = CallFuncN::create(CC_CALLBACK_1(PlayLayer::spriteMoveFinished,this,(void*)adventurer));
        adventurer->moveAction = Sequence::create(actionMove,actionMoveDone,NULL);

        adventurer->charSprite->runAction(adventurer->walkAction);
        adventurer->charSprite->runAction(adventurer->moveAction);

        this->addChild(adventurer->charSprite);
        charArray.pushBack(adventurer);
    }
}

addAdventurer方法的最后一个部分就是处理角色在屏幕上面的行走和移动。我们把之前存储CCSpriteFrame 的animFrames拿过来,然后把它转换成动画。(每个动画帧之间的间隔是0.2秒,整个动画差不多就要2秒的时间来运行完)。然后我们把这个动画放到一个sequence 动作中(使用CCSequence 类),最后,我们使用CCRepeatForever创建walkAction,并把它赋值给adventurer。

我们现在已经可以让角色有行走的动画了,但是,我们还想让它实际移动。所以,我们需要创建另外一个action,叫做CCMoveTo 。并且使用CCSequence类把它与一个回调函数关联起来。当CCMoveTo结束的时候,就运行CCCallFuncN指定的回调函数。

Side Note: 如果你想指定带一个参数的函数,那么就使用CCCallFuncN–它会把Sprite本身传递进去,通过sender参数传递:

auto actionMoveDone = CallFuncN::create(this,callfuncN_selector(PlayLayer::spriteMoveFinished));

如果你不想让任何参数传递的话,就使用CCCallFunc函数。

现在,我们还剩下一件事情没有涉及了,就是之前CCMove结束的时候,通过CCCallFuncND指定的回调函数,如下所示:

void PlayLayer::spriteMoveFinished(Node* sender, void* adv)
{
    auto adventurer = (Adventurer*)adv;

    Size s = Director::getInstance()->getWinSize();

    int minY = adventurer->charSprite->getContentSize().height / 2;
    int maxY = s.height - adventurer->charSprite->getContentSize().height / 2;
    int rangeY = maxY - minY;
    int actualY = (CCRANDOM_0_1() * rangeY) + minY;

    int minX = -300;
    int maxX = 0;
    int rangeX = maxX - minX;
    int actualX = (CCRANDOM_0_1() * rangeX) + minX;

    adventurer->charSprite->setPosition(ccp(actualX, actualY));
    adventurer->stopAction(adventurer->moveAction);
    adventurer->charSprite->runAction(adventurer->moveAction);
}

这里再重复解释上面的代码的话,就有点烦人了。简言之,就是在CCMoveTo结束之后,随机再生成一个x,y坐标,然后让advertuere移动到这个位置去,再开始行走的动画。

原文链接地址:http://www.iphonegametutorials.com/2010/09/14/cocos2d-sprite-tutorial-part-3/