天天看點

Cocos2d-x3.X入門遊戲執行個體(一) 經典飛機大戰開發筆記

         有一個多月沒有寫學習筆記了,主要是在學習了一段時間後覺得想整個小遊戲,選來選去選擇了微信飛機大戰作為處女作。飛機大戰内容簡單,比較适合剛學習Cocos2dx的新手拿來練手,而且網上的教程和源碼都很多,我就是在Jacky的飛機大戰專欄的幫助下完成的。是以代碼我就不會細說了,主要是為了記錄和總結開發過程中遇到的問題。想看代碼詳解的可以去Jacky的專欄,這裡給對外連結接:http://blog.csdn.net/column/details/jackyairplane.html

         這裡說一下,之前一直都用2.2.3來學習,不過3.0貌似已經出來蠻久了,而且朋友的公司也都在用3.0開發,是以我也決定更新。在官網下載下傳了最新的3.1版本,花了一天時間把之前所有的例子都更新了,其實差别也不是太大很快就能适應。好了,進入正題。

一、背景滾動

    如何讓背景滾動,讓飛機看起來像在往前飛?這大概是大部分童鞋遇到的第一個問題。思考一下會發現實作起來不難。基本原理就是:建立2個同樣圖檔的背景精靈,圖檔的height要大于視窗的height,1号精靈放在正常的位置,并将2号精靈的位置設定在1号精靈的上面,然後定時器調用精靈位置移動的方法使圖檔看起來像在滾動。

//加載background1,background1和background2
		m_spBackground1 = Sprite::createWithSpriteFrameName("background.png");
		m_spBackground1->setAnchorPoint(Point(0, 0));
		m_spBackground1->setPosition(Point(0, 0));
		addChild(m_spBackground1);

		m_spBackground2 = Sprite::createWithSpriteFrameName("background.png");
		m_spBackground2->setAnchorPoint(Point(0, 0));
		m_spBackground2->setPosition(Point(0, m_spBackground2->getContentSize().height));
		addChild(m_spBackground2);

		//為防止運作時出現黑線,給兩張背景圖設定抗鋸齒
		m_spBackground1->getTexture()->setAliasTexParameters();
		m_spBackground2->getTexture()->setAliasTexParameters();

		//定時器調用背景移動函數,實作背景滾動  
		this->schedule(schedule_selector(GameLayer::backgroundOnMove), 0.01f);
           
//背景滾動函數
void GameLayer::backgroundOnMove(float dt){
	//背景1的Y坐标每次-2
	m_spBackground1->setPositionY(m_spBackground1->getPositionY() - 2);
	//背景2的Y坐标在背景1的基礎上-2,這裡-2是為了讓兩張圖檔有部分重疊,以免看到交界處的黑線
	m_spBackground2->setPositionY(m_spBackground1->getPositionY() + m_spBackground1->getContentSize().height - 2);
	//當背景2的Y坐标滾動到了0時,馬上将背景1的Y坐标還原,這樣又回到了最初的位置設定
	if (m_spBackground2->getPositionY() <= 0)
	{
		m_spBackground1->setPositionY(0);
	}
}
           

背景滾動這樣就實作了,确實很簡單,這是豎屏的實作方法,如果是想橫屏滾動也是同樣的設定方式,這個效果在很多遊戲中都會用到,還是挺實用的。

二、飛機移動

讓飛機跟随手指移動來躲避敵機和接取道具,并設定移動範圍,不讓飛機移動到螢幕外。第一點是基本的觸摸功能的實作,第二點隻要判斷手指所在位置是否螢幕邊緣,如果過界這限制飛機移動。

void PlaneLayer::onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event){
	//飛機是否存活,存活則可以移動
	if (m_bIsAlive)
	{
		Point touchLocation = this->convertTouchToNodeSpace(touch);
		Point oldTouchLocation = touch->getPreviousLocation();
		oldTouchLocation = this->convertToNodeSpace(oldTouchLocation);
		Point translation = touchLocation - oldTouchLocation;
		this->planeMove(translation);
	}
}
           
void PlaneLayer::planeMove(Point touchLocation){
	if (m_bIsAlive)
	{
		auto newPos = m_spHero->getPosition() + touchLocation;
		auto winSize = Director::getInstance()->getWinSize();
		//移動不能超過螢幕
		int intX = m_spHero->getContentSize().width / 2;
		int intY = m_spHero->getContentSize().height / 2;
		newPos.x = newPos.x <= intX ? intX : (newPos.x >= winSize.width - intX ? winSize.width - intX : newPos.x);
		newPos.y = newPos.y <= intY ? intY : (newPos.y >= winSize.height - intY ? winSize.height - intY : newPos.y);
		m_spHero->setPosition(newPos);
	}
}
           

三、判定範圍

