在第一篇《如何制作一个横版格斗过关游戏》基础上,增加角色运动、碰撞、敌人、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))); |
打开ActionSprite.cpp文件,实现walkWithDirection方法,代码如下:
void ActionSprite::walkWithDirection(CCPoint direction) { //检查前置动作状态是否空闲 if (_actionState == kActionStateIdle) { //停止所有动作 this->stopAllActions(); //执行行走的动作 this->runAction(_walkAction); //标记状态为行走 _actionState = kActionStateWalk; } if (_actionState == kActionStateWalk) { //根据_walkSpeed值改变精灵的速度 _velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed); //检查精灵的左右方向 if (_velocity.x >= 0) { //用setScaleX来翻转精灵 看清楚是有 setScaleX是有X的 精灵可以左右变换 this->setScaleX(1.0); } else { 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 delta) { //在每次游戏更新场景的时候都会进行调用,当精灵处于行走状态时, if (_actionState == kActionStateWalk) { //它更新精灵的期望位置。位置+速度*时间,实际上就是意味着每秒移动X和Y点 //这里的this指针就是指 谁调用就代表谁,英雄调用的就代表英雄,敌人调用的就代表敌人 _desiredPosition = ccpAdd(this->getPosition(), ccpMult(_velocity, delta)); } }
这个方法在每次游戏更新场景的时候都会进行调用,当精灵处于行走状态时,它更新精灵的期望位置。位置+速度*时间,实际上就是意味着每秒移动X和Y点。打开GameLayer.cpp文件,在init函数this->initTileMap();后面添加如下代码:
1
|
this->scheduleUpdate();
|
1
2 3 4 |
GameLayer::~GameLayer(
void)
{ this->unscheduleUpdate(); } |
void GameLayer::update(float delta) { _hero->update(delta); this->updatePositions(); this->setViewpointCenter(_hero->getPosition()); this->reorderActors(); } void GameLayer::updatePositions() { //_tileMap->getMapSize().width 得到的是地图中横向有多少个瓦片地图 float widthMap = _tileMap->getMapSize().width; //_tileMap->getTileSize().width 得到的时每个瓦片地图的宽度 float widthTile = _tileMap->getTileSize().width; //widthMap * widthTile 代表的是整个地图的宽 //widthMap * widthTile - _hero->getCenterToSides()用整个地图的宽减去精灵中心到精灵边界的距离 //相减得到的值就是精灵能够横向移动的最大位置 //desiredPositionX x轴的目标位置 就是在当前位置和目标 float desiredPositionX = MAX(_hero->getCenterToSides(), _hero->getDesiredPosition().x); float posX = MIN( widthMap * widthTile - _hero->getCenterToSides(),desiredPositionX); //得到每个瓦片地图的高 float heightTile = _tileMap->getTileSize().height; //下面的3 是指属于地板的瓦片只有3个 float posY = MIN(3 * heightTile + _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(); 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); } |
以上代码让英雄处于屏幕中心位置,当然,英雄在地图边界时的情况除外。编译运行,效果如下图所示:
代码例子 http://vdisk.weibo.com/s/BDn59yfnBVkre