原文位址 http://www.is17.com/40/
學習cocos2dx3.1中,網上教程不算特别多,參照各種教程,慢慢總結編寫,跟引用了一些優秀類,總算做出來了個超簡單的像素鳥遊戲。
現在講解一下核心内容跟,我遇到的一些問題的處理方法。
一、建立項目:
其實網上各種各樣的環境配置眼花缭亂的也不一定配置成功,我們要做的僅僅是建一個可以顯示helloworld的東西。
我這裡有個超簡單的方法。
原材料:vs2012,cocos2dx3.1.1 沒錯,隻需要這些。
打開某英文(中文将導緻編譯失敗)目錄下的D:\cocos2d-x-3.1.1\build\cocos2d-win32.vc2012.sln
右鍵解決方案,生成解決方案,等十幾分鐘吧大概。i3i5表示都是100%cpu,卡的一比。
完了之後,右鍵cpp-empty-test->設為啟動項目。---->F5。不出意外的話出現了helloworld,這麼簡單都失敗的話請百度。
我們的新項目就是改這個東西,我們僅僅是學習,現在不用看那些各種Python的東西來生成新項目====。
二、分析遊戲:
其實我寫的時候是倆眼一抹黑,先做核心,然後在添加各種功能,然後代碼各種改,對于一個沒有學過c++的人,各種問題各種出。
真正做東西我們要理清思路,設計類什麼的更好一些。思路清晰方能事半功倍。
小鳥,背景,水管,地面各做一個類,我寫的倉促,地面并沒有寫成類。
小鳥類核心代碼:Player
bool Player::init()
{
//當時為了顯高端引用了别人的一個大圖類,所有資源打包的那種,其實并不好用,而且我并不知道他的包怎麼打,但是思路挺好,真正的官方用法是引用cocostudio打包
playerSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bird0_0"));
// 注釋掉這一句跟上句相同效果。
// playerSp = Sprite::create("bird0_0.png");
this->addChild(playerSp);
//小鳥飛行動畫
auto animation = Animation::create();
char szName[100] = {0};
//将小鳥的三張圖檔做成動畫,
for( int i=0;i<3;i++)
{
sprintf(szName, "bird0_%d.png", i);
animation->addSpriteFrameWithFile(szName);
}
animation->setDelayPerUnit(1.8f / 14.0f);
auto action = Animate::create(animation);
// 小鳥循環飛行
playerSp->runAction(RepeatForever::create(action));
// 添加剛體,這個類中小鳥自動附加到剛體上
auto body = PhysicsBody::createCircle(playerSp->getContentSize().width * 0.3f);
body->getShape(0)->setFriction(0);
body->getShape(0)->setRestitution(1.0f);
//以下三行設定可以碰撞,具體參數不懂,反正這樣就觸發碰撞了。
body->setCategoryBitmask(1); // 0001
body->setCollisionBitmask(1); // 0001
body->setContactTestBitmask(1); // 0001
this->setPhysicsBody(body);
return true;
}
//小鳥死亡,僅作了停止播放動畫
void Player::die(){
playerSp->stopAllActions();
}
背景類:bggroundlayer
Size visibleSize = Director::getInstance()->getVisibleSize();
/* 生成兩張圖檔放在0和遊戲寬度位置 */
m_bg1= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));
m_bg1->setPosition(Point(0, 0));
m_bg1->setAnchorPoint(Point::ZERO);
//設定抗鋸齒修正拼圖縫隙(這個很有用不加會有一個顯示bug可自己測試)
m_bg1->getTexture()->setAliasTexParameters();
this->addChild(m_bg1);
m_bg2= Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("bg_day"));
m_bg2->setPosition(Point(visibleSize.width, 0));
m_bg2->setAnchorPoint(Point::ZERO);
m_bg1->getTexture()->setAliasTexParameters();
this->addChild(m_bg2);
//這個是根據幀頻率觸發的一個公共方法,來實作地圖的橫向滾動
void BackgroundLayer::logic(float dt)
{
int posX1 = m_bg1->getPositionX(); // 背景地圖1的X坐标
int posX2 = m_bg2->getPositionX(); // 背景地圖2的X坐标
int iSpeed = 1; // 地圖滾動速度,其實這個可以提出來的
/* 兩張地圖向上滾動(兩張地圖是相鄰的,是以要一起滾動,否則會出現空隙) */
posX1 -= iSpeed;
posX2 -= iSpeed;
/* 螢幕寬 */
int iVisibleWidth = Director::getInstance()->getVisibleSize().width;
/* 當第1個地圖完全離開螢幕時,讓第2個地圖完全出現在螢幕上,同時讓第1個地圖緊貼在第2個地圖後面 */
if (posX1 < -iVisibleWidth) {
posX2 = 0;
posX1 = iVisibleWidth;
}
/* 同理,當第2個地圖完全離開螢幕時,讓第1個地圖完全出現在螢幕上,同時讓第2個地圖緊貼在第1個地圖後面 */
if (posX2 < -iVisibleWidth) {
posX1 = 0;
posX2 = iVisibleWidth;
}
m_bg1->setPositionX(posX1);
m_bg2->setPositionX(posX2);
}
管道類:Conduit
寫這個遇到了很大問題,我并沒有接觸過cocos,隻是找例子來參考做,使用了跟小鳥一樣的生成方法,要麼隻能生成一個剛體,要麼生成之後變成了類似于單例的樣子,無法生成多個。後來終于看到了這個方法解決了問題。
int iVisibleWidth = Director::getInstance()->getVisibleSize().width;
int jianHeight = 100;
auto pipeUpSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_up"));
pipeUpSp->setPosition(Point(0, 0));
this->addChild(pipeUpSp);
auto pipeDownSp = Sprite::createWithSpriteFrame(AtlasLoader::getInstance()->getSpriteFrameByName("pipe_down"));
pipeDownSp->setPosition(Point(0, pipeUpSp->getContentSize().height+jianHeight));
this->addChild(pipeDownSp);
// 添加剛體
auto body = PhysicsBody::create();
body->setDynamic(false);
//我們生成了一個剛體,向這裡放入兩個元素。這樣我們就生成了一堆可操作的管道了。
body->addShape(PhysicsShapeBox::create(pipeUpSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, 0)));
body->addShape(PhysicsShapeBox::create(pipeDownSp->getContentSize(),PHYSICSSHAPE_MATERIAL_DEFAULT, Point(0, pipeUpSp->getContentSize().height+jianHeight)));
body->setCategoryBitmask(1); // 0001
body->setCollisionBitmask(1); // 0001
body->setContactTestBitmask(1); // 0001
this->setPhysicsBody(body);
地面我并沒有寫到類裡邊,加載地面也并沒有新技術,就是建立一個Sprite,一個Body。其移動方法如下
ground->setPositionX(ground->getPositionX() - 1);
if(ground->getPositionX() < ground->getContentSize().width/2-24) {
ground->setPositionX(ground->getContentSize().width/2-1);
}
然後我們将這些東西添加到場景類中,在這裡之前我們要修改自帶的類,使之能跳轉到我們的場景類。
我們修改AppDeleGate.cpp-->applicationDidFinishLaunching使其加載loadscene,
director->setOpenGLView(glview);
/* 設定Win32螢幕大小為288X512, */
glview->setFrameSize(288, 512);
/* 簡單的螢幕适配,按比例拉伸,可能有黑邊 */
glview->setDesignResolutionSize(288, 512, ResolutionPolicy::SHOW_ALL);
// turn on display FPS
director->setDisplayStats(true);
// set FPS. the default value is 1.0/60 if you don't call this
director->setAnimationInterval(1.0 / 60);
// 這裡添加load類
// auto scene = TollgateScene::scene();
auto scene = LoadingScene::create();
// run
director->runWithScene(scene);
loadscene類:loadscene中實作了一個開頭切換場景動畫,也可用作實作load進度條之類。
void LoadingScene::onEnter(){
// add background to current scene
auto fileUtils = FileUtils::getInstance();
std::vector<std::string> searchPaths;
searchPaths.push_back("image");
fileUtils->setSearchPaths(searchPaths);
Sprite *background = Sprite::create("splash.png");
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
background->setPosition(origin.x + visibleSize.width/2, origin.y + visibleSize.height/2);
this->addChild(background);
//加載1024的一張大圖
Director::getInstance()->getTextureCache()->addImageAsync("atlas.png", CC_CALLBACK_1(LoadingScene::loadingCallBack, this));
}
void LoadingScene::loadingCallBack(Texture2D *texture){
//配合atlastxt
AtlasLoader::getInstance()->loadAtlas("atlas.txt", texture);
//切換場景,TollgateScene便是我們的主要遊戲場景類
auto scene = TollgateScene::scene();
TransitionScene *transition = TransitionFade::create(1, scene);
Director::getInstance()->replaceScene(transition);
}
然後就是最重要的TollgateScene類了,這裡我們實作了遊戲邏輯。
首先我們添加重力環境,當時我順手把背景也寫在了這裡邊,背景不用重複加載也就沒改。
Scene* TollgateScene::scene()
{
auto scene = Scene::createWithPhysics();
/**設定重力*/
Vect gravity(0, -300.1f);
scene->getPhysicsWorld()->setGravity(gravity);
/* 開啟測試模式 這将會給所有剛體加一個紅框 */
// scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
auto backgroundLayer = BackgroundLayer::create();
//addChild方法添加到場景,第二個參數是層級,越大層級越高越靠前
scene->addChild(backgroundLayer, 0);
auto layer = TollgateScene::create();
scene->addChild(layer, 10);
layer->m_backgroundLayer = backgroundLayer;
return scene;
}
[/cc]
然後我們在init函數裡邊添加一些初始顯示且不怎麼變動的東西。
[cc ]
bool TollgateScene::init()
{
if (!Layer::init())
{
return false;
}
//場景尺寸
visibleSize = Director::getInstance()->getVisibleSize();
//初始化一個個性字型類
Number::getInstance()->loadNumber(NUMBER_FONT.c_str(), "font_0%02d", 48);
//添加開始按鈕
this->initGame();
//添加地面
this->setGround();
return true;
}
/**添加開始按鈕 按鈕點選會觸發遊戲開始事件。*/
void TollgateScene::initGame(){
auto closeItem = MenuItemImage::create( "play_0.png", "play_1.png", CC_CALLBACK_1(TollgateScene::startGame,this));
closeItem->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 ));
// create menu, it's an autorelease object
menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
}
最最重點的遊戲開始了:(其實并不複雜,我們設定了初始等級,隐藏開始按鈕,添加小鳥與管道,添加随幀事件,添加碰撞事件,邏輯很清晰)
void TollgateScene::startGame(Ref* sender){
level = 0;
showLevel(level);
menu->setVisible(false);
this->setConduit();
this->setBird();
this->schedule(schedule_selector(TollgateScene::logic));
//監聽事件
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = CC_CALLBACK_2(TollgateScene::onTouchBegan, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(TollgateScene::onContactBegin,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
}
//加載小鳥
void TollgateScene::setBird(){
player = Player::create();
player->setPosition(Point(visibleSize.width / 2, visibleSize.height /2 +100));
this->addChild(player,5);
}
//加載管道,我們把管道添加到一個數組裡邊,以遍來播放管道動畫,其實我們隻用到了兩組管道來重複播放。
void TollgateScene::setConduit(){
for (int i = 0; i < 2; i++) {
conduit = Conduit::create();
conduit->setPosition(Point(visibleSize.width + i*PIP_INTERVAL + PIP_WIDTH, this->getRandomHeight()));
this->addChild(conduit,4);
this->pips.push_back(conduit);
}
}
//給管道一個随機高度
int TollgateScene::getRandomHeight() {
Size visibleSize = Director::getInstance()->getVisibleSize();
return rand()%(int)(2*PIP_HEIGHT + PIP_DISTANCE - visibleSize.height);
}
//點選螢幕 簡單的給小鳥添加一個向上的力量
bool TollgateScene::onTouchBegan(Touch* touch, Event* event) {
player->getPhysicsBody()->setVelocity(Vect(0, 180));
return true;
}
//碰撞觸發遊戲結束事件,顯示按鈕,遊戲結束,小鳥死亡。(好吧這個函數裡并沒寫)
bool TollgateScene::onContactBegin(const PhysicsContact& contact) {
menu->setVisible(true);
this->gameOver();
return true;
}
//遊戲結束 停止幀事件,結束偵聽事件,清空數組,移除顯示元素。
void TollgateScene::gameOver(){
this->unschedule(schedule_selector(TollgateScene::logic));
_eventDispatcher->removeAllEventListeners();
for (auto singlePip : this->pips) {
this->removeChild(singlePip);
}
pips.clear();
player->die();
this->removeChild(player);
this->removeChild(menu);
this->removeChild(scoreSprite);
this->initGame();
}
還有一些動态的東西,并不多:
[cc ]
//背景移動
void TollgateScene::logic(float dt)
{ //調用背景移動
m_backgroundLayer->logic(dt);
//這裡好像介紹過了
ground->setPositionX(ground->getPositionX() - 1);
if(ground->getPositionX() < ground->getContentSize().width/2-24) {
ground->setPositionX(ground->getContentSize().width/2-1);
}
for (auto singlePip : this->pips) {
//判斷過關,當管道x<小鳥x
singlePip->setPositionX(singlePip->getPositionX() - 1);
if(singlePip->getPositionX() == visibleSize.width/2-PIP_WIDTH) {
level++;
showLevel(level);
}
//當管道從這個移出螢幕了再讓他從另一邊進來。
if(singlePip->getPositionX() < -PIP_WIDTH) {
singlePip->setPositionX(visibleSize.width+PIP_WIDTH/2);
singlePip->setPositionY(this->getRandomHeight());
}
}
}
//顯示關卡等級 Number類會根據數字傳回一個精靈,也就是圖檔版的數字,比較好看而已。當過關了就刷一下。
void TollgateScene::showLevel(int lev){
this->removeChild(scoreSprite);
scoreSprite = (Sprite* )Number::getInstance()->convert(NUMBER_FONT.c_str(), lev);
scoreSprite->setPosition(Point(visibleSize.width/2-scoreSprite->getContentSize().width/2,visibleSize.height*7/8));
this->addChild(scoreSprite,20);
}
講解跟流程寫完了,可能還遺漏了一些東西大緻流程就是這樣了,請忽略排版,我的編輯器是最原始的。