一般的圖檔周圍都有空白區域,如果在判斷飛機是否跟敵機相撞時直接用飛機的boundingBox來判斷則會出現明明沒有撞到卻game over的現象,是以這裡我們要調整一下飛機的判定範圍,将其縮小。具體的數值修正就看大家的圖檔和需要的效果,你想讓遊戲難一點可以把判定範圍設得大一點哦~同理,敵機道具和子彈等都可以這樣做修正。

Rect PlaneLayer::getHeroRect(){
	Rect rect = m_spHero->boundingBox();
	Point pos = this->convertToWorldSpace(rect.origin);
	Rect heroRect(pos.x + 42, pos.y, 20, 80);
	return heroRect;
}
           

四、子彈處理

子彈是怎麼形成的呢?稍微想一想就明白了,每顆子彈都是一個精靈,使用定時器讓它在合适的位置不停出現(飛機前方),并以一定速度往前飛行。不過飛出螢幕和打中敵機時要記得及時删除,不然一直在建立精靈影響性能。飛機大戰的子彈處理比較簡單,隻有主機有子彈,并且都是固定的往前飛的形态,若是想實作雷電、東方之類的彈幕效果,就需要借助一些運動軌迹和算法之類的來實作了。

void BulletLayer::addBullet(float dt)
{
	//加載子彈音效
	CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/bullet.wav");

	//這裡我用了精靈,是實作每次射出兩顆子彈
	auto* bullet = Sprite::createWithSpriteFrameName("bullet1.png");
	auto* b1 = Sprite::createWithSpriteFrameName("bullet1.png");
	
	//設定子彈初始位置
	auto planePosition = PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition();
	auto bulletPosition = Point(planePosition.x + 2,
		planePosition.y + PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getContentSize().height / 2
		+ (bullet->getContentSize().height + 3) * 2);
	bullet->setPosition(bulletPosition);
	
	/*
		雖然發出兩顆子彈,實際上與敵機碰撞的仍然是第一顆,
		第二顆隻是起裝飾效果,是以我将它的父節點設為第一顆子彈
		當第一顆子彈消失後,第二顆也會一起消失
	*/
	bullet->addChild(b1);
	b1->setPosition(Point(bullet->getContentSize().width / 2, -bullet->getContentSize().height + 5));

	//将子彈加入層和Vector,加入Vector是為了友善之後的子彈碰撞和移除
	this->addChild(bullet);
	m_vAllBullet.pushBack(bullet);

	//設定子彈的移動速度,并run移動的動作,在動作結束後調用移除子彈的函數
	int winHeight = Director::getInstance()->getWinSize().height;
	float length = winHeight + bullet->getContentSize().height / 2 - bulletPosition.y;//飛行距離,超出螢幕即結束  
	float velocity = 530 / 1;//飛行速度:420pixel/sec  
	float realMoveDuration = length / velocity;//飛行時間  

	auto* actionMove = MoveTo::create(realMoveDuration, Point(bulletPosition.x, winHeight + bullet->getContentSize().height  / 2));
	auto* actionDone = CallFuncN::create(CC_CALLBACK_1(BulletLayer::bulletMoveFinished, this));
	auto* sequence = Sequence::create(actionMove, actionDone, NULL);
	bullet->runAction(sequence);
}
           
Cocos2d-x3.X入門遊戲執行個體(一) 經典飛機大戰開發筆記

五、單雙子彈之間的銜接

       遊戲中有接到道具變成雙發子彈的,具體實作效果跟單子彈是一樣的。但雙發子彈是有時效的,是以這裡要說一下如何銜接兩種子彈。原理如下:在吃到道具後,stop發射單發子彈的定時器,并且start雙發子彈的定時器,這時雙發子彈定時器就不是永久調用了,而是要設定發射次數,這個次數定多長看遊戲需要。并在若幹秒後再次啟動單發子彈定時器,這個若幹秒其實就是雙發子彈發射若幹次的時間,至于到底是多少秒就要你自己去調試了,我是發現在不同的模拟器或者手機上時間是不一樣的,這東西應該也跟手機性能有關。隻要别太明顯看到兩種子彈一起發出去就行了。額,這個圖太難截了,我就不截了,能明白意思就好。

if (UFOLayer::spMutiBullets != nullptr)
		{
			ufo = UFOLayer::spMutiBullets;
			//判斷飛機是否吃到雙發子彈道具
			if (planeLayer->getHeroRect().intersectsRect(ufo->getBoundingBox())){
				//調用吃到道具音效
				CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("get_double_laser.mp3");
				//移除該道具
				this->ufoLayer->removeUFO(ufo);
				//停止單發子彈定時器
				this->bulletLayer->stopShoot();
				//開啟雙發子彈定時器
				this->mutiBulletsLayer->startShoot();
				//在指定時間後再次開啟單發子彈定時器
				this->bulletLayer->startShoot(17.3f);
			}
		}
           
