花了3天时间,把这个游戏简单地实现了一遍,以加深对cocos2d-x 3.0的进一步了解。在编码过程中也遇到不少问题,不过感谢两位大神的博客专栏,一个为http://blog.csdn.net/column/details/jackyairplane.html,另一个为
http://www.zaojiahua.com/category/cocos2d-xdemo/,在完成代码的过程中遇到的一些小问题都可以在上面的博客中找到了答案。
下面我来总结一下我的开发历程,加深体会的同时也可以记录一下这3天的工作,接下来,我将按照自己的开发思路来和大家分享一下我的飞机大战如何制作。
一、总体框架的设计
首先我们来认识一下这个游戏的组成
1、游戏的主要元素: (a)添加背景 (b)添加飞机 (c)添加子弹 (d)添加敌机 (e)添加ufo
2、游戏的主要成分: (a)碰撞检测 (b)分数统计 (c)播放声效 (d)暂停恢复 (e)数据存储
3、游戏设计的总体思路
分三个场景,第一个为游戏进入场景,第二个为游戏的主逻辑场景,第三个为游戏结束场景,他们的关系如下:
3.1 游戏加载场景要做的就是预加载资源,资源包括声音,图片,动画等,还可以做一些初始化的处理,譬如数据存储等。
3.2 游戏结束场景要做的就是统计当前分数,显示最高记录,并给出菜单进行下一步的操作。
3.3 游戏主场景是游戏的核心,包含了游戏的基本逻辑,下面说一下这个游戏我的思路是怎么样的:
3.3.1、Weapon层来管理飞机和子弹,因为子弹发射需要获取飞机的位置,飞机切换子弹也需要进行一些子弹类中的函数,所以我决定通过一个中介层Weapon来处理他们的交互。
3.3.2、EnemyLayer层管理敌机,由于敌机的类型有3种,我在EnemyLayer里分别用三个vector来管理不同类型的敌机。
3.3.3、UfoLayer层管理ufo,并在该层控制bomb标志的加减。
3.3.4、触摸的话我放在Weapon层里面处理,因为我觉得触摸主要是控制飞机的位置,没必要另外建一个触摸层,这样子还要考虑两个层之间的通讯。
3.3.5、碰撞检测我放在GameScene中处理,这个也算是一个联合国的方式了,把所有的元素管理层都添加在这个Scene中,处理各种交互。不过这样做貌似责任过重了,不过也方便了处理各个层之间的交互,有好有坏吧,看个人怎么权衡。
上面所说的可以简单用下图来表示:
说了那么多,接下来我们一个个来说明。
二、游戏的开始,资源的添加
资源非常宝贵,没有资源游戏写得再怎么牛B也显示不出来啊。资源的获取可以到上述第一个推荐的博客中找,另外还可以直接去下载一个飞机大战的apk,解压里面的资源。
资源的处理cocos2d-x为我们给出了几个缓存类,这里使用了SpriteFrameCache和AnimationCache。在游戏预加载场景中我用一个preloadResource的函数来统一处理资源的预加载,节选如下:
//帧缓存初始化 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("game.plist"); //缓存飞机动画 Animation* heroAnimation = Animation::create(); heroAnimation->setDelayPerUnit(0.2f); heroAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero1.png")); heroAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero2.png")); AnimationCache::getInstance()->addAnimation(heroAnimation,"heroAnimation");
其中game.plist文件是用TexturePacker来处理的,这个软件相当方便,推荐使用。把图片打包资源存到帧缓存后我们就可以先缓存游戏设计到的动画,这里给出了缓存飞机动画的代码。把资源都处理好了,接下来我们要做的就是调用,而且调用非常便捷。
三、先来个会滚动的背景
其实在这个飞机游戏中,飞机并不是真的在飞行,而是背景在飞行。实现起来也很简单,就是两张图片在改变Y轴方向上的坐标,然后设定边界,但坐标位置到一定值的时候就返回重新开始。先创建两个背景精灵并添加图片:
//添加飞机精灵 auto winSize = Director::getInstance()->getWinSize(); _hero = Sprite::createWithSpriteFrameName("hero1.png"); _hero->setPosition(Point(winSize.width*0.5, _hero->getContentSize().height)); this->addChild(_hero,6); //创建初始化闪烁动画 auto blink = Blink::create(1.0f, 3); //创建飞机飞行动画 auto planeAnimate = Animate::create(AnimationCache::getInstance()->getAnimation("heroAnimation")); _hero->runAction(blink); _hero->runAction(RepeatForever::create(planeAnimate));
注意上面那一句注释,如果没有设置的话背景会出现一条难看的白边。然后设置坐标变换即可。
void BackgroundLayer::backgroundMove(float dt) { background1->setPositionY(background1->getPositionY() - 2.0f); background2->setPositionY(background1->getPositionY() + background1->getContentSize().height - 2.0f); if(0 == background2->getPositionY()) { background1->setPositionY(0); } }
在实现飞机的时候,我们到底是使它继承Node还是Sprite好呢。继承Node的话会比较灵活,里面还可以添加其他元素,可以再添加一个Sprite来初始话飞机,而继承Sprite的话貌似就直接一点,但是限制也是有的,就譬如继承之后不能使用很多Sprite中的函数,需要重写初始化图片之类的函数。由于这个主角灵活性比较强,这里我还是选择继承Node吧。
加载飞机动画,这个创建一个精灵来执行播放就OK了。
//添加飞机精灵 auto winSize = Director::getInstance()->getWinSize(); _hero = Sprite::createWithSpriteFrameName("hero1.png"); _hero->setPosition(Point(winSize.width*0.5, _hero->getContentSize().height)); this->addChild(_hero,6); //创建初始化闪烁动画 auto blink = Blink::create(1.0f, 3); //创建飞机飞行动画 auto planeAnimate = Animate::create(AnimationCache::getInstance()->getAnimation("heroAnimation")); _hero->runAction(blink); _hero->runAction(RepeatForever::create(planeAnimate));
另外这个类中要处理的就是飞机爆炸和爆炸之后的调用处理。飞机爆炸其实也是动画的播放而已。
void HeroPlane::blowUp() { auto blowup = Animate::create(AnimationCache::getInstance()->getAnimation("heroBlowupAnimation")); auto hide = Hide::create(); auto callback = CallFunc::create(CC_CALLBACK_0(HeroPlane::callBack, this)); _hero->stopAllActions(); _hero->runAction(Sequence::create(blowup, hide, callback, NULL)); }
关键的东西来了,爆炸之后的回调处理,也就是走向游戏结束。这个游戏结束由于涉及到其他类的一些操作,所以放在GameScene中处理,这里调用就可以了。
void HeroPlane::callBack() { _hero->stopAllActions(); _hero->removeFromParentAndCleanup(true); this->destroyInstance();//销毁飞机单例 GameScene* gameScene = (GameScene*)(this->getParent()->getParent()); gameScene->gameOver(); }
五、发射子弹
这个由于灵活性不是很强,我就继承了Sprite,这样子我们就必须重写初始化的函数,调用主函数中的初始化函数来实现。如下:
Bullet* Bullet::createBullet(std::string frameName) { Bullet* bullet = new Bullet(); if(bullet) { bullet->initWithSpriteFrameName(frameName); bullet->autorelease(); return bullet; } CC_SAFE_DELETE(bullet); return nullptr; } void Bullet::initBulletWithFrameName(std::string frameName) { this->initWithSpriteFrameName(frameName); }
子弹就两个需要处理的东东,一个为飞行,一个为飞行结束后要处理的,至于碰撞的话我们之前说好了放在GameScene中去处理。
//子弹的飞行 void Bullet::bulletMove(Point origin) { //设置子弹的飞行的参数 this->setPosition(origin); auto destination = Point(origin.x, END_Y); float duration = (END_Y - origin.y)/speed; //创建移动动作和动作完成后的回调动作 auto moveTo = MoveTo::create(duration, destination); auto moveEnd = CallFunc::create(CC_CALLBACK_0(Bullet::moveEnd, this)); SimpleAudioEngine::getInstance()->playEffect("bullet.mp3"); this->runAction(Sequence::create(moveTo, moveEnd, NULL)); } //子弹结束的处理 void Bullet::moveEnd() { Weapon* weapon = (Weapon*)this->getParent(); weapon->removeBullet(this); }
看看他的类先
CC_SYNTHESIZE_READONLY(HeroPlane*, _heroPlane, heroPlane); CC_SYNTHESIZE_READONLY(cocos2d::Vector<Bullet*>, _bulletVector, bulletVector); CREATE_FUNC(Weapon); bool init(); //添加子弹 void addBullet(float dt); //添加双排子弹 void addMultiBullet(float dt); //移除子弹 void removeBullet(Bullet* bullet); //获取子弹发射位置 cocos2d::Point getShootPostion(); //飞机爆炸 void heroBlowUp(); //开启单排子弹发射 void startBullet1Shoot(float dt); //停止单排子弹发射 void stopBullet1Shoot(); //开启双排子弹发射 void startBullet2Shoot(); //停止双排子弹发射 void stopBullet2Shoot(); //改变子弹类型 void changeBullet(); //暂停触摸 void pauseTouch(); //恢复触摸 void resumeTouch(); //添加触摸事件 bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event); void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event); private: cocos2d::EventListenerTouchOneByOne* touchListener; BulletType bulletType;
责任也算是重大了。我们挑着比较关键的来说,不关键的一笔带过。
6.1
子弹的发射和切换
发射子弹就是添加一个子弹sprite到飞机头部位置,然后让子弹做直线运动,这个简单吧。
//添加子弹 void Weapon::addBullet(float dt) { Bullet* bullet = Bullet::createBullet("bullet1.png"); //这个可以使用批次渲染,慢慢改善 this->addChild(bullet, 1); //添加到子弹管理器中 _bulletVector.pushBack(bullet); //获得初始位发射 bullet->bulletMove(getShootPostion()); }切换子弹的话就是单排子弹编程双排子弹,然后一定时限之后又切换回单排。双排子弹的发射和单排一样,只不多设置一下发射位置就可以了。
//添加双子弹发射,同单子弹发射一样,就是两个单排子弹 void Weapon::addMultiBullet(float dt) { Bullet* bulletLeft = Bullet::createBullet("bullet2.png"); Bullet* bulletRight = Bullet::createBullet("bullet2.png"); this->addChild(bulletLeft,1); this->addChild(bulletRight,1); _bulletVector.pushBack(bulletLeft); _bulletVector.pushBack(bulletRight); bulletLeft->bulletMove(ccpSub(getShootPostion(), Point(7, 0))); bulletRight->bulletMove(ccpAdd(getShootPostion(), Point(7, 0))); }
当然,子弹的发射我们需要通过定时器去处理间隔,而子弹的切换也就是关闭单排子弹的定时器,开启双排子弹的定时器,然后再倒过来,这个说应该好理解点。说白了就是对定时器的操作而已。
//单排子弹的发射,用定时器实现,传入参数为延迟时间dt void Weapon::startBullet1Shoot(float dt) { this->bulletType = Bullet1; this->schedule(schedule_selector(Weapon::addBullet), 0.3f, kRepeatForever, dt); } //单排子弹的停止发射,停止发射器 void Weapon::stopBullet1Shoot() { this->unschedule(schedule_selector(Weapon::addBullet)); } //双排子弹的发射 void Weapon::startBullet2Shoot() { this->bulletType = Bullet2; this->schedule(schedule_selector(Weapon::addMultiBullet), 0.3f, 20, 0); } //双排子弹的停止发射 void Weapon::stopBullet2Shoot() { this->unschedule(schedule_selector(Weapon::addMultiBullet)); }
有个比较重要但是简单的就是,记得移除子弹,除了在层中移除,还要从子弹管理器中移除哦!
//移除子弹,还要注意从子弹管理容器中除去 void Weapon::removeBullet(Bullet* bullet) { _bulletVector.eraseObject(bullet); this->removeChild(bullet, true); }
3.0
版本的触摸事件非常简单,就是添加触摸监听器,然后绑定触摸事件,再添加到事件分发器中然系统处理即可,使用起来确实很容易。如下:
//添加触摸事件,并添加到分发器中去 touchListener = EventListenerTouchOneByOne::create(); touchListener->setSwallowTouches(true); touchListener->onTouchBegan = CC_CALLBACK_2(Weapon::onTouchBegan, this); touchListener->onTouchMoved = CC_CALLBACK_2(Weapon::onTouchMoved, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, _heroPlane);触摸一开始要判断触摸点是否在认可的范围内,在的话可以返回true来继续后面的触摸移动的操作,不然返回false不作处理。
//触摸开始事件 bool Weapon::onTouchBegan(Touch* touch, Event* event) { //获得飞机的触摸范围 auto node = dynamic_cast<HeroPlane*>(event->getCurrentTarget()); Rect rect = node->getHero()->getBoundingBox(); //扩大触摸点的范围 Rect touchRect = Rect(rect.origin.x-5, rect.origin.y-5, rect.size.width+10, rect.size.height+10); Point point = touch->getLocation(); //判断触摸点是否在触摸允许范围内 if(touchRect.containsPoint(point)) { return true; } return false; }
触摸移动的过程中注意对坐标的处理,别让飞机离开屏幕太多!!不然看不到飞机了,还玩个毛线。
//触摸移动事件 void Weapon::onTouchMoved(Touch* touch, Event* event) { auto node = dynamic_cast<HeroPlane*>(event->getCurrentTarget()); Size winSize = CCDirector::getInstance()->getWinSize(); //设置范围 auto point = node->getHero()->getPosition()+touch->getDelta(); point.x = MAX(point.x , PlaneWidth*0.4); point.x = MIN(point.x , (winSize.width - PlaneWidth*0.4)); point.y = MAX(point.y , PlaneHeight*0.4); point.y = MIN(point.y , (winSize.height - PlaneHeight*0.4)); node->getHero()->setPosition(point); }
另外还有就是,当我们想暂停游戏的时候,可以调用Director::getInstance()->pause();但是这样子不能暂停触摸操作,所以触摸暂停和恢复还要另作处理,这个也不难,就调用自带的触摸暂停和恢复函数就可以了。
//暂停触摸,用于控制游戏暂停 void Weapon::pauseTouch() { _eventDispatcher->pauseEventListenersForTarget(_heroPlane); } //恢复触摸,用于控制恢复游戏 void Weapon::resumeTouch() { _eventDispatcher->resumeEventListenersForTarget(_heroPlane); }
这里先写好,方便在GameScene中进行调用。其他的没什么好说的了,我们的Weapon就负责这么多。
先来介绍一下Enemy类,继承自Sprite,定义了敌机的基本操作。由于继承了Sprite,所以我们要重新写创建和初始化的操作。如下:
Enemy* Enemy::createEnemy() { Enemy* enemy = new Enemy(); if(enemy) { enemy->initWithTexture(nullptr, Rect::ZERO); enemy->autorelease(); return enemy; } CC_SAFE_DELETE(enemy); return nullptr; } void Enemy::initEnemyWithFrameName(std::string filename) { this->initWithSpriteFrameName(filename); }
//随机获得敌机发射位置 cocos2d::Point getEnemyOriginPosition(cocos2d::Size enemySize); //随机获得初始速度 int getSpeed(); //敌机下落 void enemyFly(cocos2d::Point pos); //敌机销毁 void FlyEnd(); //敌机爆炸 void enemyBlowUp(std::string animationName); //设置生命值 void setLife(int life); //判断是否死亡 bool isDead();
这些和子弹类的写法差不多,在这里就不加以说明了,多出的也就是添加一个记录生命值的变量,然后通过它来判断敌机是否死亡而已。另外还有个重要处理就是敌机的爆炸,我这里通过传入缓存好的爆炸动画名来进行不同爆炸效果的演示。
//敌机爆炸,传入的参数为爆炸动画的名称 void Enemy::enemyBlowUp(std::string animationName) { auto animate = Animate::create(AnimationCache::getInstance()->getAnimation(animationName)); auto hide = Hide::create(); auto callback = CallFunc::create(CC_CALLBACK_0(Enemy::FlyEnd, this)); this->stopAllActions(); this->runAction(Sequence::create(animate, hide, callback, NULL)); }
敌机管理层EnemyLayer
这个层主要的工作是按照定时器的功能来触发敌机,原理和之前的子弹发射一样。不过这里用了3个vector容器来管理不同类型的敌机。如果对于之前子弹管理有了了解,这个就就好处理了。以敌机1的发射为例:
//添加敌机1 void EnemyLayer::addEnemy1(float dt) { //创建对象 Enemy* enemy1 = Enemy::createEnemy(); enemy1->initEnemyWithFrameName("enemy1.png"); //设置生命值 enemy1->setLife(1); this->addChild(enemy1,2); //添加到敌机1的容器中 _enemy1Vector.pushBack(enemy1); //设置敌机飞行 enemy1->enemyFly(enemy1->getEnemyOriginPosition(Size(57,43))); }
然后把这个函数和定时器绑定就可以了。当然,和子弹的管理器一样,别忘了也要在移除的时候从管理器中进行移除工作!!!
为了在以后随着分数的增长提升难度,所以我们要在调用定时器的时候留一个参数传入的位置,以根据不同状态改变定时器的调用频率控制游戏难度。如下:
void EnemyLayer::setLevelFactor(float factor) { this->stopEnemy(); this->startEnemy(factor); } //根据因子发射敌机 void EnemyLayer::startEnemy(float factor) { this->schedule(schedule_selector(EnemyLayer::addEnemy1), factor*5, kRepeatForever, factor*3); this->schedule(schedule_selector(EnemyLayer::addEnemy2), factor*15, kRepeatForever, factor*10); this->schedule(schedule_selector(EnemyLayer::addEnemy3), factor*30, kRepeatForever, factor*30); } //关闭敌机发射 void EnemyLayer::stopEnemy() { this->unschedule(schedule_selector(EnemyLayer::addEnemy1)); this->unschedule(schedule_selector(EnemyLayer::addEnemy2)); this->unschedule(schedule_selector(EnemyLayer::addEnemy3)); }
这个层实现起来也是没有难度啊!
接下来是UFO层UfoLayer,这令游戏变得更加有趣。这个层主要负责随机发射ufo,管理Bomb的显示和数目。在管理方面,同样使用了Vector容器来管理发射的UFO(感觉有点大材小用的感觉,因为ufo的数量不多啊),不过为了统一起来,没关系拉,照样用vector,挺方便(--!)。先来看看发射ufo:
//添加ufo void UfoLayer::addUfo(float dt) { int isaddufo = (int)(CCRANDOM_0_1()*2); do { CC_BREAK_IF(isaddufo); //随机生成ufo int type = (int)(CCRANDOM_0_1()*2+1); String* ufoName = String::createWithFormat("ufo%d.png", type); auto ufoSprite = Sprite::createWithSpriteFrameName(ufoName->getCString()); this->addChild(ufoSprite, 5, type); this->ufoMove(ufoSprite, getRandomPosition(ufoSprite->getContentSize())); //添加到ufo管理容器中去 _ufoVector.pushBack(ufoSprite); }while(0); }
这里我简单地使用isaddufo变量来判断该次调用是否发射ufo,因为这个CCRANDON_0_1()貌似不是很随机,所以效果嘛你懂的!!另外的发射后的处理同子弹和敌机的原理一样,不多说。但是要注意的就是别忘了在层中移除ufo的时候也从管理器中移除。
接下来说说bomb的操作,我们玩游戏就知道,当吃到bomb类型的ufo的时候,会出现bomb标志,相应的数目也会变化,当我们点击的时候,屏幕中的敌机会被销毁,同时也要进行数目的变化。实现起来也不难,就是通过一个bomb数目的变量来进行bomb标识和数目的显示而已。
//碰撞到飞机之后更新bomb的数目 void UfoLayer::updateBombNum() { if(m_bombNum > 0) { String* num = String::createWithFormat("X%d", m_bombNum); bombCount->setString(num->getCString()); } } void UfoLayer::addBombNum() { m_bombNum++; if(!this->getChildByTag(BOMB_ITEM)) { this->addBombItem(); } this->updateBombNum(); } void UfoLayer::subBombNum() { m_bombNum--; if(m_bombNum <= 0) { this->removeChildByTag(BOMB_ITEM, true); this->removeChildByTag(BOMB_NUM, true); } SimpleAudioEngine::getInstance()->playEffect("use_bomb.mp3"); this->destroyAllEnemy(); this->updateBombNum(); }
到这里,我们把游戏的基本元素都实现了,接下来就是交互了,请出我们的联合国来吧。GameScene是处理交互逻辑的重要场所,关键的碰撞处理也在这里。我们来看看。
void checkHeroPlaneWithEnemyCollision();//检测敌机与飞机的碰撞 void checkBulletWithEnemyCollision(); //检测子弹与敌机的碰撞 void checkHeroPlaneWithUFOCollision();//检测ufo和飞机的碰撞
通过调用this->scheduleUpdate();并重载update函数来进行每一帧都进行碰撞检测。先来看看
void checkHeroPlaneWithEnemyCollision();//检测敌机与飞机的碰撞 { Rect planeRect = weapon->getheroPlane()->getHero()->getBoundingBox(); for(auto enemy : enemyLayer->getEnemy1Vector()) { //遍历敌机1容器分别进行碰撞检测 if(planeRect.intersectsRect(enemy->getBoundingBox())) { this->unscheduleUpdate();//关闭碰撞检测 enemy->enemyBlowUp("enemy1DownAnimation"); SimpleAudioEngine::getInstance()->playEffect("enemy1_down.mp3"); m_score += 2; //累积分数 controlLayer->updateScore(m_score); //更新分数显示 weapon->heroBlowUp();//飞机爆炸 } } //敌机2,3的代码这里略去 }
这里的例子是飞机与敌机1的碰撞,就是判断两个对象的Rect是否重合,重合就触发碰撞后的处理。接下来的碰撞检测原理也是一样,就碰撞后处理的对象不同而已,下面贴代码。
//遍历敌机管理容器和子弹管理容器,如过任意两个对象重合,则对生命值进行操作,符合要求的爆炸处理 void GameScene::checkBulletWithEnemyCollision() { for(auto bullet : weapon->getbulletVector()) { auto pos = bullet->getPosition(); for(auto enemy : enemyLayer->getEnemy1Vector()) { auto rect = enemy->getBoundingBox(); if(rect.containsPoint(pos)) { weapon->removeBullet(bullet); if(enemy->isDead()) { enemy->enemyBlowUp("enemy1DownAnimation"); m_score += 2; controlLayer->updateScore(m_score); } break; } } //敌机2,3的代码这里略去 }
//ufo与飞机的碰撞,并进行bomb的添加或者双排子弹的切换 void GameScene::checkHeroPlaneWithUFOCollision() { for(auto ufo : ufoLayer->getufoVector()) { Rect planeRect = weapon->getheroPlane()->getHero()->getBoundingBox(); if(planeRect.intersectsRect(ufo->getBoundingBox())) { if(ufo->getTag() == 1) { SimpleAudioEngine::getInstance()->playEffect("get_double_laser.mp3"); weapon->changeBullet(); } else { SimpleAudioEngine::getInstance()->playEffect("get_bomb.mp3"); ufoLayer->addBombNum(); } ufoLayer->collision(ufo); //添加改变的代码 break; } } }
//消灭所有敌机 void GameScene::eliminateAllEnemy() { for(auto enemy : enemyLayer->getEnemy1Vector()) { enemy->enemyBlowUp("enemy1DownAnimation"); m_score += 2; controlLayer->updateScore(m_score); } enemyLayer->getEnemy2Vector().clear(); for(auto enemy :enemyLayer->getEnemy2Vector()) { enemy->enemyBlowUp("enemy2DownAnimation"); m_score += 5; controlLayer->updateScore(m_score); } enemyLayer->getEnemy2Vector().clear(); for(auto enemy : enemyLayer->getEnemy3Vector()) { enemy->enemyBlowUp("enemy3DownAnimation"); m_score += 10; controlLayer->updateScore(m_score); } enemyLayer->getEnemy3Vector().clear(); } //游戏暂停 void GameScene::pause() { CocosDenshion::SimpleAudioEngine::getInstance()->pauseBackgroundMusic(); CocosDenshion::SimpleAudioEngine::getInstance()->pauseAllEffects(); weapon->pauseTouch(); Director::getInstance()->pause(); } //游戏恢复 void GameScene::resume() { CocosDenshion::SimpleAudioEngine::getInstance()->resumeBackgroundMusic(); CocosDenshion::SimpleAudioEngine::getInstance()->resumeAllEffects(); weapon->resumeTouch(); Director::getInstance()->resume(); } //游戏结束 void GameScene::gameOver() { CocosDenshion::SimpleAudioEngine::getInstance()->stopBackgroundMusic(); CocosDenshion::SimpleAudioEngine::getInstance()->stopAllEffects(); Scene* gameOveScene = TransitionCrossFade::create(1.0f, GameOverLayer::scene(m_score)); Director::getInstance()->replaceScene(gameOveScene); } //改变游戏难度等级 void GameScene::changeLevel(float factor) { enemyLayer->setLevelFactor(factor); }
到这里,游戏基本可以玩起来拉,但是我们还需要显示分数,控制游戏难度和暂停的处理,那么接下来我们要来实现一个控制层ControlLayer。
初始化的时候先把基本元素显示出来。
//添加暂停菜单按钮 pauseItem = MenuItemSprite::create(Sprite::createWithSpriteFrameName("game_pause_nor.png"),Sprite::createWithSpriteFrameName("game_pause_pressed.png"),CC_CALLBACK_0(ControlLayer::pauseCallBack, this)); auto menu = Menu::create(pauseItem, NULL); menu->setPosition(size.width*0.6, winSize.height-size.height*0.6); this->addChild(menu); //添加分数演示标签 scoreLabel = Label::createWithBMFont("font.fnt", "0"); scoreLabel->setPosition(winSize.width-50, winSize.height-20); this->addChild(scoreLabel);
添加好元素,我们重要的就是要处理好回调函数pauseCallBack(),数更新updateScore()和等级设置setLevel()。
//暂停返回事件 void ControlLayer::pauseCallBack() { CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("button.mp3"); //若是未暂停状态则执行暂停处理 if(!Director::getInstance()->isPaused()) { //此处添加暂停处理代码 //把暂停标签按钮设置为恢复游戏的样式 pauseItem->setNormalImage(Sprite::createWithSpriteFrameName("game_resume_nor.png")); pauseItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_resume_pressed.png")); //执行暂停 GameScene* gameScene = (GameScene*)this->getParent(); gameScene->pause(); } else//若已经暂停,再次点击按钮则执行恢复游戏处理 { //把恢复标签设置为暂停游戏的样式 pauseItem->setNormalImage(Sprite::createWithSpriteFrameName("game_pause_nor.png")); pauseItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_pause_pressed.png")); //执行恢复 GameScene* gameScene = (GameScene*)this->getParent(); gameScene->resume(); } } //更新分数,主要用label的setString来实现 void ControlLayer::updateScore(int score) { String* scoreString = String::createWithFormat("%d", score); scoreLabel->setString(scoreString->getCString()); if(level < 4) { setLevel(score); } } //设置等级 void ControlLayer::setLevel(int score) { if(score > levelscore) { level++; levelfactor -= 0.3; levelscore += 50; GameScene* gameScene = (GameScene*)this->getParent(); gameScene->changeLevel(levelfactor); } }
这样子玩起来的时候就没问题了!!!!当然,不可能一直玩下去吧,总有结束的时候,最后我们还需要来添加游戏结束处理层GameOverLayer。
这个层处理的工作前面已经说过了,就是统计分数,显示最高得分和返回菜单,布局什么的你想怎么都行。这个就不说了,要用到的就菜单和标签,实现起来很简单。还有什么声音的添加之类的,都是先预加载,然后在适当的位置播放,在暂停的时候添加暂停播放,恢复的时候添加恢复播放的代码即可,这里都不展开说明了。最后还要提提的就是数据存储,使用UserDefault轻松搞掂。就两三行代码,不贴了!最后
3.0 版本的移植也是很容易的,我是先在Eclipse中打开proj.android,再修改一下平台的信息,修改一下竖屏的参数,然后直接运行就可以拉,就这样就可以在手机上玩了,虽然有点简单,但是是个不错的入门练习哦!!!截个图吧!
文章到这里也结束了,希望再接再厉,深入了解更多游戏开发的知识!!!
发个源码地址:https://github.com/decajes/MyGame