Cocos2d-x 精灵教程:第三部分

前言

我们在第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移动到这个位置去,再开始行走的动画。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值