oid BulletLayer::startShoot(float delay)  
{
	//delay是延遲時間,指延遲多久開始調用定時器,隻在第一次調用時有效
	this->schedule(schedule_selector(BulletLayer::addBullet), 0.2f, kRepeatForever, delay);
}

void MutiBulletsLayer::startShoot()
{
	//這裡設定雙發子彈運作80次
	this->schedule(schedule_selector(MutiBulletsLayer::addMutiBullets), 0.2f, 80, 0);
}
           

六、敵機處理

         敵機的生成跟子彈差不多,不同的是飛行方向與子彈相反,要在螢幕外就開始生成且位置随機,運作速度也要在一定範圍内調整,并根據分數增加要提高移動速度來增加遊戲難度。這款遊戲裡有3種敵機,我們可以根據遊戲運作時間或者獲得分數來設定什麼時候出現難度更高的敵機。道具處理與敵機類似,不再贅述。

void EnemyLayer::addEnemy1(float dt)
{
	//調用綁定敵機1  
	Enemy* enemy = Enemy::create();
	enemy->bindSprite(Sprite::createWithSpriteFrameName(enemy1FrameName), ENEMY1_MAXLIFE);

	//随機初始位置  
	auto enemySize = enemy->getSprite()->getContentSize();
	auto winSize = Director::getInstance()->getWinSize();
	int minX = enemySize.width / 2;
	int maxX = winSize.width - enemySize.width / 2;
	int rangeX = maxX - minX;
	int actualX = (rand() % rangeX) + minX;

	enemy->setPosition(Point(actualX, winSize.height + enemySize.height / 2));
	m_vAllEnemy1.pushBack(enemy);
	this->addChild(enemy);

	//根據遊戲難度給minDuration,maxDuration指派  
	int minDuration, maxDuration;

	switch (iLevel)
	{
	case EASY:
		minDuration = 4.0f;
		maxDuration = 6.0f;
		break;
	case MIDDLE:
		minDuration = 3.5f;
		maxDuration = 4.5f;
		break;
	case HARD:
		minDuration = 2.5f;
		maxDuration = 3.5f;
		break;
	case NIGHTMARE:
		minDuration = 2.0f;
		maxDuration = 3.0f;
		break;
	case CHASM:
		minDuration = 1.6f;
		maxDuration = 2.8f;
		break;
	default:
		minDuration = 4.0f;
		maxDuration = 6.0f;
		break;
	}

	int rangeDuration = maxDuration - minDuration;
	int actualDuration = (rand() % rangeDuration) + minDuration;

	auto *actionMove = MoveTo::create(actualDuration, Point(actualX, 0 - enemy->getSprite()->getContentSize().height / 2));
	auto *actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::enemy1MoveFinished, this));

	auto* sequence = Sequence::create(actionMove, actionDone, NULL);
	enemy->runAction(sequence);
           
void EnemyLayer::startEnemy1(float delay){
	//遊戲開始0.3秒後出現1類敵機,并每0.3秒出現一次
	this->schedule(schedule_selector(EnemyLayer::addEnemy1), 0.3f, kRepeatForever, delay);
}

void EnemyLayer::startEnemy2(float delay){
	//遊戲開始5秒後出現2類敵機,并每5秒出現一次
	this->schedule(schedule_selector(EnemyLayer::addEnemy2), 5.0f, kRepeatForever, delay);
}

void EnemyLayer::startEnemy3(float delay){
	//遊戲開始10秒後出現3類敵機,并每10秒出現一次
	this->schedule(schedule_selector(EnemyLayer::addEnemy3), 10.0f, kRepeatForever, delay);
}
           

七、碰撞處理

       遊戲裡主機與敵機碰撞,主機與道具碰撞,子彈與敵機碰撞都是相同的處理方式,這裡拿主機與敵機的碰撞來做示例。

for (Enemy *et : vAllEnemy){
		//主機與敵機矩形是否相交
		if (planeLayer->getHeroRect().intersectsRect(et->getBoundingBox())){
			//主機播放爆炸動畫
			planeLayer->planeBlowup();
			//停止發射子彈
			bulletLayer->stopShoot();
			//停止生成敵機
			enemyLayer->stopEnemy();
			//移除所有子彈
			bulletLayer->removeAllBullet();
			//移除所有敵機
			enemyLayer->removeAllEnemy();
			//顯示結束畫面
			controlLayer->gotoGameOver(iScore);
			break;
		}
	}
           

這個遊戲呢比較簡單,這裡基本上概括了開發過程中會遇到的一些問題。也許還有一些問題忘了寫,不過現在離我做完這個遊戲有一段時間了,記不太清楚咯~接下來會寫一個跑酷遊戲的開發筆記,哈哈都是些入門遊戲,學習要循序漸進嘛!

本帖源碼下載下傳:飛機大戰源碼及素材下載下傳

繼續閱讀