helloworld讲解cocos2d-x的编程思路与要点
用helloworld講解cocos2d-x的編程思路與要點
本文以cocos2d-x的helloworld為例,講解cocos2d-x引擎的特點和要點,2.2為了展示新功能,把包括屏幕自適應在內的新特性相關代碼加入了helloworld工程代碼里,但是也增加新人的上手難度,我會避過不談,只說關鍵的幾句代碼,對于已經了解cocos2d-x架構的朋友,本文后面的內容對你毫無幫助,可以去關注我寫的《cocos2d-x提高篇》(不過此刻我或許還沒寫)。當然了,不可能一開始就把所有內容說清楚,剛上手的朋友要想做游戲抑或是寫例子,首先要理解下兩個要點。
要點一:
關于繪制。許多朋友在剛接觸cocos2d-x的時候,迫不及待去尋找繪制函數,問我有沒有類似drawimage()抑或是drawstring()這樣的函數。這里要多說幾句,cocos2d-x底層已經對繪制進行了簡單的封裝,我們并不需要顯性的去寫drawImage()這樣的函數。
對于這點該如何理解呢?譬如說我們要做一個游戲,往往會把邏輯層和繪制層分開。在邏輯層,可能有很多關卡,但是當前玩家只有一個關卡,關卡里有地圖也有許多敵兵。我們在邏輯層進行邏輯運算,更改敵兵的狀態和坐標。之后再在繪制層把屏幕里的敵兵以及地圖的可見區域畫到屏幕上。這就是經典的(邏輯-繪制-邏輯-繪制)結構。
而對于剛上手的朋友可以這么理解,cocos2d-x已經把繪制的代碼寫好了,我們只需要在邏輯層設置。在cocos2d-x里,有scene(場景)的概念,當前scene只有一個,scene里有很多ccsprite(精靈),一個ccsprte有x,y,引用圖片這些屬性,Cocos2d-x會不斷的把當前scene里的可見精靈繪制到屏幕上。因此,如果我們要在cocos2d-x里繪制一張背景圖,首先創建一個scene,然后創建一個精靈,以某種形式將其添加到scene里,設置好引用的圖片和精靈位置,最后記得將scene設置當前場景就ok了。下面看代碼。
先看main.cpp
#include"main.h"
#include"../Classes/AppDelegate.h"
#include"CCEGLView.h"
USING_NS_CC;
intAPIENTRY_tWinMain(HINSTANCEhInstance,
HINSTANCEhPrevInstance,
LPTSTRlpCmdLine,
intnCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
AppDelegateapp;
CCEGLView*eglView=CCEGLView::sharedOpenGLView();
eglView->setViewName("HelloCpp");
eglView->setFrameSize(2048,1536);
eglView->setFrameZoomFactor(0.4f);
returnCCApplication::sharedApplication()->run();
}
這里我只要記住eglview->setFrameSize(w,h)函數作用是設置視圖大小
vs里的main.cpp文件是win32平臺下程序的入口,這里會創建一個窗口,大小為(2048,1536)
setFrameZoomFactor(0.4f)是將窗口縮小為所設大小的0.4倍,為了照顧我這樣的小屏幕用戶,不過這樣還是很蛋疼
所以我把窗口設置為480X320,屏蔽了縮放窗口那句話
再看AppDelegate.h
#ifndef_APP_DELEGATE_H_
#define_APP_DELEGATE_H_
#include"cocos2d.h"
classAppDelegate:privatecocos2d::CCApplication
{
public:
AppDelegate();
virtual~AppDelegate();
virtualboolapplicationDidFinishLaunching();
virtualvoidapplicationDidEnterBackground();
virtualvoidapplicationWillEnterForeground();
};
#endif//_APP_DELEGATE_H_
AppDelegate是程序的控制類,里面有三個函數(applicationDidFinishLaunching,applicationDidEnterBackground,applicationWillEnterForeground)
分別在程序啟動后,程序掛起進入后臺,程序從后臺返回時執行,定義需要我們自己實現。
看看AppDelegate.cpp里我們只看applicationDidFinishLaunching()是如何實現的
boolAppDelegate::applicationDidFinishLaunching(){
CCDirector*pDirector=CCDirector::sharedDirector();
CCEGLView*pEGLView=CCEGLView::sharedOpenGLView();
pDirector->setOpenGLView(pEGLView);
CCSizeframeSize=pEGLView->getFrameSize();
#if(CC_TARGET_PLATFORM==CC_PLATFORM_WINRT)||(CC_TARGET_PLATFORM==CC_PLATFORM_WP8)
pEGLView->setDesignResolutionSize(designResolutionSize.width,designResolutionSize.height,kResolutionShowAll);
#else
pEGLView->setDesignResolutionSize(designResolutionSize.width,designResolutionSize.height,kResolutionNoBorder);
#endif
vector<string>searchPath;
if(frameSize.height>mediumResource.size.height)
{
searchPath.push_back(largeResource.directory);
pDirector->setContentScaleFactor(MIN(largeResource.size.height/designResolutionSize.height,largeResource.size.width/designResolutionSize.width));
}
elseif(frameSize.height>smallResource.size.height)
{
searchPath.push_back(mediumResource.directory);
pDirector->setContentScaleFactor(MIN(mediumResource.size.height/designResolutionSize.height,mediumResource.size.width/designResolutionSize.width));
}
else
{
searchPath.push_back(smallResource.directory);
pDirector->setContentScaleFactor(MIN(smallResource.size.height/designResolutionSize.height,smallResource.size.width/designResolutionSize.width));
}
CCFileUtils::sharedFileUtils()->setSearchPaths(searchPath);
pDirector->setDisplayStats(true);
pDirector->setAnimationInterval(1.0/60);
CCScene*pScene=HelloWorld::scene();
pDirector->runWithScene(pScene);
returntrue;
}
前面說過cocos2d-x2.2加入了很多新特性相關的代碼在helloworld例子里,這里面的一大段都是和屏幕自適應相關的,因此可以跳過。
關鍵代碼為
開頭的
CCDirector*pDirector=CCDirector::sharedDirector();
CCDirector是游戲管理類,他的實例全局一份,通過CCDirector::sharedDirector()這個靜態函數取得,通過它可以取得一些游戲設置信息,也可以通過它控制當前運行哪個場景。
再看函數結尾部分
pDirector->setDisplayStats(true);
這句代碼設置在屏幕上顯示游戲運行的幀數,如果掉幀會立即看見
當前幀數60幀,每次渲染次數為3
pDirector->setAnimationInterval(1.0/60);
這句設置游戲每幀間隔1/60秒
CCScene*pScene=HelloWorld::scene();
pDirector->runWithScene(pScene);
最后2句話是調用HelloWorld::scene函數創建一個ccscene,然后運行這個場景(前面我說過的),之后scene里的精靈就會被繪制到屏幕上了。
第一次運行場景調用runWithScene(pScene),如果是從一個場景切換到另一個場景則要調用replaceScene(pScene);
我們的ccscene是調用HelloWorld::scene這個靜態函數創建出來的,這個場景里有一張背景圖一個文字label以及一個按鈕
在看HelloWorldScene類之前,我們先普及另外的要點知識
要點2:
1.layer:在scene和sprite之間還有一個layer(層)的概念。一般來說,我們要創建一個或多個層,把層添加場景里,再把精靈放進層里。有了用層管理精靈
的概念后,我們在做游戲的時候就可以把一個場景分成很多層,譬如說地圖層和對象層,還可以有特效層(子彈層)和ui層。層和層之間可以設置遮擋關系
,先繪制哪一層后繪制哪一層,也可以通過設置某一層的顯示或不顯示來屏蔽掉一層。層和層之間也可以是包含關系(父子關系),譬如說一個地圖層,我們可
以分成四個小層,這四個小層分別被添加了春夏秋冬四種風格的精靈構成四種景色的地圖。然后根據需要只讓其中一層顯示,就實現了一個場景里季節變換的效果。
同時由于layer這個類繼承了cocos2d-x的觸摸接口類,我們可以實現某一層的手指觸摸行為。
2.node結構:CCNode(節點)是cocos2d-x里的一個比較底層的基類,我們常用的CCScene,CCLayer,CCSprite類都繼承了CCNode。CCNode具有容器功能
(有個children成員,是個隊列),每個ccnode都可以包含別的CCNode,通過addchild(m_other_node)函數把別的節點加為自己的子節點,這種關系成為父子關系。
上級的node稱為parent(父親),下級的node成為child。正是由于ccnode的這種節點特性,我們才可以把layer加入scene,把sprite加入layer。形成一個樹形結構。
3.內存管理(引用計數器和代碼風格):這一段如看不懂,直接跳過。cocos2d-x的retain和release機制(引用計數器機制)引自objective-c。在java和c#這些語言里,當一個對象不被任何人引用時,所占
內存會自動釋放,在c++里程序員new出來的對象,需要自己delete。而在cocos2d-x(objective-c)里。我們創建出來的大多數對象都繼承CCObject這個類,它有一個計數器。一般來說
new出來的對象,計數器為1。如果我們對其使用retain,計數器就會加1.如果我們對其使用release,計數器就會-1,當計數器為0時,對象就會被析構。和reatinrelease并行的還有一個auotorelease
的概念,如果我們將一個ccobject設為autorelease,它被被加到一個池里,當它計數器為1時它也會在某個時間段被自動釋放。那么如果我們得到一個ccobject,我們怎么知道它的計數器為幾,又是不是
auotorelease的呢?這里有個語法規范,只要不是通過new出來的對象,而是通過其他接口得到的(譬如helloworld::scene()),它就應該是autorelease的,并且對我們來說它的計數器為1.應為就算它
被外部retain過,它也必然會在某個時間被外部release(成對出現,保證內存不泄露),因此我們如果只有還需要使用這個對象,并且不能保證在這之前,外部都不將其release,我們就應該手動將其retain
并且在不用的時候將其release。譬如說CCDirector在切換到某個場景時,就會將其retain,確保其不會被釋放,在下次切換時,將其release,這樣就不會導致該場景永遠不被釋放(release只是表示我們不需要
這個對象了,不代表此時該對象就會被釋放,因為別處可能對其retain了,還需要這個對象)。
基于這種前提,我們一般寫的類,都不會把構造函數暴露,讓別處直接newClassX(),而是封裝一個靜態函數create()確保別人能得到一個auotorelease的對它來說計數器為1的實例對象。
在create函數里,我們new出一個對象,執行初始化函數init(),并將其設為autorelease,將其返回。舊版本的cocos2d-x為了和cocos2d(objective-c版本)接口一致,采用的是類名()寫法。
例如CCNode::Node()這樣的寫法,已經被新版拋棄,因為不適合c++。
下面看HelloWorldScene代碼,先看頭文件
#ifndef__HELLOWORLD_SCENE_H__
#define__HELLOWORLD_SCENE_H__
#include"cocos2d.h"
classHelloWorld:publiccocos2d::CCLayer
{
public:
virtualboolinit();
staticcocos2d::CCScene*scene();
voidmenuCloseCallback(CCObject*pSender);
CREATE_FUNC(HelloWorld);
};
#endif//__HELLOWORLD_SCENE_H__
HelloWorld是一個繼承了CCLayer的類,里面有個init函數是初始化函數,有個靜態函數scene會返回一個scene,這個scene里被添加了一個cclayer。
我們說了HelloWorldlayer里有一張北京,一個文字label,一個按鈕,menuCloseCallback就是點擊按鈕后的回調函數。
create函數跑哪去了?CREATE_FUNC(HelloWorld),這是一個宏,由于大多數的類都有create靜態函數,內容都是new一個對象指針,將其init(),設為auotorelease再返回,寫起來很煩
代碼也會變長,所以cocos2d-x給我們寫好了不少這樣的宏。翻譯過來就是
staticHelloWorld*create()
{
HelloWorld*pRet=newHelloWorld();
if(pRet&&pRet->init())
{
pRet->autorelease();
returnpRet;
}
else
{
deletepRet;
pRet=NULL;
returnNULL;
}
}
節省了不少行代碼,不過我一般懶得用宏,這么幾行代碼,其實順手就寫掉了。
由此可見,主要初始化內容都是寫在init()函數里。那么去看cpp文件吧
#include"HelloWorldScene.h"
#include"AppMacros.h"
USING_NS_CC;
CCScene*HelloWorld::scene()
{
CCScene*scene=CCScene::create();
HelloWorld*layer=HelloWorld::create();
scene->addChild(layer);
returnscene;
}
boolHelloWorld::init()
{
if(!CCLayer::init())
{
returnfalse;
}
CCSizevisibleSize=CCDirector::sharedDirector()->getVisibleSize();
CCPointorigin=CCDirector::sharedDirector()->getVisibleOrigin();
CCMenuItemImage*pCloseItem=CCMenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
this,
menu_selector(HelloWorld::menuCloseCallback));
pCloseItem->setPosition(ccp(origin.x+visibleSize.width-pCloseItem->getContentSize().width/2,
origin.y+pCloseItem->getContentSize().height/2));
CCMenu*pMenu=CCMenu::create(pCloseItem,NULL);
pMenu->setPosition(CCPointZero);
this->addChild(pMenu,1);
CCLabelTTF*pLabel=CCLabelTTF::create("HelloWorld","Arial",TITLE_FONT_SIZE);
pLabel->setPosition(ccp(origin.x+visibleSize.width/2,
origin.y+visibleSize.height-pLabel->getContentSize().height));
this->addChild(pLabel,1);
CCSprite*pSprite=CCSprite::create("HelloWorld.png");
pSprite->setPosition(ccp(visibleSize.width/2+origin.x,visibleSize.height/2+origin.y));
this->addChild(pSprite,0);
returntrue;
}
voidHelloWorld::menuCloseCallback(CCObject*pSender)
{
#if(CC_TARGET_PLATFORM==CC_PLATFORM_WINRT)||(CC_TARGET_PLATFORM==CC_PLATFORM_WP8)
CCMessageBox("Youpressedtheclosebutton.WindowsStoreAppsdonotimplementaclosebutton.","Alert");
#else
CCDirector::sharedDirector()->end();
#if(CC_TARGET_PLATFORM==CC_PLATFORM_IOS)
exit(0);
#endif
#endif
}
靜態函數scene()返回一個scene,里面包含了一個HelloWorld(這是一個層)。
在創建HelloWorld時,是調用create函數得到的,create()函數調用了init()函數,下面看init()函數
首先執行父類的初始化函數,然后拿到屏幕大小
CCSizevisibleSize=CCDirector::sharedDirector()->getVisibleSize();
CCPointorigin=CCDirector::sharedDirector()->getVisibleOrigin();
舊版本的例子是直接拿的getwinSize()。這里說明一下,2.2版本給大家展示了屏幕自適應,也就是我們之前在appdelegate里沒有說明的那一段,由于設置了屏幕自適應的關系,
雖然我們在main函數里設置了視圖大小,但是并不是說整個視圖都能繪制到屏幕上,可能只有中間部分繪制出來,多出去的部分就看不到了,getVisibleSize()函數就是拿到可繪制
出的部分的大小,getVisibleOrigin()則是可繪制部分左下角點相對于視圖左下角點的坐標(關于自適應我之后的博文會仔細說)。
hellowWorld的例子是將視圖等比放大到無黑邊,這樣就可能導致上下抑或左右有部分顯示不出來,而拿到可顯示部分的起始坐標和長寬就可以把精靈定位到屏幕的
左下(origin.x,origin.x+visibleSize.height/2),
右下(origin.x+visibleSize.width,origin.x+visibleSize.height/2),
抑或是中間(origin.x+visibleSize.width/2,origin.x+visibleSize.height/2)
CCMenuItemImage*pCloseItem=CCMenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
this,
menu_selector(HelloWorld::menuCloseCallback));
pCloseItem->setPosition(ccp(origin.x+visibleSize.width-pCloseItem->getContentSize().width/2,
origin.y+pCloseItem->getContentSize().height/2));
CCMenu*pMenu=CCMenu::create(pCloseItem,NULL);
pMenu->setPosition(CCPointZero);
this->addChild(pMenu,1);
CCMenuItemImage這個類菜單原件的一種(是個由2張圖片精靈組成的按鈕),它的create函數有四個參數,前個參數是圖片名,分別是點擊前要顯示的圖片,可以點擊時
顯示的圖片。之后的2個參數分別是點擊后要執行回調的對象和回調函數的指針,menu_selector(HelloWorld::menuCloseCallback)這種寫法是模仿objective-c里的selector,
menu_selector是個宏,內容其實就是把函數指針強轉為CCObject的函數指針,如果看不懂也沒關系,照著寫就行了。
pCloseItem->setPosition(ccp(origin.x+visibleSize.width-pCloseItem->getContentSize().width/2,origin.y+pCloseItem->getContentSize().height/2));
setPosition(x,y)函數是設置Node的坐標
而getContentSize()會返回Node的大小
這里把pCloseItem放在了屏幕右下角
但是由于CCMenuItemImage沒有實現cocos2d-x觸摸接口,因此要把它塞進一個CCMenu里,才能點擊。
CCMenu是菜單容器類,在cocos2d-x2.2里它繼承了layer(之前說過layer繼承了觸摸接口),因此具有觸摸行為,相當于一個菜單層。把菜單原件塞進CCMenu里,再把CCMenu
塞進layer里,就可以看到按鈕并且可以點擊了。
pMenu->setPosition(CCPointZero);
this->addChild(pMenu,1);
pMenu的坐標相對于layer是左下角點(0,0),而按鈕相對于pMenu相對于pMenu是屏幕右下的位置,因此pMenu相對于layer也是屏幕右下的位置。
注意,this->addChild(pMenu,1);這里addchild函數里多傳了一個數字,這個參數是Z值。一個layer里有很多精力,繪制時的遮擋關系是怎樣的呢。答案是Z值大的會確保被繪制在
z值小的上面。這里傳的給pMenu傳的Z值是1,之后比1小的都會在pMenu下面。如果沒傳Z值,默認是0。
menuCloseCallback是點擊按鈕的回調函數,里面根據CC_TARGET_PLATFORM預定義宏的值得不同,寫了不同的處理代碼。
CC_TARGET_PLATFORM宏是平臺標示宏,因為我們的平臺是win32,因此,我們會被編譯的代碼只是一句
CCDirector::sharedDirector()->end();這句函數的意思是關閉程序。
CCSprite是一個關聯圖片的類,當我們想添加一個圖片對象時,就會創建CCSprite,那么對于文字我們使用CCLabelTTF類。
CCLabelTTF*pLabel=CCLabelTTF::create("HelloWorld","Arial",TITLE_FONT_SIZE);
pLabel->setPosition(ccp(origin.x+visibleSize.width/2,origin.y+visibleSize.height-pLabel->getContentSize().height));
this->addChild(pLabel,1);
CCLabelTTF類的創建函數傳入三個參數,分別是文字內容,字體,和大小。
創建出來的pLabel在屏幕中間偏上部分。
設置的Z值也為1,但是由于是后添加的,所以在菜單層之上。
CCSprite*pSprite=CCSprite::create("HelloWorld.png");
pSprite->setPosition(ccp(visibleSize.width/2+origin.x,visibleSize.height/2+origin.y));
this->addChild(pSprite,0);
最后,我們創建一個背景,并添加到場景里。
CCSprite的創建函數只要傳入一個圖片名即可(事實上,其實上有三個重載,但是暫時用不到)。
圖片被放在屏幕正中央,有傳入的Z值為0,所以會被繪制在最下面。
關于HelloWorld代碼本身的解析就到此為止。
ps:注意我們創建的CCSprite由于是create接口得到到,并不是直接new出來的,那么我們應該把它看做(事實上也是)計數器為1且autorelease,那么它不會被銷毀么。
事實上,由于它被加入到了layer里,而CCNode在添加一個child時會自動把child的計數器加1,移除時-1.因此不需要我們手動retain了。等切換場景導致當前場景銷毀時,所有
子節點會被移除,子節點也會被銷毀。
或許有許多朋友會說我這篇博文說的比較粗糙,許多相關點沒有詳細說。這里我要說明一下,我不打算像翻譯工一樣把每一句代碼的意思給翻譯一遍,這篇博文的
目的只是帶朋友們熟悉一下cocos2d-x引擎的代碼風格和框架,因此我對計數器以及node結構反而多說了幾句。helloworld是比較簡單的例子,但是在cocos2d-x2.2的版本里,由于開發人員想展示許多新特性,新的函數接口,加了許多相對新手來說比較難以理解的代碼,加大了閱讀難度,不適合在一開始就拿來說。既然寫教程,就要能夠幫助新人迅速上手,減少難度,一點一點把知識點傳輸給讀者。看了這篇文章的讀者如果知道了如何創建一個scene以及創建一個精靈,繪制到屏幕上,就算過關了。
之后博客會有2個分支
1:我一直堅信程序員只要能貼圖就能做游戲,之后我會寫幾個簡單的游戲例子,由簡到難分好幾步。最初的代碼甚至不考慮屏幕分辨率也不適用動畫,然后慢慢實現這些功能,每次加入幾個cocos2d-x常用類抑或是機制的適用和說明。最初可能是簡單的打氣球之類的小游戲,最后是塔防抑或是rpg這種稍微大點的項目。跟著這條線走的朋友會學的比較輕松,唯一的敵人可能就是我博客的更新速度。
2:cocos2d-x自帶的testcpp的例子其實很經典,許多朋友卻懶得看,我打算一一進行講解,到時候我的博客就有類似工具書的作用了。已經入門的朋友到時可以跟這條線。不過我不知道說明時候能更新就是了。
大家在學習cocos2d-x的時候一定要按著引擎的框架走,按cocos2d-x的風格寫代碼,嚴格遵守retainrelease機制,就不會出現代碼莫名其妙編譯不過去的情況了。最后祝大家晚安,下一篇博文開始要實際做游戲了,養好精神吧。
轉載于:https://www.cnblogs.com/MiniHouse/p/3960062.html
總結
以上是生活随笔為你收集整理的helloworld讲解cocos2d-x的编程思路与要点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript对象的创建之构造函数
- 下一篇: 泛型Dictionary的用法详解