如何让格斗游戏的横版过关(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(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_walk_%02d.png", i)->getCString()); walkFrames->addObject(frame); } CCAnimation *walkAnimation = CCAnimation::createWithSpriteFrames(walkFrames, . / .)); 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); ) { .); } else { .); } } } |
这段代码,检查前置动作状态是否空暇,若是的话切换动作到行走。在行走状态时。依据_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)); * _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.以上的问题。能够通过基于英雄的位置进行滚动地图。这种方法在文章《怎样制作一个基于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(); ); ); x = MIN(x, (_tileMap->getMapSize().width * _tileMap->getTileSize().width) - winSize.width / ); y = MIN(y, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - winSize.height / ); CCPoint actualPosition = ccp(x, y); CCPoint centerOfView = ccp(winSize.width / , winSize.height / ); 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(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( CCString::createWithFormat("robot_idle_%02d.png", i)->getCString()); idleFrames->addObject(frame); } CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames, . / .)); this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation))); //attack animation CCArray *attackFrames = CCArray::createWithCapacity(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( CCString::createWithFormat("robot_attack_%02d.png", i)->getCString()); attackFrames->addObject(frame); } CCAnimation *attackAnimation = CCAnimation::createWithSpriteFrames(attackFrames, . / .)); this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create(this, callfunc_selector(Robot::idle)), NULL)); //walk animation CCArray *walkFrames = CCArray::createWithCapacity(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( CCString::createWithFormat("robot_walk_%02d.png", i)->getCString()); walkFrames->addObject(frame); } CCAnimation *walkAnimation = CCAnimation::createWithSpriteFrames(walkFrames, . / .)); this->setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation))); .); .); .); .); .); bRet = true; } ); return bRet; } |
跟英雄一样,以上代码创建一个带有3个动作的机器人:空暇、出拳、行走。它也有两个測量值:centerToBottom和centerToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。
让我们開始加入一些机器人到游戏中去。打开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()
{ ; this->setRobots(CCArray::createWithCapacity(robotCount)); ; 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(); * _tileMap->getTileSize().height + robot->getCenterToBottom(); robot->setScaleX(-); 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 + ( ? (- _attackBox.original.size.width - _hitBox.original.size.width) : ), |
第一个方法创建一个新的包围盒,这有助于ActionSprite的子类创建属于它们自己的包围盒。
第二个方法,基于精灵的位置、比例因子。和包围盒原本的原点和大小来更新每一个包围盒实际測量的原点和大小。之所以要用到比例因子,是由于它决定着精灵的方向。位于精灵右側的盒子,当比例因子设置为-1时,将会翻转到左側。
打开Hero.cpp文件,在init函数后面加入例如以下代码:
1
2 3 |
this->setHitbox(this->createBoundingBoxWithOrigin(ccp(-this->getCenterToSides(), -this->getCenterToBottom()),
CCSizeMake(, ))); ), CCSizeMake(, ))); |
打开Robot.cpp文件,在init函数后面加入例如以下代码:
1
2 3 |
this->setHitbox(this->createBoundingBoxWithOrigin(ccp(-this->getCenterToSides(), -this->getCenterToBottom()),
CCSizeMake(, ))); ), CCSizeMake(, ))); |
如今我们已经有了英雄和机器人各自的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; ) { this->knockout(); } } } void ActionSprite::knockout() { this->stopAllActions(); this->runAction(_knockedOutAction); _hitPoints = ; _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(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_hurt_%02d.png", i)->getCString()); hurtFrames->addObject(frame); } CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, . / .)); this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create(this, callfunc_selector(Hero::idle)), NULL)); //knocked out animation CCArray *knockedOutFrames = CCArray::createWithCapacity(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("hero_knockout_%02d.png", i)->getCString()); knockedOutFrames->addObject(frame); } CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, . / .)); ., .), 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(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("robot_hurt_%02d.png", i)->getCString()); hurtFrames->addObject(frame); } CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, . / .)); this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create(this, callfunc_selector(Robot::idle)), NULL)); //knocked out animation CCArray *knockedOutFrames = CCArray::createWithCapacity(); ; i < ; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat("robot_knockout_%02d.png", i)->getCString()); knockedOutFrames->addObject(frame); } CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, . / .)); ., .), 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 (_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 = ;
|
这个属性保存下一次机器人能够作出决定的时间。打开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() , SCREEN.height / ) #define CURTIME GetCurTime() // 2 - convenience functions #ifndef UINT64_C #define UINT64_C(val) val##ui64 #endif )) + low #define frandom (float)rand() / UINT64_C(0x100000000) #define frandom_range(low, high) ((high - low) * frandom) + low // 3 - enumerations typedef enum _ActionState { kActionStateNone = , 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); ) + (time.tv_usec / ); 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)
{ ; float distanceSQ; ; 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 * ) { robot->setNextDecisionTime(CURTIME + frandom_range(., .) * ); randomChoice = random_range(, ); ) { if (_hero->getPosition().x > robot->getPosition().x) { robot->setScaleX(.); } else { robot->setScaleX(-.); } //4 robot->setNextDecisionTime(robot->getNextDecisionTime() + frandom_range(., .) * ); robot->attack(); if (robot->getActionState() == kActionStateAttack) { ) { 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(., .) * ); randomChoice = random_range(, ); ) { 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( * _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(); CCMenuItemLabel *restartItem = CCMenuItemLabel::create(restartLabel, this, menu_selector(GameLayer::restartGame)); CCMenu *menu = CCMenu::create(restartItem, NULL); menu->setPosition(CENTER); menu->setTag(); _hud->addChild(menu, ); } void GameLayer::restartGame(CCObject* pSender) { CCDirector::sharedDirector()->replaceScene(GameScene::create()); } |
第一个方法创建显示一个又一次開始的button。当按下它时。触发第二个方法。后者仅仅是命令导演用新的GameScene实例替换当前场景。
接着在updateRobots函数里面,在第一个end game checker here凝视后面,加入例如以下代码:
1
2 3 4 |
) == NULL)
{ this->endGame(); } |
在第二个end game checker here凝视后面,加入例如以下代码:
1
2 3 4 |
&& _hud->getChildByTag() == 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 |
, );
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
如何让格斗游戏的横版过关(2) Cocos2d-x 2.0.4的更多相关文章
- 怎样制作一个横版格斗过关游戏 Cocos2d-x 2.0.4
本文实践自 Allen Tan 的文章<How To Make A Side-Scrolling Beat 'Em Up Game Like Scott Pilgrim with Coco ...
- Beat 'Em Up Game Starter Kit (横版格斗游戏) cocos2d-x游戏源代码
浓缩精华.专注战斗! 游戏的本质是什么?界面?养成?NoNo! 游戏来源于对实战和比赛的模拟,所以它的本源就是对抗.就是战斗! 是挥洒热血的一种方式! 一个游戏最复杂最难做的是什么?UI?商城? ...
- 《Genesis-3D开源游戏引擎--横版格斗游戏制作教程:简介及目录》(附上完整工程文件)
介绍:讲述如何使用Genesis-3D来制作一个横版格斗游戏,涉及如何制作连招系统,如何使用包围盒实现碰撞检测,软键盘的制作,场景切换,技能读表,简单怪物AI等等,并为您提供这个框架的全套资源,源码以 ...
- [Unity+Android]横版扫描二维码
原地址:http://blog.csdn.net/dingxiaowei2013/article/details/25086835 终于解决了一个忧伤好久的问题,严重拖了项目进度,深感惭愧!一直被一系 ...
- Unity3D 3D横版跑酷
Unity3d 3D横版跑酷系列(Character Controller组件) @广州小龙 目前在做一个3D跑酷的横版游戏,目前说一下 Character Controller组件! 1.Slop ...
- Unity3D开发一个2D横版射击游戏
教程基于http://pixelnest.io/tutorials/2d-game-unity/ , 这个例子感觉还是比较经典的, 网上转载的也比较多. 刚好最近也在学习U3D, 做的过程中自己又修改 ...
- Word2007:如何在竖版(纵向)页面中间插入横版(横向)页面
通常情况下,我们在word排版过程中使用一种页面版式(横版/竖版)即可.但在某些特殊情况下,我们可能会需要在竖版页面中间插入一页或多页横版页面,抑或在横版页面中间插入竖版页面.那么,如何针对这 ...
- 【Cocos2d-Js实战教学(1)横版摇杆八方向移动】
本教程主要通过搭建一个横版摇杆八方向移动的实例,让大家如何用Cocos2dx-Js来做一款游戏,从基础了解Cocos2dx-Js的基本实现原理,从创建工程,到各个知识点的梳理. 教程分为上下两讲: 上 ...
- 微信小程序横版日历,tab栏
代码地址如下:http://www.demodashi.com/demo/14243.html 一.前期准备工作 软件环境:微信开发者工具 官方下载地址:https://mp.weixin.qq.co ...
随机推荐
- Node.js 博客实例(五)编辑与删除功能
原教程 https://github.com/nswbmw/N-blog/wiki/_pages的第五章,因为版本号等的原因,在原教程基础上稍加修改就可以实现. 如今给博客加入编辑文章与删除文章的功能 ...
- 004串重量 (keep it up)
设计算法并写出代码移除字符串中反复的字符,不能使用额外的缓存空间. 注意: 能够使用额外的一个或两个变量,但不同意额外再开一个数组拷贝. 简单题直接上代码: #include <stdio.h& ...
- 重新想象 Windows 8 Store Apps (29) - 图片处理
原文:重新想象 Windows 8 Store Apps (29) - 图片处理 [源码下载] 重新想象 Windows 8 Store Apps (29) - 图片处理 作者:webabcd介绍重新 ...
- Docker部署JavaWeb项目实战(转)
摘要:本文主要讲了如何在Ubuntu14.04 64位系统下来创建一个运行Java web应用程序的Docker容器. 一.下载镜像.启动容器 1.下载镜像 先查看镜像 docker images 记 ...
- 《TCP/IP详细解释》札记(23章)-TCP该保活定时器
可能有这样的备用现实TCP连接:流通过. 也就是说.假设TCP连接的两方都没有向对方发送数据.则在两个TCP模块之间不交换不论什么信息,这意味着我们能够启动一个客户与server建立连接,然后长时间不 ...
- __weak如何实现目标值自己主动设置nil的
在开始评论__weak机制之前,首先,一些床上用品 ARC 实现 苹果公司的官方介绍说,.ARC这是"内存管理由编译器"的,但事实上,只有编译器不能完全胜任,ARC另外还要看OC执 ...
- 2013成都邀请赛J称号||HDU4725 The Shortest Path in Nya Graph(spfa+slf最短的优化)
职务地址:HDU 4725 这题卡了好长时间了,建图倒是会建,可是不会最短路的算法优化,本以为都须要堆去优化的,打算学了堆之后再来优化.可是昨晚CF的一道题..(那题也是不优化过不了..)然后我就知道 ...
- Android做法说明(3)---Fragment使用app袋或v4包解析
Android做法说明(3)---Fragment使用app袋或v4包解析 1)问题简述 相信非常多的朋友在调用Fragment都会遇到以下的情况: watermark/2/text/aHR0cDov ...
- 数据库开发——参照完整性——在外键中使用Delete on cascade选项
原文:数据库开发--参照完整性--在外键中使用Delete on cascade选项 原文: http://www.mssqltips.com/sqlservertip/2743/using-dele ...
- js cookie设置最大过期时间 Infinity
Note: 对于永久cookie我们用了Fri, 31 Dec 9999 23:59:59 GMT作为过期日.如果你不想使用这个日期,可使用世界末日Tue, 19 Jan 2038 03:14:07 ...