在第一篇《如何制作一个横版格斗过关游戏》基础上,增加角色运动、碰撞、敌人、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