cocos2d-x快乐的做让人快乐的游戏5:一个横版ARPG过关游戏

在第一篇《 如何制作一个横版格斗过关游戏》基础上,增加角色运动、碰撞、敌人、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
//walkanimation
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
voidActionSprite::walkWithDirection(CCPointdirection)
{
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
voidGameLayer::didChangeDirectionTo(SimpleDPad*simpleDPad,CCPointdirection)
{
_hero->walkWithDirection(direction);
}

voidGameLayer::isHoldingDirection(SimpleDPad*simpleDPad,CCPointdirection)
{
_hero->walkWithDirection(direction);
}

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

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

1
2
3
4
5
6
7
voidActionSprite::update( floatdt)
{
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
voidGameLayer::update( floatdt)
{
_hero->update(dt);
this->updatePositions();
}

voidGameLayer::updatePositions()
{
floatposX=MIN(_tileMap->getMapSize().width*_tileMap->getTileSize().width-_hero->getCenterToSides(),
MAX(_hero->getCenterToSides(),_hero->getDesiredPosition().x));
floatposY=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函数里最后添加如下代码:

this->setViewpointCenter(_hero->getPosition());
添加如下方法:
voidGameLayer::setViewpointCenter(CCPointposition)
{
CCSizewinSize=CCDirector::sharedDirector()->getWinSize();

intx=MAX(position.x,winSize.width/ 2);
inty=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);
CCPointactualPosition=ccp(x,y);

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

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

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

1
2
CREATE_FUNC(Robot);
boolinit();
打开 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
boolRobot::init()
{
boolbRet= false;
do
{
CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName( "robot_idle_00.png"));

inti;
//idleanimation
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)));

//attackanimation
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));

//walkanimation
CCArray*walkFrames=CCArray::createWithCapacity( 6);
for(i= 0;i< 6;i++)
{
CCSpriteFrame*frame=CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(
CCString::createWithFormat( "robot_walk_%02d.png",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);

returnbRet;
}

跟英雄一样,以上代码创建一个带有3个动作的机器人:空闲、出拳、行走。它也有两个测量值:centerToBottomcenterToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。让我们开始添加一些机器人到游戏中去。打开GameLayer.h文件,添加如下代码:

CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*,_robots,Robots);
打开 GameLayer.cpp文件,添加头文件如下:
#include "Robot.h"
在构造函数里,添加如下代码:
_robots= NULL;
init函数 this->initTileMap();的后面添加如下代码:
this->initRobots();
添加如下方法:
voidGameLayer::initRobots()
{
introbotCount= 50;
this->setRobots(CCArray::createWithCapacity(robotCount));

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

intminX=SCREEN.width+robot->getCenterToSides();
intmaxX=_tileMap->getMapSize().width*_tileMap->getTileSize().width-robot->getCenterToSides();
intminY=robot->getCenterToBottom();
intmaxY= 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文件,添加如下方法:

voidGameLayer::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();的后面,添加如下代码:
this->reorderActors();
每当精灵的位置更新,这个方法会让 CCSpriteBatchNode重新设置它的每个子节点Z轴值,其根据子节点离地图底部的距离,当子节点离底部更高时,其Z轴值就会下降。编译运行,可以看到正确的绘制顺序,如下图所示:

6.出拳猛击机器人,碰撞检测。为了让英雄能够出拳,并且能够实际上打在了机器人身上,需要实现一种方式的碰撞检测。在这篇文章中,我们使用矩形创建一个非常简单的碰撞检测系统。在这个系统中,我们为每个角色定义两种矩形/盒子:
  • Hit box:代表精灵的身体
  • Attack box:代表精灵的手

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

CC_SYNTHESIZE(BoundingBox,_hitBox,Hitbox);
CC_SYNTHESIZE(BoundingBox,_attackBox,AttackBox);

BoundingBoxcreateBoundingBoxWithOrigin(cocos2d::CCPointorigin,cocos2d::CCSizesize);

以上创建了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
BoundingBoxActionSprite::createBoundingBoxWithOrigin(CCPointorigin,CCSizesize)
{
BoundingBoxboundingBox;
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;
returnboundingBox;
}

voidActionSprite::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));
}

voidActionSprite::setPosition(CCPointposition)
{
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)));
打开 Robot.cpp文件,在 init函数后面添加如下代码:
5),CCSizeMake( 25,20)));
现在我们已经有了英雄和机器人各自的Hit box和Attack box。如果是可视化的箱子,它们会像下面这样:

无论何时,当一个attack box(红色)跟一个hit box(蓝色)交叉,即一次碰撞发生。在开始编写代码,检测包围盒交叉前,需要确保 ActionSprite能够对被击中有所反应。我们已经添加了空闲、出拳、行走动作,但还未创建受伤和死亡动作。打开 ActionSprite.cpp文件,实现如下方法:
voidActionSprite::hurtWithDamage( floatdamage)
{
if(_actionState!=kActionStateKnockedOut)
{
this->stopAllActions();
this->runAction(_hurtAction);
_actionState=kActionStateHurt;
_hitPoints-=damage;

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

voidActionSprite::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
//hurtanimation
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),callfunc_selector(Hero::idle)),NULL));

//knockedoutanimation
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后面添加如下代码:

