Cocos2D-X彈出對話框的實現與封裝

 在用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個角不變形,根據想要的尺寸來進行拉伸或壓縮。

Cocos2D-X彈出對話框的實現與封裝

           使用方法: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);

測試截圖:

Cocos2D-X彈出對話框的實現與封裝




這樣,對話框的基本模型就完成了,它實現瞭如下功能:

  • 一個能夠彈出的對話框實現

  • 模態窗口的實現(須要邏輯的控制)

  • 多按鈕的支持,位置自適應,提供回調函數

  • 提供標題和內容設置

  • 支持 九圖,控制適應彈出框大小


參考文:http://blog.csdn.net/u012421525/article/details/14231205

相關文章
相關標籤/搜索