• 如何让格斗游戏的横版过关(2) Cocos2d-x 2.0.4


     

    在第一章《如何使横版格戏》基础上。添加角色运动、碰撞、敌人、AI和音乐音效,原文《How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 2》,在这里继续以Cocos2d-x进行实现。有关源代码、资源等在文章以下给出了地址。

    过程例如以下:
    1.使用上一篇的project。
    2.移动英雄。

    在第一部分我们创建了虚拟方向键,可是还未实现按下方向键移动英雄,如今让我们进行实现。

    打开Hero.cpp文件,在init函数attack animation后面,加入例如以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    //walk animation
    CCArray *walkFrames = CCArray::createWithCapacity(8);
    for (i = 0; i < 8; i++)
    {
        CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_walk_%02d.png", i)->getCString());
        walkFrames->addObject(frame);
    }
    CCAnimation *walkAnimation = CCAnimation::createWithSpriteFrames(walkFrames, float(1.0 / 12.0));
    this->setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));
    打开ActionSprite.cpp文件,实现walkWithDirection方法,代码例如以下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    void ActionSprite::walkWithDirection(CCPoint direction)
    {
        if (_actionState == kActionStateIdle)
        {
            this->stopAllActions();
            this->runAction(_walkAction);
            _actionState = kActionStateWalk;
        }
        if (_actionState == kActionStateWalk)
        {
            _velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed);
            if (_velocity.x >= 0)
            {
                this->setScaleX(1.0);
            } 
            else
            {
                this->setScaleX(-1.0);
            }
        }
    }
    这段代码,检查前置动作状态是否空暇,若是的话切换动作到行走。在行走状态时。依据_walkSpeed值改变精灵速度。

    同一时候检查精灵的左右方向,并通过将精灵scaleX设置为1或-1来翻转精灵。要让英雄的行走动作跟方向键联系起来,须要借助方向键的托付:GameLayer类。

    打开GameLayer.cpp文件,实现例如以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    void GameLayer::didChangeDirectionTo(SimpleDPad *simpleDPad, CCPoint direction)
    {
        _hero->walkWithDirection(direction);
    }

    void GameLayer::isHoldingDirection(SimpleDPad *simpleDPad, CCPoint direction)
    {
        _hero->walkWithDirection(direction);
    }

    void GameLayer::simpleDPadTouchEnded(SimpleDPad *simpleDPad)
    {
        if (_hero->getActionState() == kActionStateWalk)
        {
            _hero->idle();
        }
    }

    此时。编译执行程序的话。通过方向键移动英雄,发现英雄仅仅是原地踏步。改变英雄的位置是ActionSprite和GameLayer共同的责任。一个ActionSprite永远不会知道它在地图上的位置。因此,它并不知道已经到达了地图的边缘。它仅仅知道它想去哪里。而GameLayer的责任就是将它的期望位置转换成实际的位置。打开ActionSprite.cpp文件。实现下面方法:

    1
    2
    3
    4
    5
    6
    7
     
    void ActionSprite::update(float dt)
    {
        if (_actionState == kActionStateWalk)
        {
            _desiredPosition = ccpAdd(this->getPosition(), ccpMult(_velocity, dt));
        }
    }
    这种方法在每次游戏更新场景的时候都会进行调用,当精灵处于行走状态时,它更新精灵的期望位置。

    位置+速度*时间。实际上就是意味着每秒移动X和Y点。打开GameLayer.cpp文件,在init函数this->initTileMap();后面加入例如以下代码:

    1
     
    this->scheduleUpdate();
    在析构函数,加入例如以下代码:
    1
    2
    3
    4
     
    GameLayer::~GameLayer(void)
    {
        this->unscheduleUpdate();
    }
    添加例如以下两个方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    void GameLayer::update(float dt)
    {
        _hero->update(dt);
        this->updatePositions();
    }

    void GameLayer::updatePositions()
    {
        float posX = MIN(_tileMap->getMapSize().width * _tileMap->getTileSize().width - _hero->getCenterToSides(),
            MAX(_hero->getCenterToSides(), _hero->getDesiredPosition().x));
        float posY = MIN(3 * _tileMap->getTileSize().height + _hero->getCenterToBottom(),
            MAX(_hero->getCenterToBottom(), _hero->getDesiredPosition().y));
        _hero->setPosition(ccp(posX, posY));
    }

    设定GameLayer的更新方法。每次循环时,GameLayer让英雄更新它的期望位置。然后通过下面这些值,将期望位置进行检查是否在地图地板的范围内:

    • mapSize:地图tile数量。总共同拥有10x100个tile。但仅仅有3x100属于地板。
      tileSize:每一个tile的尺寸,在这里是32x32像素。

    GameLayer还使用到了ActionSprite的两个測量值。centerToSidescenterToBottom,由于ActionSprite要想保持在场景内,它的位置不能超过实际的精灵边界。

    假如ActionSprite的位置在已经设置的边界内。则GameLayer让英雄达到期望位置,否则GameLayer会让英雄留停在原地。
    3.编译执行,此时点击方向键,移动英雄,例如以下图所看到的:

    可是,非常快你就会发现英雄能够走出地图的右边界,然后就这样从屏幕上消失了。


    4.以上的问题。能够通过基于英雄的位置进行滚动地图。这种方法在文章《怎样制作一个基于Tile的游戏》中有描写叙述过。

    打开GameLayer.cpp文件,在update函数里最后加入例如以下代码:

    1
     
    this->setViewpointCenter(_hero->getPosition());
    加入例如以下方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    void GameLayer::setViewpointCenter(CCPoint position)
    {
        CCSize winSize = CCDirector::sharedDirector()->getWinSize();

        int x = MAX(position.x, winSize.width / 2);
        int y = MAX(position.y, winSize.height / 2);
        x = MIN(x, (_tileMap->getMapSize().width * _tileMap->getTileSize().width) - winSize.width / 2);
        y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - winSize.height / 2);
        CCPoint actualPosition = ccp(x, y);

        CCPoint centerOfView = ccp(winSize.width / 2, winSize.height / 2);
        CCPoint viewPoint = ccpSub(centerOfView, actualPosition);
        this->setPosition(viewPoint);
    }

    以上代码让英雄处于屏幕中心位置,当然,英雄在地图边界时的情况除外。编译执行。效果例如以下图所看到的:

    5.创建机器人。我们已经创建了精灵的基本模型:ActionSprite。我们能够重用它来创建游戏中电脑控制的角色。新建Robot类,派生自ActionSprite类,添加例如以下方法:

    1
    2
     
    CREATE_FUNC(Robot);
    bool init();
    打开Robot.cpp文件,init函数代码例如以下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
     
    bool Robot::init()
    {
        bool bRet = false;
        do 
        {
            CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName("robot_idle_00.png"));
            
            int i;
            //idle animation
            CCArray *idleFrames = CCArray::createWithCapacity(5);
            for (i = 0; i < 5; i++)
            {
                CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(
                    CCString::createWithFormat("robot_idle_%02d.png", i)->getCString());
                idleFrames->addObject(frame);
            }
            CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames, float(1.0 / 12.0));
            this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation)));

            //attack animation
            CCArray *attackFrames = CCArray::createWithCapacity(5);
            for (i = 0; i < 5; i++)
            {
                CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(
                    CCString::createWithFormat("robot_attack_%02d.png", i)->getCString());
                attackFrames->addObject(frame);
            }
            CCAnimation *attackAnimation = CCAnimation::createWithSpriteFrames(attackFrames, float(1.0 / 24.0));
            this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create(this, callfunc_selector(Robot::idle)), NULL));

            //walk animation
            CCArray *walkFrames = CCArray::createWithCapacity(6);
            for (i = 0; i < 6; i++)
            {
                CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(
                    CCString::createWithFormat("robot_walk_%02d.png", i)->getCString());
                walkFrames->addObject(frame);
            }
            CCAnimation *walkAnimation = CCAnimation::createWithSpriteFrames(walkFrames, float(1.0 / 12.0));
            this->setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation)));

            this->setWalkSpeed(80.0);
            this->setCenterToBottom(39.0);
            this->setCenterToSides(29.0);
            this->setHitPoints(100.0);
            this->setDamage(10.0);

            bRet = true;
        } while (0);

        return bRet;
    }

    跟英雄一样,以上代码创建一个带有3个动作的机器人:空暇、出拳、行走。它也有两个測量值:centerToBottomcenterToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。

    让我们開始加入一些机器人到游戏中去。打开GameLayer.h文件,加入例如以下代码:

    1
     
    CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _robots, Robots);
    打开GameLayer.cpp文件,加入头文件例如以下:
    1
     
    #include "Robot.h"
    在构造函数里,加入例如以下代码:
    1
     
    _robots = NULL;
    init函数this->initTileMap();的后面加入例如以下代码:
    1
     
    this->initRobots();
    加入例如以下方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    void GameLayer::initRobots()
    {
        int robotCount = 50;
        this->setRobots(CCArray::createWithCapacity(robotCount));

        for (int i = 0; i < robotCount; i++)
        {
            Robot *robot = Robot::create();
            _actors->addChild(robot);
            _robots->addObject(robot);

            int minX = SCREEN.width + robot->getCenterToSides();
            int maxX = _tileMap->getMapSize().width * _tileMap->getTileSize().width - robot->getCenterToSides();
            int minY = robot->getCenterToBottom();
            int maxY = 3 * _tileMap->getTileSize().height + robot->getCenterToBottom();
            robot->setScaleX(-1);
            robot->setPosition(ccp(random_range(minX, maxX), random_range(minY, maxY)));
            robot->setDesiredPosition(robot->getPosition());
            robot->idle();
        }
    }

    这些代码做了下面事情:

    • 创建一个包括50个机器人的数组,并把它们加入到精灵表单中。

    • 使用Defines.h里面的随机函数随机放置50个机器人到地图地板上。

      同一时候,让最小随机值大于屏幕宽度,以确保不会有不论什么机器人出如今起点处。

    • 让每一个机器人都处于空暇状态。

    编译执行。让英雄向前走,直到看到地图上的机器人,例如以下图所看到的:

    试着走到机器人区域中,你会发现机器人的绘制有些不正确。假设英雄是在机器人的以下,那么他应该被绘制在机器人的前面,而不是在后面。

    我们须要明白的告诉游戏,哪个对象先绘制,这就是Z轴来进行控制的。加入英雄和机器人时。并没有明白指定其Z轴。默认下,后面加入的对象会比前面的对象Z轴值高。这就是为什么机器人挡住了英雄。

    为了解决问题,我们须要动态的处理Z轴顺序。每当精灵在屏幕上垂直移动时,它的Z轴值应该有所改变。屏幕上越高的精灵,其Z轴值应越低。打开GameLayer.cpp文件。加入例如以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    void GameLayer::reorderActors()
    {
        CCObject *pObject = NULL;
        CCARRAY_FOREACH(_actors->getChildren(), pObject)
        {
            ActionSprite *sprite = (ActionSprite*)pObject;
            _actors->reorderChild(sprite, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - sprite->getPosition().y);
        }
    }
    然后在update函数this->updatePositions();的后面,加入例如以下代码:
    1
     
    this->reorderActors();
    每当精灵的位置更新,这种方法会让CCSpriteBatchNode又一次设置它的每一个子节点Z轴值,其依据子节点离地图底部的距离。当子节点离底部更高时,其Z轴值就会下降。编译执行,能够看到正确的绘制顺序,例如以下图所看到的:

    6.出拳猛击机器人,碰撞检測。

    为了让英雄可以出拳。而且可以实际上打在了机器人身上。须要实现一种方式的碰撞检測。在这篇文章中。我们使用矩形创建一个很easy的碰撞检測系统。在这个系统中,我们为每一个角色定义两种矩形/盒子:

    • Hit box:代表精灵的身体
    • Attack box:代表精灵的手

    假如某个ActionSprite的Attack box碰撞到还有一个ActionSprite的Hit box。那么这就是一次碰撞发生。这两个矩形之间的差别,将帮助我们知道谁打了谁。Defines.h文件里的BoundingBox定义,包括两种矩形:实际的,和原始的:
    ①原始矩形。每一个精灵的基本矩形。一旦设置后就不会改变。
    ②实际矩形。这是位于世界空间中的矩形。当精灵移动时。实际的矩形也跟着变动。
    打开ActionSprite.h文件,加入例如以下代码:

    1
    2
    3
    4
     
    CC_SYNTHESIZE(BoundingBox, _hitBox, Hitbox);
    CC_SYNTHESIZE(BoundingBox, _attackBox, AttackBox);

    BoundingBox createBoundingBoxWithOrigin(cocos2d::CCPoint origin, cocos2d::CCSize size);

    以上创建了ActionSprite的两个包围盒:Hit box和Attack box。

    还定义了一个方法,用于依据给定的原点和大小来创建一个BoundingBox结构体。打开ActionSprite.cpp文件,加入例如以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    BoundingBox ActionSprite::createBoundingBoxWithOrigin(CCPoint origin, CCSize size)
    {
        BoundingBox boundingBox;
        boundingBox.original.origin = origin;
        boundingBox.original.size = size;
        boundingBox.actual.origin = ccpAdd(this->getPosition(), ccp(boundingBox.original.origin.x, boundingBox.original.origin.y));
        boundingBox.actual.size = size;
        return boundingBox;
    }

    void ActionSprite::transformBoxes()
    {
        _hitBox.actual.origin = ccpAdd(this->getPosition(), ccp(_hitBox.original.origin.x, _hitBox.original.origin.y));
        _attackBox.actual.origin = ccpAdd(this->getPosition(), ccp(_attackBox.original.origin.x + 
            (this->getScaleX() == -1 ?

     (- _attackBox.original.size.width - _hitBox.original.size.width) : 0),
            _attackBox.original.origin.y));
    }

    void ActionSprite::setPosition(CCPoint position)
    {
        CCSprite::setPosition(position);
        this->transformBoxes();
    }

    第一个方法创建一个新的包围盒,这有助于ActionSprite的子类创建属于它们自己的包围盒。

    第二个方法,基于精灵的位置、比例因子。和包围盒原本的原点和大小来更新每一个包围盒实际測量的原点和大小。之所以要用到比例因子,是由于它决定着精灵的方向。位于精灵右側的盒子,当比例因子设置为-1时,将会翻转到左側。

    打开Hero.cpp文件,在init函数后面加入例如以下代码:

    1
    2
    3
     
    this->setHitbox(this->createBoundingBoxWithOrigin(ccp(-this->getCenterToSides(), -this->getCenterToBottom()),
        CCSizeMake(this->getCenterToSides() * 2this->getCenterToBottom() * 2)));
    this->setAttackBox(this->createBoundingBoxWithOrigin(ccp(this->getCenterToSides(), -10), CCSizeMake(2020)));
    打开Robot.cpp文件,在init函数后面加入例如以下代码:
    1
    2
    3
     
    this->setHitbox(this->createBoundingBoxWithOrigin(ccp(-this->getCenterToSides(), -this->getCenterToBottom()),
        CCSizeMake(this->getCenterToSides() * 2this->getCenterToBottom() * 2)));
    this->setAttackBox(this->createBoundingBoxWithOrigin(ccp(this->getCenterToSides(), -5), CCSizeMake(2520)));
    如今我们已经有了英雄和机器人各自的Hit box和Attack box。

    假设是可视化的箱子,它们会像以下这样:

    不管何时。当一个attack box(红色)跟一个hit box(蓝色)交叉,即一次碰撞发生。在開始编写代码,检測包围盒交叉前,须要确保ActionSprite可以对被击中有所反应。我们已经加入了空暇、出拳、行走动作,但还未创建受伤和死亡动作。打开ActionSprite.cpp文件,实现例如以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    void ActionSprite::hurtWithDamage(float damage)
    {
        if (_actionState != kActionStateKnockedOut)
        {
            this->stopAllActions();
            this->runAction(_hurtAction);
            _actionState = kActionStateHurt;
            _hitPoints -= damage;

            if (_hitPoints <= 0)
            {
                this->knockout();
            }
        }
    }

    void ActionSprite::knockout()
    {
        this->stopAllActions();
        this->runAction(_knockedOutAction);
        _hitPoints = 0;
        _actionState = kActionStateKnockedOut;
    }

    仅仅要精灵还未死亡。被击中时状态将会切换到受伤状态。运行受伤动画,而且精灵的生命值将会减去对应的伤害值。假设生命值少于0,那么死亡的动作将会触发。

    为了完毕这两个动作,我们还需更改Hero类和Robot类。

    打开Hero.cpp文件,在init函数walk animation后面加入例如以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    //hurt animation
    CCArray *hurtFrames = CCArray::createWithCapacity(3);
    for (i = 0; i < 3; i++)
    {
        CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_hurt_%02d.png", i)->getCString());
        hurtFrames->addObject(frame);
    }
    CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, float(1.0 / 12.0));
    this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create(this, callfunc_selector(Hero::idle)), NULL));

    //knocked out animation
    CCArray *knockedOutFrames = CCArray::createWithCapacity(5);
    for (i = 0; i < 5; i++)
    {
        CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_knockout_%02d.png", i)->getCString());
        knockedOutFrames->addObject(frame);
    }
    CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, float(1.0 / 12.0));
    this->setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation), CCBlink::create(2.010.0), NULL));

    打开Robot.cpp文件,在init函数walk animation后面加入例如以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    //hurt animation
    CCArray *hurtFrames = CCArray::createWithCapacity(3);
    for (i = 0; i < 3; i++)
    {
        CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("robot_hurt_%02d.png", i)->getCString());
        hurtFrames->addObject(frame);
    }
    CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, float(1.0 / 12.0));
    this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create(this, callfunc_selector(Robot::idle)), NULL));

    //knocked out animation
    CCArray *knockedOutFrames = CCArray::createWithCapacity(5);
    for (i = 0; i < 5; i++)
    {
        CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("robot_knockout_%02d.png", i)->getCString());
        knockedOutFrames->addObject(frame);
    }
    CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, float(1.0 / 12.0));
    this->setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation), CCBlink::create(2.010.0), NULL));

    以上代码应该不陌生了。

    我们用创建其它动作相同的方式创建了受伤和死亡动作。受伤动作结束时。会切换到空暇状态。

    死亡动作结束时,精灵进行闪烁。

    打开GameLayer.cpp文件,加入碰撞处理。在ccTouchesBegan函数后面加入例如以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     
    if (_hero->getActionState() == kActionStateAttack)
    {
        CCObject *pObject = NULL;
        CCARRAY_FOREACH(_robots, pObject)
        {
            Robot *robot = (Robot*)pObject;
            if (robot->getActionState() != kActionStateKnockedOut)
            {
                if (fabsf(_hero->getPosition().y - robot->getPosition().y) < 10)
                {
                    if (_hero->getAttackBox().actual.intersectsRect(robot->getHitbox().actual))
                    {
                        robot->hurtWithDamage(_hero->getDamage());
                    }
                }
            }
        }       
    }
    以上代码通过三个简单步骤来检測碰撞:
    ①.检測英雄是否处于攻击状态。以及机器人是否处于非死亡状态。
    ②.检測英雄的位置和机器人的位置垂直相距在10个点以内。

    这表明它们在同一平面上站立。
    ③.检測英雄的attack box是否与机器人的hit box进行交叉。
    假设这些条件都成立,那么则一次碰撞发生,机器人运行受伤动作。英雄的伤害值作为參数进行传递,这样该方法就会知道须要减去多少生命值。
    7.编译执行,出拳攻击机器人吧。效果例如以下图所看到的:

    8.简单机器人AI的实现。

    为了使机器人可以移动,而且可以使用我们为它们所创建的动作,就须要开发一个简单的AI(人工智能)系统。这个AI系统基于决策机制。在特定的时间间隔里,我们给每一个机器人一个机会来决定接下来该做什么。

    它们须要知道的第一件事情就是何时做出选择。打开Robot.h文件,加入例如以下代码:

    1
     
    CC_SYNTHESIZE(float, _nextDecisionTime, NextDecisionTime);
    打开Robot.cpp文件。在init函数后面,加入例如以下代码:
    1
     
    _nextDecisionTime = 0;
    这个属性保存下一次机器人能够作出决定的时间。打开Defines.h文件。改动成例如以下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
     
    #pragma once
    #include "cocos2d.h"

    // 1 - convenience measurements
    #define SCREEN CCDirector::sharedDirector()->getWinSize()
    #define CENTER ccp(SCREEN.width / 2, SCREEN.height / 2)
    #define CURTIME GetCurTime()

    // 2 - convenience functions
    #ifndef UINT64_C
    #define UINT64_C(val) val##ui64
    #endif
    #define random_range(low, high) (rand() % (high - low + 1)) + low
    #define frandom (float)rand() / UINT64_C(0x100000000)
    #define frandom_range(low, high) ((high - low) * frandom) + low

    // 3 - enumerations
    typedef enum _ActionState {
        kActionStateNone = 0,
        kActionStateIdle,
        kActionStateAttack,
        kActionStateWalk,
        kActionStateHurt,
        kActionStateKnockedOut
    } ActionState;

    // 4 - structures
    typedef struct _BoundingBox {
        cocos2d::CCRect actual;
        cocos2d::CCRect original;
    } BoundingBox;

    inline float GetCurTime(){
        timeval time;
        gettimeofday(&time, NULL);
        unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec / 1000);
        return (float)millisecs;
    };

    打开GameLayer.cpp文件,加入例如以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
     
    void GameLayer::updateRobots(float dt)
    {
        int alive = 0;
        float distanceSQ;
        int randomChoice = 0;
        CCObject *pObject = NULL;
        CCARRAY_FOREACH(_robots, pObject)
        {
            Robot *robot = (Robot*)pObject;
            robot->update(dt);
            if (robot->getActionState() != kActionStateKnockedOut)
            {
                //1
                alive++;
                
                //2
                if (CURTIME > robot->getNextDecisionTime())
                {
                    distanceSQ = ccpDistanceSQ(robot->getPosition(), _hero->getPosition());

                    //3
                    if (distanceSQ <= 50 * 50)
                    {
                        robot->setNextDecisionTime(CURTIME + frandom_range(0.10.5) * 1000);
                        randomChoice = random_range(01);

                        if (randomChoice == 0)
                        {
                            if (_hero->getPosition().x > robot->getPosition().x)
                            {
                                robot->setScaleX(1.0);
                            } 
                            else
                            {
                                robot->setScaleX(-1.0);
                            }

                            //4
                            robot->setNextDecisionTime(robot->getNextDecisionTime() + frandom_range(0.10.5) * 2000);
                            robot->attack();                        
                            if (robot->getActionState() == kActionStateAttack)
                            {
                                if (fabsf(_hero->getPosition().y - robot->getPosition().y) < 10)
                                {
                                    if (_hero->getHitbox().actual.intersectsRect(robot->getAttackBox().actual))
                                    {
                                        _hero->hurtWithDamage(robot->getDamage());

                                        //end game checker here
                                    }
                                }
                            }
                        }
                        else
                        {
                            robot->idle();
                        }
                    }
                    else if (distanceSQ <= SCREEN.width * SCREEN.width)
                    {
                        //5
                        robot->setNextDecisionTime(CURTIME + frandom_range(0.51.0) * 1000);
                        randomChoice = random_range(02);
                        if (randomChoice == 0)
                        {
                            CCPoint moveDirection = ccpNormalize(ccpSub(_hero->getPosition(), robot->getPosition()));
                            robot->walkWithDirection(moveDirection);
                        } 
                        else
                        {
                            robot->idle();
                        }
                    }
                }
            }
        }

        //end game checker here
    }

    这是一个漫长的代码片段。

    将代码分解为一段段。

    对于游戏中的每一个机器人:
    ①.使用一个计数来保存仍然存活着的机器人数量。一个机器人仅仅要不是死亡状态,就被觉得仍然存活着。这将用于推断游戏是否应该结束。


    ②.检查当前应用程序时间的推移是否超过了机器人的下一次决定时间。假设超过了,意味着机器人须要作出一个新的决定。
    ③.检查机器人是否足够接近英雄,以便于有机会出拳攻击落在英雄身上。假设接近英雄了。那么就进行一个随机选择,看是要朝着英雄出拳,还是要继续空暇着。
    ④.假如机器人决定攻击。我们就用之前检測英雄攻击时同样的方式来进行检測碰撞。仅仅是这一次,英雄和机器人的角色互换了。


    ⑤.假设机器人和英雄之间的距离小于屏幕宽度。那么机器人将作出决定。要么朝着英雄移动,要么继续空暇。机器人的移动基于英雄位置和机器人位置产生的法向量。
    每当机器人作出决定。它的下一个决定的时间被设定为在未来的一个随机时间。在此期间。它将继续运行上次作出决定时所做出的动作。

    接着在update函数里,this->updatePositions();前加入例如以下代码:

    1
     
    this->updateRobots(dt);
    updatePositions函数后面。加入例如以下代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    CCObject *pObject = NULL;
    CCARRAY_FOREACH(_robots, pObject)
    {
        Robot *robot = (Robot*)pObject;
        posX = MIN(_tileMap->getMapSize().width * _tileMap->getTileSize().width - robot->getCenterToSides(),
            MAX(robot->getCenterToSides(), robot->getDesiredPosition().x));
        posY = MIN(3 * _tileMap->getTileSize().height + robot->getCenterToBottom(),
            MAX(robot->getCenterToBottom(), robot->getDesiredPosition().y));
        robot->setPosition(ccp(posX, posY));
    }
    确保每次游戏循环时,机器人AI方法都被调用。遍历每一个机器人。并让它们朝着期望的位置进行移动。


    9.编译执行,将会看到沿着走廊过来的机器人。

    效果例如以下图所看到的:

    10.为游戏加入又一次開始的button。打开GameLayer.cpp文件,加入头文件引用:

    1
     
    #include "GameScene.h"
    加入例如以下方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    void GameLayer::endGame()
    {
        CCLabelTTF *restartLabel = CCLabelTTF::create("RESTART""Arial"30);
        CCMenuItemLabel *restartItem = CCMenuItemLabel::create(restartLabel, this, menu_selector(GameLayer::restartGame));
        CCMenu *menu = CCMenu::create(restartItem, NULL);
        menu->setPosition(CENTER);
        menu->setTag(5);
        _hud->addChild(menu, 5);
    }

    void GameLayer::restartGame(CCObject* pSender)
    {
        CCDirector::sharedDirector()->replaceScene(GameScene::create());
    }

    第一个方法创建显示一个又一次開始的button。当按下它时。触发第二个方法。后者仅仅是命令导演用新的GameScene实例替换当前场景。

    接着在updateRobots函数里面,在第一个end game checker here凝视后面,加入例如以下代码:

    1
    2
    3
    4
     
    if (_hero->getActionState() == kActionStateKnockedOut && _hud->getChildByTag(5) == NULL)
    {
        this->endGame();
    }
    在第二个end game checker here凝视后面,加入例如以下代码:
    1
    2
    3
    4
     
    if (alive == 0 && _hud->getChildByTag(5) == NULL)
    {
        this->endGame();
    }
    这些语句都是检測游戏结束的条件。第一个检測英雄被机器人攻击后,是否还存活着。假设英雄死亡了,那么游戏就结束了。第二个检測是否全部的机器人都死亡了。

    假设都死亡了,那么游戏也结束了。另外。在endGame方法里,能够看到游戏结束菜单的tag值为5。

    由于检測是在循环里面,须要确保游戏结束菜单之前没被创建过。

    否则的话。将会一直创建游戏结束菜单。
    11.编译执行,能够看到游戏结束时的样子,例如以下图所看到的:

    12.音乐和音效。

    打开GameLayer.cpp文件,加入头文件引用:

    1
     
    #include "SimpleAudioEngine.h"
    init函数里,CC_BREAK_IF(!CCLayer::init());后面加入例如以下代码:
    1
    2
    3
    4
    5
    6
    7
     
    // Load audio
    CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic("latin_industries.aifc");
    CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("latin_industries.aifc");
    CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_hit0.wav");
    CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_hit1.wav");
    CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_herodeath.wav");
    CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("pd_botdeath.wav");
    打开ActionSprite.cpp文件。加入头文件引用:
    1
     
    #include "SimpleAudioEngine.h"
    hurtWithDamage函数,第一个条件语句里加入例如以下代码:
    1
    2
     
    int randomSound = random_range(01);
    CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(CCString::createWithFormat("pd_hit%d.wav", randomSound)->getCString());
    打开ActionSprite.h文件。将knockout方法声明改动例如以下:
    1
     
    virtual void knockout();
    打开Hero.cpp文件,加入头文件引用:
    1
     
    #include "SimpleAudioEngine.h"
    加入例如以下方法:
    1
    2
    3
    4
    5
     
    void Hero::knockout()
    {
        ActionSprite::knockout();
        CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pd_herodeath.wav");
    }
    打开Robot.cpp文件,加入头文件引用:
    1
     
    #include "SimpleAudioEngine.h"
    加入例如以下方法:
    1
    2
    3
    4
    5
     
    void Robot::knockout()
    {
        ActionSprite::knockout();
        CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pd_botdeath.wav");
    }
    13.编译执行。如今游戏将有配乐。效果图:

    參考资料:
    1.How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 2http://www.raywenderlich.com/24452/how-to-make-a-side-scrolling-beat-em-up-game-like-scott-pilgrim-with-cocos2d-part-2
    2.怎样使用cocos2d制作类似Scott Pilgrim的2D横版格斗过关游戏part2(翻译) http://blog.sina.com.cn/s/blog_4b55f6860101aaav.html

    很感谢以上资料。本样例源码附加资源下载地址http://download.csdn.net/detail/akof1314/5056794
    如文章存在错误之处,欢迎指出,以便改正

    扩展:
    对此演示样例的内存泄露修正说明:《Cocos2d-x 2.0.4 小心隐藏的retain

    转自:http://blog.csdn.net/akof1314/article/details/8572546


    其他版本:http://www.apkbus.com/android-143363-1-1.html




  • 相关阅读:
    Document
    Document
    Document
    Document
    Document
    Document
    8. vue 的生命周期
    7. vue-cli 安装和使用脚手架
    5.组件(2) 之 父级传子级
    6.组件(3) 之 子级传父级
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4614517.html
Copyright © 2020-2023  润新知