//hurtanimation
CCArray*hurtFrames=CCArray::createWithCapacity( 3);
for(i= 0;i< 3;i++)
{
CCSpriteFrame*frame=CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat( "robot_hurt_%02d.png",NULL));

//knockedoutanimation
CCArray*knockedOutFrames=CCArray::createWithCapacity( 5);
for(i= 0;i< 5;i++)
{
CCSpriteFrame*frame=CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat( "robot_knockout_%02d.png",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文件,添加如下代码:
CC_SYNTHESIZE( float,_nextDecisionTime,NextDecisionTime);
打开 Robot.cpp文件,在 init函数后面,添加如下代码:
_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
#pragmaonce
#include "cocos2d.h"

//1-conveniencemeasurements
#defineSCREENCCDirector::sharedDirector()->getWinSize()
#defineCENTERccp(SCREEN.width/ 2,SCREEN.height/ 2)
#defineCURTIMEGetCurTime()

//2-conveniencefunctions
#ifndefUINT64_C
#defineUINT64_C(val)val##ui64
#endif
#definerandom_range(low,high)(rand()%(high-low+ 1))+low
#definefrandom( float)rand()/UINT64_C(0x100000000)
#definefrandom_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::CCRectactual;
cocos2d::CCRectoriginal;
}BoundingBox;

inline floatGetCurTime(){
timevaltime;
gettimeofday(&time,NULL);
unsigned longmillisecs=(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
voidGameLayer::updateRobots( floatdt)
{
intalive= 0;
floatdistanceSQ;
intrandomChoice= 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());

//endgamecheckerhere
}
}
}
}
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)
{
CCPointmoveDirection=ccpNormalize(ccpSub(_hero->getPosition(),robot->getPosition()));
robot->walkWithDirection(moveDirection);
}
else
{
robot->idle();
}
}
}
}
}

//endgamecheckerhere
}

这是一个漫长的代码片段。将代码分解为一段段。对于游戏中的每个机器人:
①.使用一个计数来保存仍然存活着的机器人数量。一个机器人只要不是死亡状态,就被认为仍然存活着。这将用于判断游戏是否应该结束。
②.检查当前应用程序时间的推移是否超过了机器人的下一次决定时间。如果超过了,意味着机器人需要作出一个新的决定。
③.检查机器人是否足够接近英雄,以便于有机会出拳攻击落在英雄身上。如果接近英雄了,那么就进行一个随机选择,看是要朝着英雄出拳,还是要继续空闲着。
④.假如机器人决定攻击,我们就用之前检测英雄攻击时相同的方式来进行检测碰撞。只是这一次,英雄和机器人的角色互换了。
⑤.如果机器人和英雄之间的距离小于屏幕宽度,那么机器人将作出决定,要么朝着英雄移动,要么继续空闲。机器人的移动基于英雄位置和机器人位置产生的法向量。
每当机器人作出决定,它的下一个决定的时间被设定为在未来的一个随机时间。在此期间,它将继续执行上次作出决定时所做出的动作。接着在update函数里,this->updatePositions();前添加如下代码:

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.为游戏添加重新开始的按钮。打开 GameLayer.cpp文件,添加头文件引用:
#include "GameScene.h"
添加如下方法:
voidGameLayer::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);
}

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

第一个方法创建显示一个重新开始的按钮,当按下它时,触发第二个方法。后者只是命令导演用新的GameScene实例替换当前场景。接着在updateRobots函数里面,在第一个end game checker here注释后面,添加如下代码:

if(_hero->getActionState()==kActionStateKnockedOut&&_hud->getChildByTag( 5)== NULL)
{
this->endGame();
}
在第二个 end game checker here注释后面,添加如下代码:
if(alive== 0&&_hud->getChildByTag( 5)== NULL)
{
this->endGame();
}
这些语句都是检测游戏结束的条件。第一个检测英雄被机器人攻击后,是否还存活着。如果英雄死亡了,那么游戏就结束了。第二个检测是否所有的机器人都死亡了。如果都死亡了,那么游戏也结束了。另外,在 endGame方法里,可以看到游戏结束菜单的 tag值为5。因为检测是在循环里面,需要确保游戏结束菜单之前没被创建过。否则的话,将会一直创建游戏结束菜单。
11.编译运行,可以看到游戏结束时的样子,如下图所示:

12.音乐和音效。打开GameLayer.cpp文件,添加头文件引用:

#include "SimpleAudioEngine.h"
init函数里, CC_BREAK_IF(!CCLayer::init());后面添加如下代码:
//Loadaudio
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文件,添加头文件引用:
#include "SimpleAudioEngine.h"
hurtWithDamage函数,第一个条件语句里添加如下代码:
intrandomSound=random_range( 0,1);
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(CCString::createWithFormat( "pd_hit%d.wav",randomSound)->getCString());
打开 ActionSprite.h文件,将 knockout方法声明修改如下:
virtual voidknockout();
打开 Hero.cpp文件,添加头文件引用:
#include "SimpleAudioEngine.h"
添加如下方法:
1
2
3
4
5
voidHero::knockout()
{
ActionSprite::knockout();
CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect( "pd_herodeath.wav");
}
打开 Robot.cpp文件,添加头文件引用:
voidRobot::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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是