cocos2d-x 4.0 学习之路(十七)第一个小游戏--忍者来袭--升级改造

还记得我们的第一个小游戏–忍者来袭吗?
那个有点太Easy了,敌人对主角一点威胁都没有。那我们今天就改造一下,让敌人也会发子弹,而且一旦敌人的子弹或者敌人碰到主角的话,就Game Over.
首先,我们想一下如何设计,让敌人也会发飞镖?我想要的效果是敌人在移动的过程中,随机位置发射一枚飞镖。

我最开始的想法是,在addMonster()中,做一个敌人的飞镖精灵,它的起始点是敌人随机移动的位置,终点当然是player的位置。但是,Moster是一直在运动的,如何设定飞镖的起始点让我犯了愁。后来我意识到,飞镖是敌人发出来了,那么可以把发射飞镖作为monster的一个动作,加到动作序列(Sequence)里面啊。于是,用CallFunc写个回调函数shootStar,在shootStar里面写发射飞镖,这样运行起来符合预期而且代码也很容易理解。

那么修改代码思路如下:原来monster只有一个MoveTo的Action,那我们改成MoveTo -> shootStar -> MoveTo这样的动作序列不就结了。而且前半段MoveTo的距离是随机产生的。于是代码如下:

    // 2 Let monster run
    int maxX = selfContentSize.width;
    int randomX = maxX - (rand() % (int)(maxX / 2));
    float monsterSpeed = 160;
    float randomDuration2 = (float)randomX / monsterSpeed;
    float randomDuration1 = (float)(maxX - randomX) / monsterSpeed;
    auto move1 = MoveTo::create(randomDuration1, Vec2(randomX, randomY));
    auto move2 = MoveTo::create(randomDuration2, Vec2(-monsterContentSize.width / 2, randomY));

    // 定义消除的Object。怪物移出屏幕后被消除,释放资源。
    auto actionRemove = RemoveSelf::create();

    // Shoot Enemy's Projectile
    auto shootStar = CallFunc::create([=]() {
        // 创建敌人飞镖
        Sprite* eProjectile = Sprite::create("Projectile_enemy.png");
        eProjectile->setPosition(monster->getPosition());

        // Add Enemy's Projectile's physicsBody
        auto physicsBody = PhysicsBody::createBox(eProjectile->getContentSize(), PhysicsMaterial(0.0f, 0.0f, 0.0f));
        physicsBody->setDynamic(false);
        physicsBody->setCategoryBitmask(3);
        physicsBody->setContactTestBitmask(4);
        eProjectile->setPhysicsBody(physicsBody);
        eProjectile->setTag(ENEMY_PROJECTILE);
        this->addChild(eProjectile);
        // 发射敌人飞镖
        float starSpeed = 120;
        float starDuration = (float)randomX / starSpeed;
        auto projectileMove = MoveTo::create(starDuration, _player->getPosition());
        auto projectileRemove = RemoveSelf::create();
        eProjectile->runAction(Sequence::create(projectileMove, projectileRemove, nullptr));
    });
    // 敌人发射飞镖时,停顿一下
    DelayTime* delay = cocos2d::DelayTime::create(0.05);
    monster->runAction(Sequence::create(move1, delay, shootStar, move2, actionRemove, nullptr));

为了使动作更逼真一点,我在发射飞镖的时候让敌人停顿了一下。
注意,代码里的CallFunc::create([=],中括号里一定得是=号,不能用&哦,因为它里面用到了monster这个局部变量。关于这里捕获模式的知识点可以参考这篇博文。好了,看一下效果:
cocos2d-x 4.0 学习之路(十七)第一个小游戏--忍者来袭--升级改造_第1张图片
敌人的飞镖也开始发射了,接下来,我们要设计一个相对复杂的碰撞检测(严格来说应该是接触检测)。也就是:

  1. 敌人的飞镖或者敌人碰到player,做游戏结束处理。
  2. player的飞镖碰到敌人或者敌人的飞镖,会将它们都消灭。
  3. player的飞镖对自己是没有影响的,敌人的飞镖对自己也是没有影响的。

怎么还会有第3条?你认为是理所当然的,程序可不是那么认为。你要知道,player和它的飞镖是两个不同的精灵,而player发射飞镖的瞬间它们是相互接触的。如果设计不恰当,飞镖就会把自己杀死。(我在做的过程中就经历了这场悲剧)

关于如何判断接触检测产生消息回调,请参考我的上一篇博文。
根据我们需求,我设计的setCategoryBitmask和setContactTestBitmask掩码如下:(这个有N多种设计,大家可以多想想,还是比较练脑子的)

        &操作之后,若都不为0,产生回调消息;否则不产生回调消息
        player:                 setCategoryBitmask(5);       // 0101
                                setContactTestBitmask(1);    // 0001
        player's projectile:    setCategoryBitmask(5);       // 0101
                                setContactTestBitmask(2);    // 0010
        monster:                setCategoryBitmask(3);       // 0011
                                setContactTestBitmask(1);    // 0001
        monster's projectile:   setCategoryBitmask(3);       // 0011
                                setContactTestBitmask(4);    // 0100

好了,有了这个宝典,代码实现就比较容易了。还需要注意的是,我们不需要用真正的物理碰撞,所以需要每个精灵的动态属性设为false,physicsBody->setDynamic(false); 这个在上一篇博文也有介绍。
以player举例就是这样子的:

    // Add _player's physicsBody
    auto physicsBody = PhysicsBody::createBox(_player->getContentSize(), PhysicsMaterial(0.0f, 0.0f, 0.0f));
    physicsBody->setDynamic(false);
    physicsBody->setCategoryBitmask(5);
    physicsBody->setContactTestBitmask(1);
    _player->setPhysicsBody(physicsBody);
    _player->setTag(ME);
    this->addChild(_player);

最后,当然是修改一下接触消息的处理。其实也比较简单,之前player飞镖的判断不用修改,可以继续使用。再加上player接触的处理就可以了。那我们需要给player也加一个标签。为了调试方便,我给4个精灵都分别加上了标签。

#define MY_PROJECTILE 10
#define ENEMY_PROJECTILE 20
#define ME 100
#define ENEMY 200

GameOver的处理,我是做了一个GameOver的Scene,里面只有一张图片。然后做一下场景切换,就OK了。

bool NA_Level1::onContactBegin(cocos2d::PhysicsContact& contact)
{
    auto nodeA = contact.getShapeA()->getBody()->getNode();
    auto nodeB = contact.getShapeB()->getBody()->getNode();

    if (nodeA && nodeB)
    {
        int tagA = nodeA->getTag();
        int tagB = nodeB->getTag();
        log("onContact!! tagA = %d, tagB = %d", tagA, tagB);

        // 当player的飞镖碰到敌人或者敌人的飞镖,将它们杀死
        if (tagA == MY_PROJECTILE) {
            nodeB->removeFromParentAndCleanup(true);
        }
        if (tagB == MY_PROJECTILE) {
            nodeA->removeFromParentAndCleanup(true);
        }

        // 当player碰到碰到敌人或者敌人的飞镖,GameOver处理
        if (tagA == ME || tagB == ME){
            log("Game over");
            Director::getInstance()->replaceScene(TransitionSlideInT::create(0.2f, GameOver::createScene()));
        }
    }
    return true;
}

代码部分就都介绍完了,整体代码在这里。看一下最终效果。
cocos2d-x 4.0 学习之路(十七)第一个小游戏--忍者来袭--升级改造_第2张图片

你可能感兴趣的:(cocos2d-x)