如何制作一个横版格斗过关游戏 cocos2d 2.0.4 下载-x 2.0.4

使用Cocos2D-X如何实现《热血格斗传说》中的地图形式?
具体就是像这种。这里贴个游戏的下载链接:还请各位老师指导一下~
按投票排序
由于之前刚山寨了《热血足球》,热血系列的问题是必答的~ArtS说的应该是不同平面高度处理的逻辑(也就是如何处理图上两个敌方角色在软垫上的逻辑),我感觉答主的疑问应该更基础一些,如Frank Lin回答的,就是如何在2d渲染中体现出这种beat'em up(类似热血,双截龙)游戏的3d感觉,我就来解释个详细版吧。首先如Lin说的,你需要搞两套坐标系,一套是现成的渲染屏幕坐标,x,y对应就是屏幕上的具体位置,这套是纯2d的。第二套是游戏世界坐标,这套是3d,也就是x,y,z,如下图这点想明白的话,首先我们需要做的就是实现一个坐标转换函数用来将游戏内人物的世界坐标转换为最后的屏幕坐标,然后交给引擎处理就可以。这种过程实际在一般3d游戏教程里有学过,也就是视差投影,简单的说根据n个参数组成一个矩阵,通过矩阵和坐标向量相乘,划去一个维度(3维转2维)。当然在热血格斗里,我们可以简单一点,设世界坐标为Pw(x,y,z),屏幕坐标Ps(x,y).那么公式就是Ps.x = Pw.x;Ps.y = a * Pw.y + b * Pw.z;a和b是我们需要定的系数,一般是常量。我们先不考虑z轴,普通状况下都为0,因为人物的z值只有在高度变化时才会改变,就是譬如人物走上高台,或者跳跃。那么如果你希望你的人物在y方向上移动速度看上去和于x方向上相同,则a可以设为1,如果慢点则可以设为小于1的值(一般游戏都会是小于1的),具体多少你可以自己调试确定。Z的系数也是一样的,当人物跳跃的时候,数值计算在独立的z变量上,在最后坐标系转换的时候加到Ps.y里就可以。ArtS说的应该是不同地形高度处理的逻辑,这个我没有实现过,我就随便说说吧,你可以先不考虑tile map(tile map不是必须的,甚至场景编辑器也不是必须的),先考虑场景就是一张大图, 图上有A,B,C三个区域,A区域的z值为0,B的z值为10,C为20(这些数据可以在场景编辑器里设定好,或者直接硬代码)。这就是涉及到你代码里怎么写场景和人物的碰撞逻辑了,我的想法是这样,场景数据里会记录可以直接移动的各个区域,譬如A,B,C,实际上都是最简单的AABB,每帧会检测人物的位置和高度去和上一帧的信息去比较,如果还是在同一个区域内就直接可以移动,不需要做高度判断。如果不是,譬如上一帧人物在A区域,这一帧按照人物的跳跃前进速度,他已经到了b区域,那么就要在检测人物当前的高度Z,如果Z值大于等于B区域的Z值(跳的足够高),那么就允许人物移动到B区域,其实也不需要特殊处理Z,只要当人物下落的时候,当Z值小于等于B区域的Z值,就停止下落即可。反之则卡在A和B的边缘上,别的区域也是同理。斜坡我没怎么想好,希望有思路的朋友说说,我就懒得搜了~
谢邀,猜测题主困惑点是如何在这个2D画面风格里做出有高低不同层的立体感。提供个思路:tiledMap 每个地面给个Z值,小人sprite在不同Z值之间的地面移动时根据Z值决定Y坐标偏移,跳跃动画、跳跃过程中锚点轨迹计算衔接好就可以。Z值和Y坐标偏移量的比例跟美术画tile图的时候协调一致。补充:建议去找几个tiledMap/瓦片地图技术的例子学习一下,简单来说这个地图是由很多小方块的sprites组成,每个sprites都有一个z值代表地面高度。假如有两个相邻的地图spriteA z=0、spriteB z=1,小人spriteP现在在A上要往B上走,伪伪代码:没有Z值前的简单行走就是spriteP.position = spriteB.position,加上z值后就是if (spriteB.z & spriteA.z) {spriteP.position = ccp(spriteB.position.x, spriteB.position.y + kRatio * (spriteB.z - spriteA.z));}kRatio是每1Z值代表多少"点"高度,这个比值跟美术素材协调一致我这个例子里行走行为是简单的移动过去为了说明终点位置确认,具体项目里怎么移动用ccAction还是每帧update锚点需要你自己设计操作方式、移动方式后再定,如果要平滑的跳跃动画你还得解决跳跃抛物线的轨迹计算呃。。。怎么会有不宜公开讨论的政治内容呢。。。。
谢邀。请善用搜索引擎,这次我先帮你:如果你要说地图高度这块,我没有实践过,想法就是有两个坐标数值,一个是代表角色在场景中位置的坐标(x,y,z),一个是通过公式和条件转换到实际显示中的坐标(x,y)。
已有帐号?
无法登录?
社交帐号登录cocos2d-x(79)
在第一篇《》基础上,增加角色运动、碰撞、敌人、AI和音乐音效,原文《》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。
步骤如下:
1.使用上一篇的工程;
2.移动英雄。在第一部分我们创建了虚拟方向键,但是还未实现按下方向键移动英雄,现在让我们进行实现。打开Hero.cpp文件,在init函数attack animation后面,添加如下代码:
//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方法,代码如下:
void ActionSprite::walkWithDirection(CCPoint direction)
if (_actionState == kActionStateIdle)
this-&stopAllActions();
this-&runAction(_walkAction);
_actionState = kActionStateW
if (_actionState == kActionStateWalk)
_velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed);
if (_velocity.x &= 0)
this-&setScaleX(1.0);
this-&setScaleX(-1.0);
这段代码,检查前置动作状态是否空闲,若是的话切换动作到行走。在行走状态时,根据_walkSpeed值改变精灵速度。同时检查精灵的左右方向,并通过将精灵scaleX设置为1或-1来翻转精灵。要让英雄的行走动作跟方向键联系起来,需要借助方向键的委托:GameLayer类。打开GameLayer.cpp文件,实现如下方法:
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文件,实现以下方法:
void ActionSprite::update(float dt)
if (_actionState == kActionStateWalk)
_desiredPosition = ccpAdd(this-&getPosition(), ccpMult(_velocity, dt));
这个方法在每次游戏更新场景的时候都会进行调用,当精灵处于行走状态时,它更新精灵的期望位置。位置+速度*时间,实际上就是意味着每秒移动X和Y点。打开GameLayer.cpp文件,在init函数this-&initTileMap();后面添加如下代码:
this-&scheduleUpdate();
在析构函数,添加如下代码:
GameLayer::~GameLayer(void)
this-&unscheduleUpdate();
增加如下两个方法:
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的两个测量值,centerToSides和centerToBottom,因为ActionSprite要想保持在场景内,它的位置不能超过实际的精灵边界。假如ActionSprite的位置在已经设置的边界内,则GameLayer让英雄达到期望位置,否则GameLayer会让英雄留停在原地。
3.编译运行,此时点击方向键,移动英雄,如下图所示:
但是,很快你就会发现英雄可以走出地图的右边界,然后就这样从屏幕上消失了。
4.以上的问题,可以通过基于英雄的位置进行滚动地图,这个方法在文章《》中有描述过。打开GameLayer.cpp文件,在update函数里最后添加如下代码:
this-&setViewpointCenter(_hero-&getPosition());
添加如下方法:
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类,增加如下方法:
CREATE_FUNC(Robot);
bool init();
打开Robot.cpp文件,init函数代码如下:
bool Robot::init()
bool bRet =
CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName(&robot_idle_00.png&));
//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);
} while (0);
跟英雄一样,以上代码创建一个带有3个动作的机器人:空闲、出拳、行走。它也有两个测量值:centerToBottom和centerToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。让我们开始添加一些机器人到游戏中去。打开GameLayer.h文件,添加如下代码:
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _robots, Robots);
打开GameLayer.cpp文件,添加头文件如下:
#include &Robot.h&
在构造函数里,添加如下代码:
_robots = NULL;
在init函数this-&initTileMap();的后面添加如下代码:
this-&initRobots();
添加如下方法:
void GameLayer::initRobots()
int robotCount = 50;
this-&setRobots(CCArray::createWithCapacity(robotCount));
for (int i =0; i & robotC 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文件,添加如下方法:
void GameLayer::reorderActors()
CCObject *pObject = NULL;
CCARRAY_FOREACH(_actors-&getChildren(), pObject)
ActionSprite *sprite = (ActionSprite*)pO
_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);
BoundingBox createBoundingBoxWithOrigin(cocos2d::CCPoint origin, cocos2d::CCSize size);
以上创建了ActionSprite的两个包围盒:Hit box和Attack box。还定义了一个方法,用于根据给定的原点和大小来创建一个BoundingBox结构体。打开ActionSprite.cpp文件,添加如下方法:
BoundingBox ActionSprite::createBoundingBoxWithOrigin(CCPoint origin, CCSize size)
BoundingBox boundingB
boundingBox.original.origin =
boundingBox.original.size =
boundingBox.actual.origin = ccpAdd(this-&getPosition(), ccp(boundingBox.original.origin.x, boundingBox.original.origin.y));
boundingBox.actual.size =
return boundingB
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函数后面添加如下代码:
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函数后面添加如下代码:
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)));
现在我们已经有了英雄和机器人各自的Hit box和Attack box。如果是可视化的箱子,它们会像下面这样:
无论何时,当一个attack box(红色)跟一个hit box(蓝色)交叉,即一次碰撞发生。在开始编写代码,检测包围盒交叉前,需要确保ActionSprite能够对被击中有所反应。我们已经添加了空闲、出拳、行走动作,但还未创建受伤和死亡动作。打开ActionSprite.cpp文件,实现如下方法:
void ActionSprite::hurtWithDamage(float damage)
if (_actionState != kActionStateKnockedOut)
this-&stopAllActions();
this-&runAction(_hurtAction);
_actionState = kActionStateH
_hitPoints -=
if (_hitPoints &= 0)
this-&knockout();
void ActionSprite::knockout()
this-&stopAllActions();
this-&runAction(_knockedOutAction);
_hitPoints = 0;
_actionState = kActionStateKnockedO
只要精灵还未死亡,被击中时状态将会切换到受伤状态,执行受伤动画,并且精灵的生命值将会减去相应的伤害值。如果生命值少于0,那么死亡的动作将会触发。为了完成这两个动作,我们还需更改Hero类和Robot类。打开Hero.cpp文件,在init函数walk animation后面添加如下代码:
//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后面添加如下代码:
//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函数后面添加如下代码:
if (_hero-&getActionState() == kActionStateAttack)
CCObject *pObject = NULL;
CCARRAY_FOREACH(_robots, pObject)
Robot *robot = (Robot*)pO
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文件,修改成如下代码:
#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
#define random_range(low, high) (rand() % (high - low +1)) + low
#define frandom (float)rand() / UINT64_C(0x)
#define frandom_range(low, high) ((high - low) * frandom) + low
// 3 - enumerations
typedef enum _ActionState {
kActionStateNone = 0,
kActionStateIdle,
kActionStateAttack,
kActionStateWalk,
kActionStateHurt,
kActionStateKnockedOut
// 4 - structures
typedef struct _BoundingBox {
cocos2d::CCR
cocos2d::CCR
} BoundingB
inline float GetCurTime(){
gettimeofday(&time, NULL);
unsigned long millisecs = (time.tv_sec *1000) + (time.tv_usec / 1000);
return (float)
打开GameLayer.cpp文件,添加如下方法:
void GameLayer::updateRobots(float dt)
int alive = 0;
float distanceSQ;
int randomChoice = 0;
CCObject *pObject = NULL;
CCARRAY_FOREACH(_robots, pObject)
Robot *robot = (Robot*)pO
robot-&update(dt);
if (robot-&getActionState() != kActionStateKnockedOut)
alive++;
if (CURTIME & robot-&getNextDecisionTime())
distanceSQ = ccpDistanceSQ(robot-&getPosition(), _hero-&getPosition());
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);
robot-&setScaleX(-1.0);
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
robot-&idle();
else if (distanceSQ &= SCREEN.width * SCREEN.width)
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);
robot-&idle();
//end game checker here
这是一个漫长的代码片段。将代码分解为一段段。对于游戏中的每个机器人:
①.使用一个计数来保存仍然存活着的机器人数量。一个机器人只要不是死亡状态,就被认为仍然存活着。这将用于判断游戏是否应该结束。
②.检查当前应用程序时间的推移是否超过了机器人的下一次决定时间。如果超过了,意味着机器人需要作出一个新的决定。
③.检查机器人是否足够接近英雄,以便于有机会出拳攻击落在英雄身上。如果接近英雄了,那么就进行一个随机选择,看是要朝着英雄出拳,还是要继续空闲着。
④.假如机器人决定攻击,我们就用之前检测英雄攻击时相同的方式来进行检测碰撞。只是这一次,英雄和机器人的角色互换了。
⑤.如果机器人和英雄之间的距离小于屏幕宽度,那么机器人将作出决定,要么朝着英雄移动,要么继续空闲。机器人的移动基于英雄位置和机器人位置产生的法向量。
每当机器人作出决定,它的下一个决定的时间被设定为在未来的一个随机时间。在此期间,它将继续执行上次作出决定时所做出的动作。接着在update函数里,this-&updatePositions();前添加如下代码:
this-&updateRobots(dt);
在updatePositions函数后面,添加如下代码:
CCObject *pObject = NULL;
CCARRAY_FOREACH(_robots, pObject)
Robot *robot = (Robot*)pO
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&
添加如下方法:
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注释后面,添加如下代码:
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());后面添加如下代码:
// 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文件,添加头文件引用:
#include &SimpleAudioEngine.h&
在hurtWithDamage函数,第一个条件语句里添加如下代码:
int randomSound = random_range(0,1);
CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(CCString::createWithFormat(&pd_hit%d.wav&, randomSound)-&getCString());
打开ActionSprite.h文件,将knockout方法声明修改如下:
virtual void knockout();
打开Hero.cpp文件,添加头文件引用:
#include &SimpleAudioEngine.h&
添加如下方法:
void Hero::knockout()
ActionSprite::knockout();
CocosDenshion::SimpleAudioEngine::sharedEngine()-&playEffect(&pd_herodeath.wav&);
打开Robot.cpp文件,添加头文件引用:
#include &SimpleAudioEngine.h&
添加如下方法:
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 2
2.如何使用cocos2d制作类似Scott Pilgrim的2D横版格斗过关游戏part2(翻译)
非常感谢以上资料,本例子源代码附加资源下载地址:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:302131次
积分:3418
积分:3418
排名:第6508名
原创:26篇
转载:94篇
评论:169条
(1)(1)(1)(1)(1)(1)(1)(1)(1)(2)(1)(1)(1)(1)(2)(1)(8)(74)(1)(5)(5)(1)(3)(1)(1)(3)

我要回帖

更多关于 cocos2d x 2.0.4 下载 的文章

 

随机推荐