在用Cocos2DX引擎開發遊戲的過程當中,咱們常常須要彈出一個對話框或者提示框,通知玩家一些必要的信息。這時候咱們就須要考慮怎樣設計和封裝一個這樣的彈出對話框。首先,這樣的彈出框通常都是「模態窗口」,即在沒有對當前彈出的對話框進行確認的時候,不能繼續往下操做。html
一個對話框通常包含幾個部分:背景圖、兩個按鈕(個數可定製)、標題、文字內容。咱們須要使對話框爲模態窗口,並設置彈出效果。下面來講說建立彈出對話框的要點:node
一、彈出層的觸摸優先級,操做與顯示相一致函數
由於彈出對話框是「模態窗口」,因此咱們須要設置觸摸優先級來保證對話框是模態的。測試
在這裏咱們以CCMenu做爲對話框按鈕的操做實現,咱們知道CCMenu 的默認觸摸級別是-128,那麼咱們的【彈出層】觸摸級別該設置爲多少?this
在咱們設定觸摸級別時,請記住一句話,操做與顯示相一致。此話何意?如今設想這樣一種狀況,咱們爲了屏蔽彈出層之外全部的層的操做,而將彈出層級別置爲 -129 ,這樣便保證了它是模態的了(通常而言,除了 CCMenu 的級別,用戶自定義也無需這麼大),可是若是當前彈出層上方還有其它的層(也能夠是彈出層的父節點上方有其它層,如多層的 UI 設計),而且其上也有 CCMenu 按鈕,那麼就出現了顯示在 彈出層上放層中的 CCMenu 按鈕不能點擊,這不科學!實際,在彈出層的 CCMenu 也不能點擊,只是咱們能夠經過將彈出層的觸摸消息傳遞給層中的 CCMenu 解決。因此設置爲一味的追求最大的觸摸優先級,並不可取,它不能很好的作到 操做與顯示相一致,而優先級小於 CCMenu 並不能屏蔽下方的其它 CCMenu 操做,因此對於彈出層自己,它設置爲 -128 是合理的,對於一樣級別的這些元素來講(彈出層和CCMenu),可以作到,顯示在最上方的優先處理。若是在彈出層上方還有層,是能夠點擊的,爲何不呢!而咱們要作的是,經過邏輯控制,讓彈出層節點,最後添加到場景的最上方。url
因此,在這裏層自己的觸摸優先級別設置爲 -128 ,而彈出層中的按鈕是 CCMenu,都是同級,操做與顯示相一致,惟一要注意的是邏輯的控制,何時彈出層,由哪一個節點彈出層(通常由場景基層來負責),以保證它顯示在最上方。spa
二、定製按鈕個數.net
咱們能夠定製按鈕的個數。在封裝的時候添加addButton方法,用來在當前對話框中動態地添加一個或幾個按鈕,而添加幾個?固然由你來決定。但肯定的是,它們的位置會隨着按鈕個數的不一樣而不一樣,若是一個按鈕,將居中顯示(一分爲二),若是兩個(三等份距離),若是三個(四等份距離),以此類推。這裏addButton()的主要做用是建立一個menuItem添加到Menu中。而後在onEnter中(此時按鈕個數已經肯定)計算並設置各按鈕的位置,並添加至界面之中。設計
三、窗口的大小可變code
彈出對話框的窗口大小要是可變的,經過 getContentSize 來獲取。
若是沒有設置 ContentSize ,那麼採起的方案是,窗口大小與傳入圖片同樣大。
若是設置了ContentSize,則將窗口設定爲指定大小。這時候須要將背景圖片進行縮放,如何縮放? 【答案】是利用9宮格圖CCScale9Sprite縮放圖片,縮放帶圓角的圖片。原理以下:
九宮格圖是經過1個CCSpriteBatchNode和9個CCSprite來實現的,原理很簡單,經過將原紋理資源切割成9部分(PS: 這也是叫9宮格的緣由),保持4個角不變形,根據想要的尺寸來進行拉伸或壓縮。
使用方法:CCScale9Sprite* background = CCScale9Sprite::create("back.png");
background->setContentSize(CCSizeMake(400,200)); //400x200是要生成的尺寸
background->setPosition(Center);
this->addChild(background);
四、回調函數的實現方案
對於回調通常有兩種方式,一種是 delegate 回調,這須要定義接口,由上層,繼承實現接口,並把本身看成參數,傳入彈出層,由彈出層調用 delegate 的接口方法實現,在 Cocos2d-x 裏面不少地方用到此方式。另外一種則是函數綁定,就像 CCMenu 那樣的綁定函數。
在這裏設計的是按鈕個數可變,功能也不盡相同,因此咱們選擇綁定函數!進一步封裝,將彈出層的按鈕回調函數設置爲內部實現,而後在 回調給它的上層,以後關閉對話框(關閉的操做由對話框層來完成)。回調給它的上層的時候傳遞CCNode參數,以其 tag 標示,點擊的是哪一個按鈕。【用void setCallbackFunc(CCObject* target, SEL_CallFuncN callfun); 做爲外部接口,設置上層對象和上層的回調函數。】
五、onEnter動態建立彈出層
根據設置的屬性去建立層 有兩種方式:
其一,實時設置,實時刷新。好比在 static PopupLayer* create(const char* gackgroundImage) 的實現裏面,建立一個精靈,並設置好圖片,添加到當前層,若是調用了 setContentSize 咱們再在此方法獲取精靈後去修改這個精靈的大小
其二,保留屬性,動態組建。也就是說前面一些封裝的函數好比setTitle()、setContentText(),addButton()、setCallbackFunc()只用於設置屬性參數(即給變量賦值)。參數設置好之後,在一個適當的執行時期,根據以上參數,動態建立符合需求的精靈/層,而這個操做在 onEnter 裏尤其合適。
在這裏咱們使用動態組建的方式,即在前面用一些變量來保存對話框所需的信息,而後在 onEnter 中,讀取這些信息,以實時添加至界面之中。
封裝 //PopupLayer.h #include "cocos2d.h" #include "cocos-ext.h" USING_NS_CC; USING_NS_CC_EXT; class PopupLayer : public CCLayer{ public: PopupLayer(); ~PopupLayer(); virtual bool init(); //須要重寫觸摸註冊函數,從新給定觸摸級別 virtual void registerWithTouchDispatcher(); //重寫觸摸函數,返回true,屏蔽其它層,達到「模態」效果 bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent); //靜態建立函數,建立一個彈出層,設置背景圖片 static PopupLayer* create(const char* backgroundImage); //設置標題 void setTitle(const char* title, int fontsize = 20); //設置文本內容,padding 爲文字到對話框兩邊預留的距離,這是可控的,距上方的距離亦是如此 void setContentText(const char* text, int fontsize=20, int padding=50, int paddingTop=100); //設置上層對象和上層回調函數,用於回調時傳遞CCNode參數 void setCallBackFunc(CCObject* target, SEL_CallFuncN callfun); //添加menuItem按鈕,封裝了一個函數,傳入些必要的參數 bool addButton(const char* normalImage, const char* selectedImage, const char* title, int tag=0); //爲了在顯示層時的屬性生效,選擇在onEnter裏動態生成 virtual void onEnter(); virtual void onExit(); CREATE_FUNC(PopupLayer); private: void buttonCallBack(CCObject* pSender); //文字內容兩邊的空白區域 int m_contentPadding; int m_contentPaddingTop; CCObject* m_callbackListener; SEL_CallFuncN m_callback; //定義了CCMenu*類型變量m_pMenu, 而且直接定義默認的set/get方法 CC_SYNTHESIZE_RETAIN(CCMenu*, m_pMenu, MenuButton); CC_SYNTHESIZE_RETAIN(CCSprite*, m_sfBackGround, SpriteBackGround); CC_SYNTHESIZE_RETAIN(CCScale9Sprite*, m_s9BackGround, Sprite9BackGround); CC_SYNTHESIZE_RETAIN(CCLabelTTF*, m_ltTitle, LabelTitle); CC_SYNTHESIZE_RETAIN(CCLabelTTF*, m_ltContentText, LabelContentText); }; //PopupLayer.cpp #include "PopupLayer.h" USING_NS_CC; // 構造函數中變量設初值 PopupLayer::PopupLayer() { m_contentPadding = 0; m_contentPaddingTop = 0; m_pMenu = NULL; m_callbackListener = NULL; m_callback = NULL; m_sfBackGround = NULL; m_s9BackGround = NULL; m_ltContentText = NULL; m_ltTitle = NULL; } //釋放 PopupLayer::~PopupLayer() { CC_SAFE_RELEASE(m_pMenu); CC_SAFE_RELEASE(m_sfBackGround); CC_SAFE_RELEASE(m_s9BackGround); CC_SAFE_RELEASE(m_ltContentText); CC_SAFE_RELEASE(m_ltTitle); } //初始化 bool PopupLayer::init() { if ( !CCLayer::init() ){ return false; } this->setContentSize(CCSizeZero); //初始化須要的Menu CCMenu* menu = CCMenu::create(); menu->setPosition(CCPointZero); setMenuButton(menu); //set()方法 setTouchEnabled(true); //開啓觸摸響應 return true; } //重寫觸摸註冊函數,從新給定觸摸級別 void PopupLayer::registerWithTouchDispatcher(){ // 這裏的觸摸優先級設置爲-128,與CCMenu同級,保證了屏蔽下方的觸摸 CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, -128, true); } //觸摸函數ccTouchBegan,返回true bool PopupLayer::ccTouchBegan( CCTouch *pTouch, CCEvent *pEvent ){ return true; } //建立一個彈出層,給背景精靈變量賦值 PopupLayer* PopupLayer::create( const char* backgroundImage ){ PopupLayer* popup = PopupLayer::create(); popup->setSpriteBackGround(CCSprite::create(backgroundImage)); popup->setSprite9BackGround(CCScale9Sprite::create(backgroundImage)); return popup; } //給標題變量賦值 void PopupLayer::setTitle( const char* title, int fontsize ){ CCLabelTTF* ltfTitle = CCLabelTTF::create(title, "Arial", fontsize); ltfTitle->setColor(ccc3(0, 0, 0)); setLabelTitle(ltfTitle); } //給文本變量賦值 void PopupLayer::setContentText( const char* text, int fontsize, int padding, int paddingTop ){ CCLabelTTF* content = CCLabelTTF::create(text, "Arial", fontsize); content->setColor(ccc3(0, 0, 0)); setLabelContentText(content); m_contentPadding = padding; m_contentPaddingTop = paddingTop; } //給下層層變量和回調函數變量賦值 void PopupLayer::setCallBackFunc( CCObject* target, SEL_CallFuncN callfun ){ m_callbackListener = target; m_callback = callfun; } //給menu菜單變量添加Item bool PopupLayer::addButton( const char* normalImage, const char* selectedImage, const char* title, int tag ){ CCSize winSize = CCDirector::sharedDirector()->getWinSize(); CCPoint center = ccp(winSize.width/2, winSize.height/2); // 建立圖片菜單按鈕 CCMenuItemImage* menuImage = CCMenuItemImage::create( normalImage, selectedImage, this, menu_selector(PopupLayer::buttonCallBack)); menuImage->setTag(tag); menuImage->setPosition(center); // 添加文字說明並設置位置 CCSize menuSize = menuImage->getContentSize(); CCLabelTTF* ttf = CCLabelTTF::create(title, "Arial", 15); ttf->setColor(ccc3(0, 0, 0)); ttf->setPosition(ccp(menuSize.width/2, menuSize.height/2)); menuImage->addChild(ttf); getMenuButton()->addChild(menuImage); return true; } //銷燬彈出框,傳遞參數node給下層 void PopupLayer::buttonCallBack( CCObject* pSender ){ CCNode* node = dynamic_cast(pSender); CCLog("touch tag: %d", node->getTag()); if (m_callback && m_callbackListener) { //執行HelloWorld層的回調函數,傳遞node參數 (m_callbackListener->*m_callback)(node); } this->removeFromParentAndCleanup(true); } //所有參數都設定好後,在運行時動態加載 void PopupLayer::onEnter(){ CCLayer::onEnter(); CCSize winSize = CCDirector::sharedDirector()->getWinSize(); CCPoint center = ccp(winSize.width/2, winSize.height/2); CCSize contentSize; // 設定好參數,在運行時加載 if (getContentSize().equals(CCSizeZero)){ getSpriteBackGround()->setPosition(center); this->addChild(getSpriteBackGround(),0,0); contentSize = getSpriteBackGround()->getTexture()->getContentSize(); } else{ CCScale9Sprite* background = getSprite9BackGround(); background->setContentSize(getContentSize()); background->setPosition(center); this->addChild(background, 0); contentSize = getContentSize(); } //添加按鈕,並根據Item的個數設置其位置 this->addChild(getMenuButton()); float btnWidth = contentSize.width / (getMenuButton()->getChildrenCount()+1); CCArray* array = getMenuButton()->getChildren(); CCObject* pObj = NULL; int i = 0; CCARRAY_FOREACH(array, pObj){ CCNode* node = dynamic_cast(pObj); node->setPosition(ccp(winSize.width/2 - contentSize.width/2 + btnWidth*(i+1), winSize.height/2 - contentSize.height/3)); i++; } // 顯示對話框標題 if (getLabelTitle()){ getLabelTitle()->setPosition(ccpAdd(center, ccp(0, contentSize.height/2 - 25.0f))); this->addChild(getLabelTitle()); } //顯示文本內容 if (getLabelContentText()){ CCLabelTTF* ltf = getLabelContentText(); ltf->setPosition(center); ltf->setDimensions(CCSizeMake(contentSize.width - m_contentPadding*2, contentSize.height - m_contentPaddingTop)); ltf->setHorizontalAlignment(kCCTextAlignmentLeft); this->addChild(ltf); } //彈出效果 CCSequence *popupActions = CCSequence::create( CCScaleTo::create(0.0, 0.0), CCScaleTo::create(0.06, 1.05), CCScaleTo::create(0.08, 0.95), CCScaleTo::create(0.08, 1.0), NULL); this->runAction(popupActions); } //退出 void PopupLayer::onExit(){ CCLayer::onExit(); } 彈出層調用: // 定義一個彈出層,傳入一張背景圖片 PopupLayer* popup = PopupLayer::create("popupBackGround.png"); // ContentSize是可選的設置,能夠不設置,若是設置則把它當作9圖縮放 //popup->setContentSize(CCSizeMake(400, 360)); popup->setTitle("Message"); popup->setContentText("Most people... blunder round this city.", 20, 50, 150); // 設置回調函數,回調傳回一個CCNode以獲取tag判斷點擊的按鈕 // 這只是做爲一種封裝實現,若是使用delegate那就可以更靈活的控制參數了 popup->setCallBackFunc(this, callfuncN_selector(HelloWorld::buttonCallBack)); //添加按鈕,設置圖片、文字,tag信息 popup->addButton("button.png", "button.png", "Ok", 0); popup->addButton("button.png", "button.png", "Cancel", 1); this->addChild(popup);
測試截圖:
這樣,對話框的基本模型就完成了,它實現瞭如下功能:
一個能夠彈出的對話框實現
模態窗口的實現(須要邏輯的控制)
多按鈕的支持,位置自適應,提供回調函數
提供標題和內容設置
支持 九圖,控制適應彈出框大小
參考文:http://blog.csdn.net/u012421525/article/details/14231205