下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

在Cocos2D-X中如何制作类似马里奥的横版游戏(下)

作者:课课家教育     来源: http://www.kokojia.com点击数:1181发布时间: 2019-03-27 08:58:27

标签: Cocos2D横版Cocos2D-X游戏开发Cocos2D-X基础

  上一篇我们讲了横版游戏中的重力模拟与碰撞问题,本篇课程将告诉你在Cocos2d-x中如何让主角考拉动起来。

在Cocos2D-X中如何制作类似马里奥的横版游戏(下)_Cocos2D横版_Cocos2D-X游戏开发_Cocos2D-X基础_课课家

  让考拉动起来!

  这里控制考拉移动变得非常简单,它只有向前和跳两个能力(源码中我加了考拉向后走功能,建议大家自己加几个虚拟按键来实现更非富的功能)如果你按着屏幕左半部考拉会向前走,按住右半部考拉会跳起来(原文设定考拉不会后退-_-)。

  我们需要在Player.h里加两个成员变量:

  bool _forwardMarch; //是否向前走

  bool _mightAsWellJump; //可以跳跃吗?

  在player.cpp的init方法或在构造函数里把它们设置为false

  在GameLevelLayer类里加上触摸,在.h文件里加上:

  virtual void registerWithTouchDispatcher();

  void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);

  void ccTouchesMoved(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);

  void ccTouchesEnded(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);

  在GameLevelLayer.cpp的init里加上(加载地图代码后)

  setTouchEnabled(true); //设置可触摸

  然后写注册触摸方法

  void GameLevelLayer::registerWithTouchDispatcher()

  {

  CCDirector* pDirector = CCDirector::sharedDirector();

  pDirector->getTouchDispatcher()->addStandardDelegate(this, 0); //注册多点触摸

  }

  现在,让我们看看那三个触摸事件吧!

  void GameLevelLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)

  {

  CCSetIterator iter = pTouches->begin();

  for (; iter!=pTouches->end(); iter++)

  {

  CCTouch* pTouch = (CCTouch*)(*iter);

  CCPoint touchLocation = this->convertTouchToNodeSpace(pTouch); //把touch点位置转换为本地坐标

  if (touchLocation.x > 240) //在屏幕最右边点,就是跳

  {

  _player->bMightAsWellJump = true;

  }

  else

  {

  _player->bForwardMarch = true;

  }

  }

  }

  void GameLevelLayer::ccTouchesEnded(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)

  {

  _player->bForwardMarch = false; //松开按键时,设置为不可跳也不是向前状态

  _player->bMightAsWellJump = false;

  }

  代码一目了然,ccTouchesBegan时根据玩家按的位置决定了考拉状态是前进还是跳跃,松开按键时将这两个状态变量重置为false。

  真正的角色移动是在player的update方法里进行的,看代码:

  void Player::update(float delta)

  {

  CCPoint gravity = ccp(0.f, -450.f); //考拉每秒下降450个单位

  CCPoint gravityStep = ccpMult(gravity, delta); //计算在重力影响下delta时间内具体下降了多少, 即dt时间后下落速度为多少

  CCPoint forwardMove = ccp(800.f, 0.f); //前进速度,每秒前进800.f

  CCPoint forwardStep = ccpMult(forwardMove, delta); // 1

  this->_velocity = ccpAdd(this->_velocity, gravityStep); //当前速度=当前速度+重力加速度

  this->_velocity = ccp(this->_velocity.x *0.9f, this->_velocity.y); //2

  if (this->bForwardMarch)

  {

  this->_velocity = ccpAdd(this->_velocity, forwardStep); //当前速度要加上向前的速度矢量

  }// 3

  CCPoint minMovement = ccp(-120.f, -350.f);

  CCPoint maxMovement = ccp(120.0f, 250.f);

  this->_velocity = ccpClamp(this->_velocity, minMovement, maxMovement); //4

  CCPoint stepVelocity = ccpMult(this->_velocity, delta); //计算下此速度下主角移动了多少

  this->_desiredPosition = ccpAdd(this->getPosition(), stepVelocity); //当前期望要去的位置=当前位置+当前速度

  }

  让我们来详细地看一下新增部分:

  当玩家触摸前进的时候和重力模拟类似,我们加了一个向前的推进力800.f,处理方式与重力相同

  第2步横向速度乘以0.9是模拟摩擦力效果,考虑下当有向前推力时玩家会向前移动,没有了呢?是不是立即停下来?那样看起来太生硬了,所以这里x轴速度每帧乘以0.9就起到了均匀减速的效果,就像摩擦力一样

  检查一下玩家是否按了前,按前进时速度上就要加上向前的推动力,就起到了向前进的效果了

  这一步是调用了clamp是限定速度前后上下不要太大,给它一个最大值。之前的那句设定下落速度最大值可以去掉了。

  好了运行一下,我们可以看到我们的主角小考拉现在可以前进了!

  让考拉跳起来!

  跳跃是动作游戏里最明显的一个特征。我们希望角色跳的平滑逼真,现在让我们来实现它。

  到player类的update方法里,在if(this->_forwardMarch)语句之前加上下面代码:

  CCPoint jumpForce = ccp(0.f, 310.f);

  if(this->_mightAsWellJump && this->_onGround)

  {

  this->_velocity = ccpAdd(this->_velocity, jumpForce);

  }

  只要加一个向上的力,角色就可以跳起来了。

  如果你止步于此,你会得到一个老式的雅代利式跳跃,即每次跳跃都是相同的高度,每次你都施给玩家一个同样的力,然后等着重力把你拉回地面来。

  这么做似乎没什么不妥,如果你要求不高的话,但是仔细观察一下各种流行的平台游戏,如超级马里奥,索尼克,洛克人,水上魂斗罗等,似乎玩家能够通过按键的力度来控制跳跃的高度,达到更灵活的效果。那是怎么做到的呢?

  其实实现这个效果也很简单,所谓玩家按键力度不过就是按键时间的长久,按的时间长也就是施加跳跃力的时间就长,跳的当然高了,半路上如果玩家不给力了,当然会跳到一半掉链子,不过玩家有种错觉就是想跳得高就使劲按着跳跃键,不想跳了松开键就是-_-

  CCPoint jumpForce = ccp(0.f, 310.f); //向上的跳跃力 玩家一直按着跳跃键时的跳跃力

  float jumpCutOff = 150.f; //玩家没有按住跳跃键时的跳跃力

  if(this->bMightAsWellJump && this->onGround) //如果当前玩家按了跳跃键并且在地上

  {

  this->_velocity = ccpAdd(this->_velocity, jumpForce); //跳跃就是加上一个向上的速度

  }

  else if (!this->bMightAsWellJump && this->_velocity.y > jumpCutOff) //玩家没有按住跳跃键,并且向上的速度已经超过了设定的值,就限定向上跳跃速度

  {

  this->_velocity = ccp(this->_velocity.x, jumpCutOff);

  }

  注释解释的很清楚,就不多解释了。

  好了,build一下run下我们的游戏吧,看吧,我们的小考拉可以自由欢腾地上下翻飞了。

  跳是跳的很欢了,不过悲剧的是,它跳到最右边就跳出屏幕,看不见了。

  来修正这个问题,这个问题其实就是视点跟随,在cocos2dx上有解决这一问题的标准算法,贴出代码:

  void GameLevelLayer::setViewpointCenter(cocos2d::CCPoint pos)

  {

  CCSize winSize = CCDirector::sharedDirector()->getWinSize();

  //限定角色不能超过半屏

  int x = MAX(pos.x, winSize.width/2);

  int y = MAX(pos.y, winSize.height/2);

  //限定角色不能跑出屏幕

  x = MIN(x, (_map->getMapsize().width * _map->getTileSize().width) - winSize.width/2);

  y = MIN(y, (_map->getMapSize().height * _map->getTileSize().height) - winSize.height/2);

  CCPoint actualPosition = ccp(x, y);

  CCPoint centerOfView = ccp(winSize.width/2, winSize.height/2);

  CCPoint viewPoint = ccpSub(centerOfView, actualPosition);

  //设定一下地图的位置

  _map->setPosition(viewPoint);

  }

  方法参数就是玩家考拉当前位置。这个方法可以不但能左右跟随还能上下跟随主角,非常好用。这个方法原理在很多博客都有提到,原理其实就是地图在跟玩家做返方向运动,大家可以查阅一下其他文章解释它的原理,不过我在这里不想再多说了,不是一两句能说清,我们只要会用这个方法就行了。

  我们要做的就是在GameLevelLayer类的update方法里调用就行了,可以放在update方法结尾之前,如下:

  this->setViewpointCenter(_player->getPosition());

  现在build再run一下,这回我们的小考拉再也不会跑出屏幕外了!

  尝一下受伤的滋味!

  现在我们可以着手做游戏过关和GameOver的功能了。

  地图里有个hazards层,这个层里放置一些考拉碰上就会挂的object。其实本质也上碰撞检测,看代码:

  void GameLevelLayer::handleHazardCollisions(Player* player)

  {

  CCArray *tiles = this->getSurroundingTilesAtPosition(player->getPosition(), _hazards);

  CCDictionary* dic = NULL;

  CCObject* obj = NULL;

  float x=0.f; float y = 0.f;

  int gid = 0;

  CCARRAY_FOREACH(tiles, obj)

  {

  dic = (CCDictionary*)obj;

  x = dic->valueForKey("x")->floatValue();

  y = dic->valueForKey("y")->floatValue();

  CCRect tileRect = CCRectMake(x, y, _map->getTileSize().width, _map->getTileSize().height);

  CCRect pRect= player->collisionBoundingBox();

  gid = dic->valueForKey("gid")->intValue();

  if (gid && tileRect.intersectsRect(pRect)) //如果有钉刺并且玩家与它发生碰撞了,gameOver

  {

  this->gameOver(false);

  }

  }

  }

  代码看上去是不是很熟悉呀,你没记错,其实就是从checkAndResolveCollisions方法里拷来的,只不过没分那么多情况而且处理方式也简单了只是调用了gameOver方法,gameOver的布尔值参数为true表示游戏胜利,为false就是失败

  _hazards在GameLevelLayer类也是个CCTMXLayer* 地图层类型的成员变量,在此类的init方法里初始化它:

  _hazards = _map->layerNamed("hazards");

  然后在update方法里调用这个方法,现在update方法是这个样子:

  void GameLevelLayer::update(float delta)

  {

  _player->update(delta);

  this->handleHazardCollisions(_player);

  this->checkForAndResolveCollisions(_player);

  this->setViewpointCenter(_player->getPosition());

  }

  现在实现gameOver方法了,当玩家跳到harads层里的刺上时,我们会调用这个方法游戏结束,或者当玩家走到终点时,我也会调用这个方法,这个方法会显示出一个restart按钮,并在屏幕上打印出一些信息,告诉玩家你死了或者你赢了之类的-_-

  void GameLevelLayer::gameOver(bool bWon)

  {

  bGameOver = true;

  CCString* gameText;

  if (bWon)

  {

  gameText = CCString::create("You Won!");

  }

  else

  gameText = CCString::create("You have Died!");

  CCLabelTTF* diedLabel = CCLabelTTF::create(gameText->getCString(), "Marker Felt", 40);

  diedLabel->setPosition(ccp(240, 200));

  CCMoveBy *slideIn = CCMoveBy::create(1.f, ccp(0, 250));

  CCMenuItemImage* replay = CCMenuItemImage::create("replay.png","replay.png","replay.png", this, menu_selector(GameLevelLayer::restartGame));

  CCArray *menuItems = CCArray::create();

  menuItems->addObject(replay);

  CCMenu *menu = CCMenu::create();

  menu->addChild(replay);

  menu->setPosition(ccp(240, -100));

  this->addChild(menu);

  this->addChild(diedLabel);

  menu->runAction(slideIn);

  }

  方法开头的变量bGameOver也是GameLevelLayer类新定义的一个成员变量,在init里要初始化为false,此变量表示游戏是否结束。这个变量作用是你在update方法里开头判断一下,如果bGameOver==true,则直接返回不要做任何事了。如下:

  void GameLevelLayer::update(float delta)

  {

  if(bGameOver)

  return;

  _player->update(delta);

  this->handleHazardCollisions(_player);

  this->checkForAndResolveCollisions(_player);

  this->setViewpointCenter(_player->getPosition());

  }

  现在你再编译运行,碰上钉板试试,会出现杯具性的结局...

  不要尝试的太多,小心动物保护组织会找上你家门来(-_-这就是所谓的美式幽默吗?)

  修正一个致命BUG

  还记得前面出现过当考拉掉出地图出现的事情吗?在游戏地图里有些坑我们本意是考拉掉下去游戏会结束,但事实上程序崩掉了。凡是带有TILED地图的都会出现这样的问题,只要角色走出地图边界游戏就会崩掉,这个问题困扰了好多人,也包括我。其实解决这个BUG很简单,关键在tileGIDAt方法。

  此方法你打开源码实现就知道,它有个CCASSERT宏,判断出地图就会抛出异常。本来吗此方法是取地图上某处tile的gid,你都出地图了还取什么当然要抛异常啦。这里我们就在getSurroundingTilesAtPosition方法里加个判断,注意当然要在调用tileGIDAt方法之前,不让考拉出地图:

  if (tilePos.y > (_map->getMapSize().height-1))

  {

  this->gameOver(false);

  return NULL;

  }

  在checkForAndResolveCollisions方法里有调用 getSurroundingTilesAtPosition方法,如果它返回个空数组可就不妙了,所以在checkForAndResolveCollisions也要改一下,在 CCArray* tiles = this->getSurroundingTilesAtPosition(player->getPosition(), _walls);一句之后,加上

  if (bGameOver) //可能玩家掉坑里,就不处理了

  {

  return;

  }

  编译再运行,把考拉掉进坑里试试,发现程序不再DOWN了!

  Winner!

  在最后,我们处理下考拉走到终点时显示胜利的情况。

  这里我们简单点,只是判断下考拉向右走了多远就取胜

  void GameLevelLayer::checkForWin()

  {

  if (_player->getPositionX()>3130.0)

  {

  this->gameOver(true);

  }

  }

  真实游戏里我们通常在终点放置一个object,如门呀城堡小旗什么的,主角碰到就胜利了可进入下一关,有兴趣的自己尝试!

  这个方法也要放到GameLevelLayer的update方法里,如下:

  void GameLevelLayer::update(float delta)

  {

  if(bGameOver)

  return;

  _player->update(delta);

  this->handleHazardCollisions(_player);

  this->checkForWin();

  this->checkForAndResolveCollisions(_player);

  this->setViewpointCenter(_player->getPosition());

  }

  运行一下,走到终点试试:

  添加音效

  胜利了之后没有音乐怎么行,一个无声的世界可真是会令人绝望呀,来我们加些音效吧!

  在GameLevelLayer.cpp和 player.cpp加上头文件如下:

  #include "SimpleAudioEngine.h"

  using namespace CocosDenshion;

  在GamelevelLayer的init方法里加上:

  SimpleAudioEngine::shareEngine()->playBackgroundMusic("level1.mp3");

  在player.cpp里的update方法里判断玩家跳的地方加上音效:

  if(this->bMightAsWellJump && this->onGround) //如果当前玩家按了跳跃键并且在地上

  {

  this->_velocity = ccpAdd(this->_velocity, jumpForce); //跳跃就是加上一个向上的速度

  SimpleAudioEngine::sharedEngine()->playEffect("jump.wav");

  }

  在GameLevelLayer.cpp的gameOver方法里也加上音效:

  void GameLevelLayer::gameOver(bool bWon)

  {

  if (bGameOver) //不要反复调用

  {

  return;

  }

  bGameOver = true;

  SimpleAudioEngine::sharedEngine()->playEffect("hurt.wav");

  编译运行,试一试你亲手制作的带有音乐感的游戏吧!

赞(25)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程