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


在用Cocos2DX引擎開發遊戲的過程中,我們經常需要彈出一個對話框或者提示框,通知玩家一些必要的信息。這時候我們就需要考慮怎樣設計和封裝一個這樣的彈出對話框。首先,這樣的彈出框一般都是“模態窗口”,即在沒有對當前彈出的對話框進行確認的時候,不能繼續往下操作。


  一個對話框一般包含幾個部分:背景圖、兩個按鈕(個數可定製)、標題、文字內容。我們需要使對話框爲模態窗口,並設置彈出效果。下面來說說創建彈出對話框的要點:

  1、彈出層的觸摸優先級,操作與顯示相一致

  因爲彈出對話框是“模態窗口”,所以我們需要設置觸摸優先級來保證對話框是模態的。

  在這裏我們以CCMenu作爲對話框按鈕的操作實現,我們知道CCMenu 的默認觸摸級別是-128,那麼我們的【彈出層】觸摸級別該設置爲多少?

  在我們設定觸摸級別時,請記住一句話,操作與顯示相一致。此話何意?現在設想這樣一種情況,我們爲了屏蔽彈出層以外所有的層的操作,而將彈出層級別置爲 -129 ,這樣便保證了它是模態的了(一般而言,除了 CCMenu 的級別,用戶自定義也無需這麼大),但是如果當前彈出層上方還有其它的層(也可以是彈出層的父節點上方有其它層,如多層的 UI 設計),並且其上也有 CCMenu 按鈕,那麼就出現了顯示在 彈出層上放層中的 CCMenu 按鈕不能點擊,這不科學!實際,在彈出層的 CCMenu 也不能點擊,只是我們可以通過將彈出層的觸摸消息傳遞給層中的 CCMenu 解決。所以設置爲一味的追求最大的觸摸優先級,並不可取,它不能很好的做到 操作與顯示相一致,而優先級小於 CCMenu 並不能屏蔽下方的其它 CCMenu 操作,所以對於彈出層本身,它設置爲 -128 是合理的,對於同樣級別的這些元素來說(彈出層和CCMenu),能夠做到,顯示在最上方的優先處理。如果在彈出層上方還有層,是可以點擊的,爲什麼不呢!而我們要做的是,通過邏輯控制,讓彈出層節點,最後添加到場景的最上方。

  所以,在這裏層本身的觸摸優先級別設置爲 -128 ,而彈出層中的按鈕是 CCMenu,都是同級,操作與顯示相一致,唯一要注意的是邏輯的控制,什麼時候彈出層,由哪個節點彈出層(一般由場景基層來負責),以保證它顯示在最上方。

  2、定製按鈕個數

  我們可以定製按鈕的個數。在封裝的時候添加addButton方法,用來在當前對話框中動態地添加一個或幾個按鈕,而添加幾個?當然由你來決定。但確定的是,它們的位置會隨着按鈕個數的不同而不同,如果一個按鈕,將居中顯示(一分爲二),如果兩個(三等份距離),如果三個(四等份距離),以此類推。這裏addButton()的主要作用是創建一個menuItem添加到Menu中。然後在onEnter中(此時按鈕個數已經確定)計算並設置各按鈕的位置,並添加至界面之中。

  3、窗口的大小可變


  彈出對話框的窗口大小要是可變的,通過 getContentSize 來獲取。

  •   如果沒有設置 ContentSize ,那麼採取的方案是,窗口大小與傳入圖片一樣大。
  •   如果設置了ContentSize,則將窗口設定爲指定大小。這時候需要將背景圖片進行縮放,如何縮放? 【答案】是利用9宮格圖CCScale9Sprite縮放圖片,縮放帶圓角的圖片。原理如下:

  九宮格圖是通過1個CCSpriteBatchNode和9個CCSprite來實現的,原理很簡單,通過將原紋理資源切割成9部分(PS: 這也是叫9宮格的原因),保持4個角不變形,根據想要的尺寸來進行拉伸或壓縮。

 

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

  使用方法:

[AppleScript] 純文本查看 複製代碼
?
1
2
3
4
5
6
7
     CCScale9Sprite* background = CCScale9Sprite::create(back.png”);
 
background->setContentSize(CCSizeMake(400,200)); //400x200是要生成的尺寸
 
background->setPosition(Center);
 
this->addChild(background);


  4、回調函數的實現方案

  對於回調一般有兩種方式,一種是 delegate 回調,這需要定義接口,由上層,繼承實現接口,並把自己當作參數,傳入彈出層,由彈出層調用 delegate 的接口方法實現,在 Cocos2d-x 裏面很多地方用到此方式。另一種則是函數綁定,就像 CCMenu 那樣的綁定函數。

  在這裏設計的是按鈕個數可變,功能也不盡相同,所以我們選擇綁定函數!進一步封裝,將彈出層的按鈕回調函數設置爲內部實現,然後在 回調給它的上層,之後關閉對話框(關閉的操作由對話框層來完成)。回調給它的上層的時候傳遞CCNode參數,以其 tag 標示,點擊的是哪個按鈕。【用void setCallbackFunc(CCObject* target, SEL_CallFuncN callfun); 作爲外部接口,設置上層對象和上層的回調函數。】

  5、onEnter動態創建彈出層

  根據設置的屬性去創建層 有兩種方式:

  其一,實時設置,實時刷新。比如在 static PopupLayer* create(const char* gackgroundImage) 的實現裏面,創建一個精靈,並設置好圖片,添加到當前層,如果調用了 setContentSize 我們再在此方法獲取精靈後去修改這個精靈的大小

  其二,保留屬性,動態組建。也就是說前面一些封裝的函數比如setTitle()、setContentText(),addButton()、setCallbackFunc()只用於設置屬性參數(即給變量賦值)。參數設置好以後,在一個適當的執行時期,根據以上參數,動態創建符合需求的精靈/層,而這個操作在 onEnter 裏尤爲合適。

  在這裏我們使用動態組建的方式,即在前面用一些變量來保存對話框所需的信息,然後在 onEnter 中,讀取這些信息,以實時添加至界面之中。

  封裝

[AppleScript] 純文本查看 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//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

[AppleScript] 純文本查看 複製代碼
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#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彈出對話框的實現與封裝

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

  •   一個可以彈出的對話框實現
  •   模態窗口的實現(需要邏輯的控制)
  •   多按鈕的支持,位置自適應,提供回調函數
  •   提供標題和內容設置
  •   支持 九圖,控制適應彈出框大小
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章