我們有C++基礎,學習引擎總是急於求成,想立馬做出一款簡單的遊戲給朋友玩。但是我們往往看了很多資料卻一直不知道如何下手去寫,有時候只要能走出第一步我們就會遊刃有餘,但是眼高手低的我們不是大神,需要有人指引一下。這裏我就寫一下我是如何入門學習cocos2dx3.1的,給大家參考一下。
如果你想第一天就寫出微信打飛機,請耐心去閱讀。我也是一個菜鳥,博客難免粗糙和出錯,請大家諒解。加油吧!
我們創建工程後總會自帶一個HelloWorld類,短短的幾行代碼就出來了一個遊戲的雛形,請問我們真的理解它了嗎?如果我們能早一點弄明白這幾行代碼,我們或許會比現在走得更遠。
理解HelloWorld類
HelloWorld去掉退出按鈕只有下面三個函數。
- static cocos2d::Scene* createScene();
- virtual bool init();
- CREATE_FUNC(HelloWorld);//一定要自己看源碼
學習之前我要強調一遍,這三個方法一定要做到透徹理解和重寫。因爲所有的遊戲場景都需要這三個函數。
創建遊戲HelloWorld場景的時候,只需要在AppDelegate寫一句: Helloworld::createScene();
請看它的實現
- // 'scene' is an autorelease object
- auto scene = Scene::create();
- // 'layer' is an autorelease object
- auto layer = HelloWorld::create();
- // add layer as a child to scene
- scene->addChild(layer);
- // return the scene
- return scene;
首先新建一個新場景,然後create一個HelloWorld層並添加到新創建的場景,最後返回這個場景給AppDelegate。
一個場景的創建和展示就是這麼簡單,但是你有沒有發現HelloWorld中沒有create呢?並且沒有調用init()方法呢?我們在HelloWorld中沒有在任何地方看到調用init初始化,那麼這是在哪裏調用的呢?
請看CREATE_FUNC的宏定義:
- #defineCREATE_FUNC(__TYPE__) \
- static __TYPE__*create() \
- { \
- __TYPE__ *pRet = new __TYPE__(); \
- if (pRet && pRet->init()) \
- { \
- pRet->autorelease(); \
- return pRet; \
- } \
- else \
- { \
- delete pRet; \
- pRet = NULL; \
- return NULL; \
- } \
- }
\表示換行,因爲宏定義代碼如果放同一行閱讀不方便。
可以看到CREATE_FUNC宏其實是定義了一個靜態成員函數,這個函數可以new一個新的對象,並對其進行init()操作。
這樣整個Helloworld的邏輯就清晰了:
外部類調用static cocos2d::Scene* createScene();來創建一個新的場景。 CREATE_FUNC(HelloWorld);是定義一個靜態的create類成員函數,並在這個函數中調用virtual bool init();初始化這個場景。
以後每當我們新建一個場景的時候,按照這個格式即可。好了,下面該動手了。
現在請刪除Helloworld新建一個叫MainScene的場景,完善該類,修改AppDelegate.cpp中的場景創建爲auto scene = MainScene::createScene();運行展示新的場景。(這個時候整個屏幕還是黑暗的,,因爲沒有任何元素顯示)。
注意:如果是新手,我建議你再看一下Helloworld裏面這三句代碼的定義,你肯定會出錯。
比如:static方法裏面 auto layer =***(這裏是當前類名,不是Layer)::create();
init()方法最好加virtual修飾,也可以這樣寫, virtual bool init() override;(override表示繼承來的,對它重載)。在init裏面一定先初始化父類Layer::init()。
到這裏應該會自己重寫一個空白的場景了,如果你以前不明白這裏,並且剛纔沒有動手寫的話,那麼再請你刪除CREATE_FUNC這句,自己重寫一個create函數吧。如果你記不起怎麼寫,我不建議你再繼續往下閱讀,學習就到此爲止吧。
動手重寫CREATE_FUNC宏定義
- MainScene *MainScene::create1(){
- auto mainS = new MainScene;
- if ( mainS && mainS->init())
- {
- mainS->autorelease();
- return mainS;
- }
- else
- {
- delete mainS;
- mainS = NULL;
- return nullptr;
- }
- }
修改AppDegate.cpp裏面的HelloWorld::createScene();改爲 auto scene = MainScene::createScene();
再使用一下自己寫的create1: auto layer=MainScene::create1();
這樣我們就完全掌握了遊戲場景的創建和它的原理,其實更重要的是我們認識到了應該去怎麼學,cocos2dx引擎使用了大量的宏定義,我們一定不能只追求表面的用法,而應該深入下去學習宏實現了那些東西。特別是到後面的內存管理更是如此。
總結一下,也再重複一遍,所有遊戲場景的基礎都是這三句代碼:
- static cocos2d::Scene* createScene();
- virtual bool init();
- CREATE_FUNC(HelloWorld);
我在後面每一天新加場景都要手寫一遍,一定要搞把這三句代碼以及實現牢記於心。後面的學習中還會遇到大量的宏,我們要學會跟蹤宏定義,仔細閱讀每一段代碼,這樣我們才能學懂而不是簡單的學會。
好,後面接下來完善我們的第一個遊戲場景。現在新建的場景是空白的,什麼都沒有。我們要嘗試往場景中添加各種展示元素。
這裏有三個定義要搞清楚,場景(scene)、層(layer)、精靈(sprite)。(原諒我表達能力有限,大家最好先閱讀一本cocos2dx的書籍,把一些理論知識弄明白)。
場景可以包含多個層,層可以包含多個精靈。 精靈可以是我們在遊戲看到的所有的元素,比如按鈕、血量條、人物等。比如酷跑中,可以看到近處的背景精靈移動快,遠處的背景精靈移動慢,我們可以添加兩個層到場景中,一個層中循環快速的精靈背景,一個循環慢速的精靈背景。這樣就容易理解它們的概念了吧。
進入正題
Sprite精靈創建
如果你是新手,一定要把下面的代碼自己敲一下。磨刀不誤砍柴工,現在擔心敲代碼耽誤時間,以後只會耽誤更多的時間。
首先在resource文件夾下放一張圖片00191880.jpg(華爲入職時的照片,工號191880,從來沒改過^_^),
然後創建精靈,展示這張圖片。
三句,很簡單,添加到init()函數中吧!
- auto sprite =Sprite::create("00191880.jpg");//auto是C++11的自動推斷變量類型
- sprite->setPosition(200, 200);//設置這個精靈在屏幕的位置
- this->addChild(sprite);//把這個精靈添加到當前層中。
我沒學過OC,我以前做windows客戶端,感覺這種寫法很不習慣,入鄉隨俗吧,如果你連this也不知道是什麼意思的話,建議你看一下C++Primer,這本書很重要。
下面是我練習的代碼,自己嘗試一下吧!
- auto spriteA =Sprite::create("1.png");
- auto spriteB =Sprite::create("2.png");
- auto spriteC =Sprite::create("3.png");
- this->addChild(spriteA);
- this->addChild(spriteB);
- this->addChild(spriteC);
- Size visibleSize =Director::getInstance()->getVisibleSize();
- spriteA->setPosition(visibleSize.width /4-30, visibleSize.height / 2);
- spriteB->setPosition(visibleSize.width /4 * 3+32, visibleSize.height / 2);
- spriteC->setPosition(visibleSize.width /2, visibleSize.height / 8 * 7+50);
- spriteA->setScale(1.5);
- spriteB->setScale(1.5);
- spriteC->setScale(1.5);
如果你閱讀過cocos2dx的書籍或者百度一下的話,相信你上面的代碼一定看得懂。Director::getInstance()是一個單例,獲取整個遊戲的導演類,我不會告訴你後面的getVisibleSize()、getWinSize()還有setScale()是什麼意思的,希望你能養成自己動手去查的習慣。
下面寫一點小菜吧,添加的精靈不會動是不是很沒意思?下面就讓圖片動起來:
在init裏面添加
- this->schedule(schedule_selector(Second::myupdate));
這是一個定時器,每隔一段時間會執行myupdate函數。
myupdate的定義如下(我不會告訴你schedule後面還可以再加一個參數表示隔多久執行一次的,自己查去吧):
- voidSecond::myupdate(float f){//注意有一個float f 形參
- auto sp = this->getChildren();//獲取這個層中所有的孩子,也就是所有的精靈,看不懂?別逗我了,點進去看源碼吧,注意它的返回值類型。
- for (auto a: sp)
- {
- a->setPosition(a->getPosition().x, a->getPosition().y - 2);
- }
- }
上面這段代碼就是移動剛纔你添加的所有的精靈。
for (auto a: sp)看不懂?好吧,這個類似於迭代器的遍歷,你可以改成for(auto a= sp.begin();a!=sp.end();a++){}(原諒我手打代碼,沒有在編譯器寫,因爲我是重新整理的)。
再運行一下你的程序讓它們動起來吧,如果你夠厲害的吧,肯定會有辦法讓它們在屏幕中怎麼都停不下來!
補充:Sprite->setTexture()這個可以修改精靈的材質。
*****************************************************************************************
到這裏,你是不是已經猜到微信打飛機飛機是怎麼移動的了?只要再加一個碰撞檢測就我們就可以實現了。碰撞檢測?咱們不整這麼高端了,其實就是for循環獲取這子彈和飛機的位置,查看子彈精靈是否在飛機精靈的位置啦。
咱們不急,下面學一下標籤(Label),它讓我們可以展示打飛機的分數等。
Label的創建有很多種方法,下面簡單介紹三種
Label::create
Label::createWithTTF
Label::createWithBMFont
學過了Sprite肯定能看懂下面代碼。再說一遍,一定要自己去嘗試一下。
- std::string words = "三翻四復";//windows下是不支持中文的,xcode支持。
- auto label = Label::create(words,"STHeiti", 30);//黑體,三十號
- this->addChild(label);
- TTFConfigconfig("fonts/barnacle.ttf", 25);//TTF字體
- auto labelTTF =Label::createWithTTF(config, "Hello ");
- labelTTF->setPosition(333,333);
- labelTTF->setColor(Color3B(255, 0, 0));
- this->addChild(labelTTF);
- auto labelTTF1 =Label::createWithTTF("ABCDEFG", "fonts/16.ttf", 44);
- labelTTF1->setTextColor(Color4B(255, 0,0, 255));
- labelTTF1->setPosition(444, 444);
- labelTTF1->enableShadow(Color4B::BLUE,Size(10,-10));
- labelTTF1->enableOutline(Color4B::GREEN,3);
- labelTTF1->enableGlow(Color4B::BLACK);
- this->addChild(labelTTF1);
//下面的代碼是我學習了前面的時候自己去寫的,多加嘗試
- std::vector<std::string> names ={"AAAAAAAA","BBBBBBBBBB","CCCCCCCCCCC","DDDDDDDDDDDD"};
- for (auto str : names){
- auto tmpLabel = Label::create(str,"STHeiti", 40);
- tmpLabel->setPosition(visibleSize.width / 2,visibleSize.height-i*45);
- tmpLabel->setColor(Color3B(rand() /255, rand() / 255, rand() / 255));
- tmpLabel->setRotation(rand()/180);
- this->addChild(tmpLabel);
- }
還有一個Label::createWithBMFont("fonts/futura-48.fnt","只能是英文或者數字"),futura-48.fnt字體很漂亮,有需要的我再上傳吧。
上面的代碼還有很多我沒提過的知識點,因爲這些都是我學習的筆記,最近我時間不是很多,所以不會寫得太細,有忽略掉的就自己動手去查吧!相信你的自學能力不會比我差。
setColor是設置顏色。enableShadow加陰影。enableOutline加描邊。還有很多特性,大家有興趣都自己嘗試一下。
*********************************************************************************************
開始打飛機
到這裏一個場景典型的元素就介紹完了,Sprite和lable,下面來個硬菜吧!打飛機!
利用微信打飛機的素材,實現打飛機的基本功能。
有幾個小細節說一下:
1:使用鼠標拖動飛機。
因爲還沒學習事件響應,這裏提前學習一下單點觸控。
在init添加下面這段代碼,屏幕就可以響應單擊事件了。
- auto listen =EventListenerTouchOneByOne::create();
- listen->onTouchBegan =CC_CALLBACK_2(GameScene::onTouchBegan, this);
- listen->onTouchMoved =CC_CALLBACK_2(GameScene::onTouchMoved, this);
- listen->setSwallowTouches(true);
- Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listen,this);
CC_CALLBACK_2表示回調函數接收兩個參數。
onTouchBegan、onTouchMoved等在頭文件聲明,這幾個函數是繼承自基類Layer的。如果你不確定跟基類的函數名是否一致,請在聲明的時候加override,表示重載。注意onTouchBegan是返回值類型是bool,其他的是void。
bool onTouchBegan (Touch*, Event*)override;
void onTouchMoved(Touch*, Event*)override;
- voidGameScene::onTouchMoved(Touch* pTouch, Event* pEvent){
- Point touch =pTouch->getLocation();//返回點擊的位置
- Rect rectPlayer =spPlayer->getBoundingBox();//看返回值類型,應該知道這個是飛機所佔矩形區域的大小
- if(rectPlayer.containsPoint(touch)){//如果點擊的點在這個矩形區域內就可以對飛機進行拖動
- Point temp = pTouch->getDelta();
- spPlayer->setPosition(spPlayer->getPosition() + temp);
- }
- }
getBoundingBox()是獲取精靈所佔的矩形大小,containsPoint()查看點是否在矩形內。知道了如果響應單點觸控,這樣就可以完全實現飛機的拖拽了。
2.如何判斷子彈是否命中飛機?
我前面提到過定時器,每幀執行回調函數。可以把敵機存到一個數組裏,每次遍歷敵機數組,判斷子彈的點是否在敵機中。如果是的話,就表示命中,然後在數組中刪除敵機元素,在層中刪除敵機精靈。
對精靈執行 sp->removeFromParentAndCleanup(true);可以在層中消除自身。
到這裏應該可以寫出簡單的打飛機了。