• 学习实战三:基于Cocos2d-x引擎模仿微信打飞机游戏


    学习Cocos2d-x游戏引擎有一个来月了,这一个来月的时间里,做了两个小游戏,一个是模仿的打地鼠游戏(做了大概十天);另一个是模仿的打飞机游戏(做了五天)。关于前一个,只是在网上下了个叫做疯狂地鼠的安卓版游戏,然后便开始模仿,用的游戏素材也是那个安装包里提取出来的,对这个游戏的模仿应该说是限于用了素材吧,具体的功能实现是自己想的,因为没有源码可以看。而第二个游戏,微信打飞机,因为前段时间这个游戏火了一把,所以有网友利用Cocos2d引擎和Cocos2d-x引擎做出来了。我做完打地鼠游戏之所以选择了做打飞机这个游戏,也主要是看中了网上有教程。因为自己刚学,所以之前那个打地鼠的游戏只能说有功能了,而有些功能的代码为什么要这么写,我还不是很清楚,我只知道有这个功能,我就模仿,或者说引擎自带了某个效果,我就把这个效果做到我的游戏中了。所以总的来说有点朦胧感,因而想通过做个有系统讲解某个游戏怎么做,有源码可以看的来学着做。

    微信打飞机这个游戏,是跟着CSDN上一个博客专栏写的,在前几篇的博客中提到过。他的专栏没有全看完,只是看了前几篇的介绍,了解了下大致的写的思路,然后主要是看他给的源码了。

    做这个游戏的过程中,前期工作:如子弹的生成、敌机的生成、碰撞检测、利用数组对子弹和敌机进行管理,这两块模仿着源码做的,也可以说是抄了一遍。然后逐步深入,对这个游戏的主要功能的理解加深,后面的工作主要是自己来做了,源码只是在遇到了某个困难、或者说某个功能没有思路了就去看了看。对于这个游戏我自己感觉做的好的地方主要有:后期自己写了关于敌机生成的代码,作者是单独控制三种飞机的生成,而我后来是重写了这块的代码,单独写敌机类,在初始化的时候根据初始化参数来生成不同的飞机,具体代码,enemy类,继承自CCNode:

    enemy.h
    typedef enum
    {
    	k_Enemy_Type_Small=0,
    	k_Enemy_Type_Middle,
    	k_Enemy_Type_Large,
    	k_Enemy_Type_Count
    }EnemyType;

    首先定义了三种飞机。然后重写了enemy的create函数,使能传入一个飞机类型的参数:

    Enemy* Enemy::create(EnemyType type)
    {
    	Enemy* enemy=new Enemy();
    	enemy->init(type);
    	enemy->autorelease();
    	return enemy;
    }


    接着在enemy的init函数中根据传递过来的飞机类型参数来生成不同类型的飞机:

    bool Enemy::init(EnemyType type/* =k_Enemy_Type_Small */)
    {
    	_type=type;
    	_life=pow((double)type,2)*16+1;
    	CCString* frameName=CCString::createWithFormat("enemy%d.png",type);
    	_enemy=CCSprite::createWithSpriteFrameName(frameName->getCString());
    	this->addChild(_enemy);
    	return true;
    }


    这样就实现了一个函数控制不同类型飞机的生成了,代码显得更为简洁。最后在飞机显示的enemyLayer类中飞机生成类型的参数:

    void EnemyLayer::update(float delta)
    {
    	addSmall++;
    	addMiddle++;
    	addLarge++;
    	float speed=gameSpeed;
    	if (addSmall>50-gameSpeed)
    	{
    		Enemy* enemySmall=Enemy::create(k_Enemy_Type_Small);
    		enemySmall->setTag(k_Enemy_Type_Small);
    		this->flyTo(enemySmall,3.0f-speed);
    		addSmall=0;
    	}
    	if (addMiddle>300-gameSpeed)
    	{
    		Enemy* enemyMiddle=Enemy::create(k_Enemy_Type_Middle);
    		enemyMiddle->setTag(k_Enemy_Type_Middle);
    		this->flyTo(enemyMiddle,5.0f-speed);
    		addMiddle=0;
    	}
    	if (addLarge>800-gameSpeed)
    	{
    		Enemy* enemyLarge=Enemy::create(k_Enemy_Type_Large);
    		enemyLarge->setTag(k_Enemy_Type_Large);
    		enemyLarge->getEnemySprite()->runAction(enemyLarge->flyAction());
    		this->flyTo(enemyLarge,6.0f-speed);
    		addLarge=0;
    	}
    }


    这里巧妙的利用引擎的定时器功能(这个方法是参考另外一个网友的,通过设定不同的数值来生成不同的飞机),而我在这里加入的功能主要是加入了游戏速度的影响功能,即代码中的gameSpeed参数,这个参数会根据游戏得分的增加而变动,数值会变大,然后在这里的影响就是每种飞机的生成时间会随着gameSpeed数值增大而缩短。可以在代码中看到,每个if函数的函数体中enemy的create参数都是不同的,参数不同就会生成不同的敌机。

    敌机生成之后,要用一个数组控制飞机,我参考的博客专栏中作者是用了三个数组,而我想到,在之后的碰撞检测中会增加代码量,所以只用到一个数组。前段的update函数中每个if函数体只控制飞机的生成,而飞机的飞行没有控制,我把控制独立的抽象为一个函数,并且这个函数带enemy类型对象的参数和一个float型的参数,这个float参数是控制飞机的飞行时间的,而enemy类型的对象主要是为了设置飞机的初始位置而用,具体代码如下:

    void EnemyLayer::flyTo(Enemy* sender,float speed)
    {
    	CCSize winSize=CCDirector::sharedDirector()->getWinSize();
    	//计算飞机随机产生的横坐标
    	int max=winSize.width-sender->getEnemySprite()->getContentSize().width/2;
    	int min=sender->getEnemySprite()->getContentSize().width/2;
    	float randomX=rand()%max;
    	if (randomX<min)
    	{
    		randomX+=min;
    	}
    
    	//设定位置
    	sender->setPosition(ccp(randomX,winSize.height));
    	this->addChild(sender);
    	allEnemys->addObject(sender);
    
    	//飞行目的地
    	CCPoint pos=CCPointMake(randomX,-sender->getEnemySprite()->getContentSize().height/2);
    	//飞机飞向目的地
    	CCMoveTo* flyTo=CCMoveTo::create(speed,pos);
    	flyTo->setTag(9);
    	CCCallFuncN* moveDone=CCCallFuncN::create(this,callfuncN_selector(EnemyLayer::moveDone));
    	CCSequence* seq=CCSequence::create(flyTo,moveDone,NULL);
    	sender->runAction(seq);
    }


    可以看到这段代码也是较短的。同一个函数控制三种不同类型飞机的飞行,并且这里是同一个数组控制不同类型的飞机,这个是我自己想的功能,当然也是在那位博客作者提出了思路,我照抄一遍后才想到了新的方法。

    可以说,整个游戏代码中,我对这段代码的满意度是最高的,因为这个复杂度我感觉是整个代码文件中最复杂的一段,所以有点小小的满足感。这个工作做好了之后,游戏后段的碰撞检测代码量也少了很多,比如做敌机和子弹的检测只要一个函数就可以,而敌机和英雄飞机的碰撞检测也只需要一个函数就可以解决:

    //敌机和子弹的碰撞检测
    void GameScene::collisionBetweenBulletAndEnemy()
    {
    	CCObject *bobj,*eobj;
    	//遍历子弹数组
    	CCARRAY_FOREACH(_bulletLayer->allBulltets,bobj)
    	{
    		CCSprite* bullet=(CCSprite*)bobj;
    		CCArray* enemys=_enemyLayer->getEnemys();
    		CCARRAY_FOREACH(enemys,eobj)
    		{
    			Enemy* enemy=(Enemy*)eobj;
    			int type=enemy->getType();
    			if (enemy->getBoundingBox().intersectsRect(bullet->boundingBox()))
    			{
    				if(enemy->getEnemyLife()==1)
    				{
    					this->alterScore((EnemyType)type);
    					bullet->setVisible(false);
    					_enemyLayer->enemyBlowUp(enemy,(EnemyType)type);
    					enemys->removeObject(enemy);
    				}
    				if (enemy->getEnemyLife()>1)
    				{
    					_bulletLayer->allBulltets->removeObject(bullet);
    					bullet->setVisible(false);
    					enemy->enemyBeHit(enemy,(EnemyType)type);
    					enemy->lostLife(1);
    				}
    			}
    		}
    	}
    }
    //英雄飞机和敌机的碰撞检测
    void GameScene::collisionBetweenHeroAndEnemy(bool enable)
    {
    	if (enable)
    	{
    		//遍历所有敌机
    		CCObject* bobj;
    		CCArray* enemys=_enemyLayer->getEnemys();
    		CCARRAY_FOREACH(enemys,bobj)
    		{
    			Enemy* enemy=(Enemy*)bobj;
    			if (_planeLayer->getChildByTag(AirPlane)->boundingBox().intersectsRect(enemy->getBoundingBox()))
    			{
    				//CCLog("collision");
    				_planeLayer->isCollision();
    				this->over();
    			}
    		}
    	}
    }


    可以看到,代码量少了很多。

    我感觉关于敌机生成、子弹生成、碰撞检测这三块是整个游戏主要的工作量所在,因而这三块所占用的时间大概有三天左右吧~(我的时间是几乎一天的时间都用在了这个上面,有的时候晚上也会写代码写到十点多,所以一天的时间花的比较多了),做完了这三块,剩下的工作量就少多了,主要是道具和得分显示的功能实现了。

    关于道具显示。因为有了关于敌机生成的相关经验,在道具生成这里我也是用的一个函数控制两种道具,思路和敌机类类似,代码:

    void PropLayer::initThisType(PropType type/* =k_Prop_Bomb */)
    {
    
    	CCSize winSize=CCDirector::sharedDirector()->getWinSize();
    
    	CCString* propName=CCString::createWithFormat("prop_type_%d.png",type);
    	_prop=CCSprite::createWithSpriteFrameName(propName->getCString());
    
    	_prop->setTag((int)type);
    	//计算道具随机产生的横坐标
    	int max=winSize.width-_prop->getContentSize().width/2;
    	int min=_prop->getContentSize().width/2;
    	float randomX=rand()%max;
    	if (randomX<min)
    	{
    		randomX+=min;
    	}
    	//设定位置
    	_prop->setPosition(ccp(randomX,winSize.height));
    	this->addChild(_prop);
    	allProps->addObject(_prop);
    
    	//道具飞行动画
    	CCMoveTo* move1=CCMoveTo::create(0.2f,ccp(randomX,winSize.height*0.7));
    	CCMoveTo* move2=CCMoveTo::create(0.4f,ccp(randomX,winSize.height*0.75));
    	CCMoveTo* move3=CCMoveTo::create(0.4f,ccp(randomX,0));
    	CCCallFuncN* remove=CCCallFuncN::create(this,callfuncN_selector(PropLayer::moveDone));
    	
    	CCSequence* seq=CCSequence::create(move1,move2,move3,remove,NULL);
    	_prop->runAction(seq);
    }

    在这里考虑到道具量少,只有两个,且不是主要的游戏元素。所以我没有单独写道具类,只是单独写了一个道具层在层中写了一个initThisType函数,用于控制不同的道具类型。然后通过定时器控制道具的生成:

    void GameScene::addProp(float dt)
    {
    	int i=rand()%2;
    	_propLayer->initThisType((PropType)i);
    }


    随机生成道具的类型。之后便是道具和英雄飞机的碰撞检测了:

    void GameScene::collisionBetweenHeroAndProp()
    {
    	CCObject* pobj;
    	CCARRAY_FOREACH(_propLayer->allProps,pobj)
    	{
    		CCSprite* prop=(CCSprite*)pobj;
    		if (prop->boundingBox().intersectsRect(_planeLayer->getBoundingBox()))
    		{
    			CCLog("Get porp");
    			if (prop->getTag()==(int)k_Prop_Bullet)
    			{
    				AudioEngine::sharedEngine()->playEffect("sound/get_double_laser.mp3");
    				_bulletLayer->switchBulletType(k_Bullet_Double);
    				this->setDPStatus(true);
    			}
    			if (prop->getTag()==(int)k_Prop_Bomb)
    			{
    				AudioEngine::sharedEngine()->playEffect("sound/get_bomb.mp3");
    				int i=this->getBombNumber();
    				i++;
    				_panelLayer->getChildByTag(800)->setVisible(true);
    				_panelLayer->getChildByTag(801)->setVisible(true);
    				_panelLayer->alterBomb(i);
    				this->setBombNumber(i);
    			}
    			_propLayer->allProps->removeObject(prop);
    			_propLayer->removeChild(prop);
    		}
    	}
    	if (this->getDPStatus()==true)
    	{
    		bulletLastTime--;
    		if (bulletLastTime==0)
    		{
    			_bulletLayer->switchBulletType(k_Bullet_Single);
    			bulletLastTime=1200;
    			this->setDPStatus(false);
    		}
    	}
    }


    这个函数感觉有点乱。主要是两种道具的功能不一样,一个是得到炸弹,另一个是双排子弹,且双排子弹是由时间控制的,所以写了一个if函数用来控制双排子弹的存在时间。得到道具之后便是激活相关的功能了。炸弹道具的使用便是遍历一遍敌机数组,使屏幕上的所有敌机全部调用单个飞机挨打的爆炸动画:

    //全部爆炸
    void EnemyLayer::allBlowUp(CCArray* enemys)
    {
    	GameScene* pGameScene=(GameScene*)this->getParent();
    	CCObject* obj;
    	CCARRAY_FOREACH(enemys,obj)
    	{
    		Enemy* enemy=(Enemy*)obj;
    		if (enemy->getEnemyLife()>0&&enemy->isRunning())
    		{
    			int type=enemy->getType();
    			this->enemyBlowUp(enemy,(EnemyType)type);
    			pGameScene->alterScore((EnemyType)type);
    			enemys->removeAllObjects();
    		}	
    	}
    }


    而关于双排子弹,则是两种类型子弹间的切换。因为子弹的生成是用定时器生成的,所以单排子弹生成的时候要停止双排子弹的生成定时器,而双排子弹生成的时候则相反,我实现的具体方法是:

    //子弹切换
    void BulletLayer::switchBulletType(BulletType type)
    {
    	if (type==k_Bullet_Single)
    	{
    		this->unschedule(schedule_selector(BulletLayer::addDoubleBullet));
    		this->schedule(schedule_selector(BulletLayer::addSingleBullet),0.1f);
    	}
    	else if (type==k_Bullet_Double)
    	{
    		this->unschedule(schedule_selector(BulletLayer::addSingleBullet));
    		this->schedule(schedule_selector(BulletLayer::addDoubleBullet),0.1f);
    	}
    }


    这段代码我没有把握,不知道是不是这样做的。切换代码后就是不同种类子弹的飞行了。


    游戏中主要的东西便是这些,我写的也主要是这些。而关于游戏得分的显示,我参考了专栏的作者,因为那个确实不会,参考之后也需要加强学习才行啊。

    最后有一个关于历史得分的功能,我这里用到了引擎的CCUserDefault方法,直接用的明文存储。并且才游戏结束后有个历史得分和本局得分的比较:

    //历史最高
    	highScore=CCUserDefault::sharedUserDefault()->getIntegerForKey("HighScore");
    	if (highScore>finalScore)
    	{
    		CCString* strHighScore=CCString::createWithFormat("%d",highScore);
    		CCLabelBMFont* high=CCLabelBMFont::create(strHighScore->m_sString.c_str(),"fonts/font.fnt");
    		high->setColor(ccc3(143,146,147));
    		high->setPosition((ccp(winSize.width/2,winSize.height*0.65)));
    		this->addChild(high);
    	}
    	else if(highScore<finalScore)
    	{
    		AudioEngine::sharedEngine()->playEffect("sound/achievement.mp3");
    		CCUserDefault::sharedUserDefault()->setIntegerForKey("HighScore",finalScore);
    		CCString* strHighScore=CCString::createWithFormat("%d",finalScore);
    		CCLabelBMFont* high=CCLabelBMFont::create(strHighScore->m_sString.c_str(),"fonts/font.fnt");
    		high->setColor(ccc3(143,146,147));
    		high->setPosition((ccp(winSize.width/2,winSize.height*0.65)));
    		this->addChild(high);
    	}


    哪个分值高,历史最高得分就是显示哪个~

    好啦,差不多啦~~~作为一个非计算机专业的文科生,在学习了两个来月的C++和一个来月的Cocos2d-x引擎后能写出两个小游戏,我觉得挺开心的。

    来几张游戏截图吧~







    源码地址:戳这里第一次用github,折腾了好久的说。。。


  • 相关阅读:
    Converting PDF to Text in C#
    Working with PDF files in C# using PdfBox and IKVM
    Visualize Code with Visual Studio
    Azure Machine Learning
    Building Forms with PowerShell – Part 1 (The Form)
    ML.NET is an open source and cross-platform machine learning framework
    Microsoft Visual Studio Tools for AI
    Debugging Beyond Visual Studio – WinDbg
    Platform.Uno介绍
    Hawk-数据抓取工具
  • 原文地址:https://www.cnblogs.com/zhong-dev/p/4044608.html
Copyright © 2020-2023  润新知