cocos2d-x 如何制作一个类马里奥的横版平台动作游戏续 2
欢迎回来,上篇我们讲到了物理引擎中重力环境模拟以及主角考拉与地面墙壁的碰撞,相信大家已经对2D世界的物理模拟有了一定的了解,现在我们接着讲如何让考拉动起来吧!
让考拉动起来! 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); //当前期望要去的位置=当前位置+当前速度 }让我们来详细地看一下新增部分:
让考拉跳起来! 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一下,这回我们的小考拉再也不会跑出屏幕外了!
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",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 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");编译运行,试一试你亲手制作的带有音乐感的游戏吧! 源码下载地址: 下载 剩下我们还能做什么? 要做的还有什么,角色动画没有吧,此外还有怪物,AI,关卡,角色状态机等等。这些都在IOS GAME三件套 Platformer Start Kit里,前面说过,本教程只是这个Start Kit的前奏曲,那真正的Platformer game是什么样的呢? 学完此教程后,你可以学到 怎么样?心动了吧,涉于版权问题不便在此公开,有兴趣的可到此看看: 横版平台游戏源码 此外大名鼎鼎的三件套之一横版格斗游戏 Beat 'Em Up Game Starter Kit 也是非常精彩哦!网上公开的源码教程还不到里面的二十分之一-_- 有兴趣可到此看看: 横版格斗游戏源码 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |