在第一篇《如何制作一个横版格斗过关游戏》基础上,增加角色运动、碰撞、敌人、AI和音乐音效,原文《How To Make A Side-Scrolling Beat ‘Em Up Game Like Scott Pilgrim with Cocos2D – Part 2》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。
步骤如下:
1.使用上一篇的工程;
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))); |
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); } } } |
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)); } } |
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让英雄更新它的期望位置,然后通过以下这些值,将期望位置进行检查是否在地图地板的范围内:
GameLayer还使用到了ActionSprite的两个测量值,centerToSides和centerToBottom,因为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(); |
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个动作的机器人:空闲、出拳、行走。它也有两个测量值:centerToBottom和centerToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。让我们开始添加一些机器人到游戏中去。打开GameLayer.h文件,添加如下代码:
1
|
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _robots, Robots);
|
1
|
#include
"Robot.h"
|
1
|
_robots =
NULL;
|
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(); } } |
这些代码做了以下事情:
编译运行,让英雄向前走,直到看到地图上的机器人,如下图所示:
试着走到机器人区域中,你会发现机器人的绘制有些不对。如果英雄是在机器人的下面,那么他应该被绘制在机器人的前面,而不是在后面。我们需要明确的告诉游戏,哪个对象先绘制,这就是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); } } |
1
|
this->reorderActors();
|
假如某个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() * 2, this->getCenterToBottom() * 2))); this->setAttackBox( this->createBoundingBoxWithOrigin(ccp( this->getCenterToSides(), - 10), CCSizeMake( 20, 20))); |
1
2 3 |
this->setHitbox(
this->createBoundingBoxWithOrigin(ccp(-
this->getCenterToSides(), -
this->getCenterToBottom()),
CCSizeMake( this->getCenterToSides() * 2, this->getCenterToBottom() * 2))); this->setAttackBox( this->createBoundingBoxWithOrigin(ccp( this->getCenterToSides(), - 5), CCSizeMake( 25, 20))); |
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. 0, 10. 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. 0, 10. 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()); } } } } } |
1
|
CC_SYNTHESIZE(
float, _nextDecisionTime, NextDecisionTime);
|
1
|
_nextDecisionTime =
0;
|
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. 1, 0. 5) * 1000); randomChoice = random_range( 0, 1); 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. 1, 0. 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. 5, 1. 0) * 1000); randomChoice = random_range( 0, 2); 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);
|
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)); } |
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()); } |
第一个方法创建显示一个重新开始的按钮,当按下它时,触发第二个方法。后者只是命令导演用新的GameScene实例替换当前场景。接着在updateRobots函数里面,在第一个end game checker here注释后面,添加如下代码:
1
2 3 4 |
if (_hero->getActionState() == kActionStateKnockedOut && _hud->getChildByTag(
5) ==
NULL)
{ this->endGame(); } |
1
2 3 4 |
if (alive ==
0 && _hud->getChildByTag(
5) ==
NULL)
{ this->endGame(); } |
12.音乐和音效。打开GameLayer.cpp文件,添加头文件引用:
1
|
#include
"SimpleAudioEngine.h"
|
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"); |
1
|
#include
"SimpleAudioEngine.h"
|
1
2 |
int randomSound = random_range(
0,
1);
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(CCString::createWithFormat( "pd_hit%d.wav", randomSound)->getCString()); |
1
|
virtual
void knockout();
|
1
|
#include
"SimpleAudioEngine.h"
|
1
2 3 4 5 |
void Hero::knockout()
{ ActionSprite::knockout(); CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect( "pd_herodeath.wav"); } |
1
|
#include
"SimpleAudioEngine.h"
|
1
2 3 4 5 |
void Robot::knockout()
{ ActionSprite::knockout(); CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect( "pd_botdeath.wav"); } |
参考资料:
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》