學習cocos2d-x中的菜單主要需要了解:菜單(CCMenu)和菜單項(CCMenuItem)以及CCMenuItem的具體子類。
a. 下面來學習一下相關的類。
1. CCMenu
菜單,是CCLayer的子類,是一個層(容器),可以往裡面添加菜單項。下面是它的類結構圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL0QDN381NxIDM2kTM1MTMvwFNw8CXxEjMxAjMvw1ckF2bsBXdvwFdl5mLuR2cj5Set1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
CCMenu預設接受觸屏事件的優先級是-128(優先級很高,因為值越小,響應觸屏事件的優先級越高),可以通過繼承它實作自定義的效果,建立CCMenu對象的函數:
static CCMenu* menuWithItems(CCMenuItem* item, ...);
static CCMenu* menuWithItem(CCMenuItem* item);
2. CCMenuItem
菜單項,開發中一般是直接使用它的子類。CCMenuItem有三個直接子類:
CCMenuItemLabel(字元标簽菜單)、CCMenuItemSprite(圖檔菜單)、CCMenuItemToggle(開關菜單)。
下面是CCMenuItem的類結構圖:
現在分别來了解一下各個不同的菜單項。
(1) CCMenuItemLabel:使用文字标簽建立菜單項
所有支援CCLabelProtocol的節點都可以用來建立CCMenuItemLabel,CCLabelProtocol是标簽的共同接口。CCLabelProtocol也有三個直接子類,下面是類結構圖:
CCLabelTTF:同時也是CCSprite的子類,用來渲染文字标簽的,可以指定字型,每次設定字元串内容時都需要重新建立紋理和渲染,性能不好,可以看它的相關源碼:
void CCLabelTTF::setString(const char *label)
{
if (m_pString)
{
delete m_pString;
m_pString = NULL;
}
m_pString = new std::string(label);
CCTexture2D *texture;
if( CCSize::CCSizeEqualToSize( m_tDimensions, CCSizeZero ) )
{
texture = new CCTexture2D();
texture->initWithString(label, m_pFontName->c_str(), m_fFontSize);
}
else
{
texture = new CCTexture2D();
texture->initWithString(label, m_tDimensions, m_eAlignment, m_pFontName->c_str(), m_fFontSize);
}
this->setTexture(texture);
texture->release();
CCRect rect = CCRectZero;
rect.size = m_pobTexture->getContentSize();
this->setTextureRect(rect);
}
可以用CCLabelBMFont或者CCLabelAtlas代替它。
CCLabelBMFont :也是CCSpriteBatchNode的子類,建立CCLabelBMFont對象需要一個字元串和一個fnt格式的檔案(字庫),如:
CCLabelBMFont *label = CCLabelBMFont::labelWithString("Bitmap Font Atlas", "fonts/bitmapFontTest.fnt");
這個fnt檔案包含了這些資訊:對應圖檔的名字(圖檔包含了所有你要繪制的字元)、圖檔中的字元對應的unicode編碼、字元在圖檔中的坐标、寬高等。初始化CCLabelBMFont對象時,會把圖檔添加到緩存(CCTextureCache)中,解析fnt檔案,把fnt檔案中對應的資訊儲存到一個ccBMFontDef類型的數組裡面,數組的索引是charId(字元的unicode編碼值),ccBMFontDef是一個結構體:
typedef struct _BMFontDef {
//! ID of the character
unsigned int charID;
//! origin and size of the font
CCRect rect;
//! The X amount the image should be offset when drawing the image (in pixels)
int xOffset;
//! The Y amount the image should be offset when drawing the image (in pixels)
int yOffset;
//! The amount to move the current position after drawing the character (in pixels)
int xAdvance;
} ccBMFontDef;
繪制字元串時,根據字元對應的unicode碼去查找ccBMFontDef資訊,從緩存中取出圖檔,再根據ccBMFontDef中坐标、寬高取出對應區域的字元圖檔,把字元在字元串中的索引位置作為tag添加到CCLabelBMFont中,因為CCLabelBMFont本身是CCSpriteBatchNode,這樣就實作了批處理渲染精靈,提高了性能。下面是建立字元對應的CCSprite的部分代碼:
void CCLabelBMFont::createFontChars()
{
/** .... */
//以下代碼是周遊字元串時:for循環内的代碼
const ccBMFontDef& fontDef = (*(m_pConfiguration->m_pBitmapFontArray))[c];
CCRect rect = fontDef.rect;
CCSprite *fontChar;
fontChar = (CCSprite*)(this->getChildByTag(i));
if( ! fontChar )
{
fontChar = new CCSprite();
fontChar->initWithBatchNodeRectInPixels(this, rect);
this->addChild(fontChar, 0, i);
fontChar->release();
}
else
{
// reusing fonts
fontChar->setTextureRectInPixels(rect, false, rect.size);
// restore to default in case they were modified
fontChar->setIsVisible(true);
fontChar->setOpacity(255);
}
/** .... */
}
CCLabelAtlas :也是CCAtlasNode的子類,建立一個CCLabelAtlas對象的代碼如下:
static CCLabelAtlas * labelWithString(const char *label, const char *charMapFile, unsigned int itemWidth, unsigned int itemHeight, unsigned char startCharMap);
//示例
CCLabelAtlas* label1 = CCLabelAtlas::labelWithString("123 Test", "fonts/tuffy_bold_italic-charmap.png", 48, 64, ' ');
參數的含義:要繪制的字元,圖檔檔案,圖檔檔案中每個字元的寬度,圖檔檔案中每個字元的高度,圖檔的起始字元。
CCAtlasNode封裝了一個CCTextureAtlas的變量,CCTextureAtlas初始化圖檔檔案的時候會把圖檔加載到緩存(CCTextureCache)中:
bool CCTextureAtlas::initWithFile(const char * file, unsigned int capacity)
{
// retained in property
CCTexture2D *texture = CCTextureCache::sharedTextureCache()->addImage(file);
if (texture)
{
return initWithTexture(texture, capacity);
}
else
{
CCLOG("cocos2d: Could not open file: %s", file);
delete this;
return NULL;
}
}
接下來CCTextureAtlas負責管理該大圖,可以随意繪制圖檔的某一矩形區域,渲染方式采用的是OpenGL ES VBO(頂點緩沖對象,儲存在顯存中)。 CCTextureAtlas有一個m_pQuads屬性,它是CCTextureAtlas類的核心,是一個ccV3F_C4B_T2F_Quad類型的數組,ccV3F_C4B_T2F_Quad是一個結構體,有四個成員屬性,它們都是ccV3F_C4B_T2F類,分别表示左上,左下,右上,右下。看源碼:
//! a Point with a vertex point, a tex coord point and a color 4B
typedef struct _ccV3F_C4B_T2F
{
//! vertices (3F)
ccVertex3F vertices; // 12 bytes
// char __padding__[4];
//! colors (4B)
ccColor4B colors; // 4 bytes
// char __padding2__[4];
// tex coords (2F)
ccTex2F texCoords; // 8 byts
} ccV3F_C4B_T2F;
//! 4 ccVertex2FTex2FColor4B Quad
typedef struct _ccV2F_C4B_T2F_Quad
{
//! bottom left
ccV2F_C4B_T2F bl;
//! bottom right
ccV2F_C4B_T2F br;
//! top left
ccV2F_C4B_T2F tl;
//! top right
ccV2F_C4B_T2F tr;
} ccV2F_C4B_T2F_Quad;
ccV3F_C4B_T2F有三個成員,分别表示:頂點、顔色、紋理坐标。
CCTextureAtlas類就是根據這個數組來繪制矩形的,數組的容量就是要繪制的字元數量。指定字元串的時候:是根據指定字元的ASCII碼值跟startCharMap(圖檔起始字元)ASCII碼值的偏移量,得到該字元在圖檔上的區域的,然後生成繪制矩形所需要的資料,源碼:
//CCLabelAtlas - CCLabelProtocol
void CCLabelAtlas::setString(const char *label)
{
/** .... */
this->updateAtlasValues();
/** .... */
}
//CCLabelAtlas - Atlas generation
void CCLabelAtlas::updateAtlasValues()
{
unsigned int n = m_sString.length();
ccV3F_C4B_T2F_Quad quad;
const unsigned char *s = (unsigned char*)m_sString.c_str();
CCTexture2D *texture = m_pTextureAtlas->getTexture();
float textureWide = (float) texture->getPixelsWide();
float textureHigh = (float) texture->getPixelsHigh();
for(unsigned int i = 0; i < n; i++) {
unsigned char a = s[i] - m_cMapStartChar;
float row = (float) (a % m_uItemsPerRow);
float col = (float) (a / m_uItemsPerRow);
#if CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
// Issue #938. Don't use texStepX & texStepY
float left = (2 * row * m_uItemWidth + 1) / (2 * textureWide);
float right = left + (m_uItemWidth * 2 - 2) / (2 * textureWide);
float top = (2 * col * m_uItemHeight + 1) / (2 * textureHigh);
float bottom = top + (m_uItemHeight * 2 - 2) / (2 * textureHigh);
#else
float left = row * m_uItemWidth / textureWide;
float right = left + m_uItemWidth / textureWide;
float top = col * m_uItemHeight / textureHigh;
float bottom = top + m_uItemHeight / textureHigh;
#endif // ! CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
quad.tl.texCoords.u = left;
quad.tl.texCoords.v = top;
quad.tr.texCoords.u = right;
quad.tr.texCoords.v = top;
quad.bl.texCoords.u = left;
quad.bl.texCoords.v = bottom;
quad.br.texCoords.u = right;
quad.br.texCoords.v = bottom;
quad.bl.vertices.x = (float) (i * m_uItemWidth);
quad.bl.vertices.y = 0;
quad.bl.vertices.z = 0.0f;
quad.br.vertices.x = (float)(i * m_uItemWidth + m_uItemWidth);
quad.br.vertices.y = 0;
quad.br.vertices.z = 0.0f;
quad.tl.vertices.x = (float)(i * m_uItemWidth);
quad.tl.vertices.y = (float)(m_uItemHeight);
quad.tl.vertices.z = 0.0f;
quad.tr.vertices.x = (float)(i * m_uItemWidth + m_uItemWidth);
quad.tr.vertices.y = (float)(m_uItemHeight);
quad.tr.vertices.z = 0.0f;
m_pTextureAtlas->updateQuad(&quad, i);
}
}
是以圖檔上的字元排列順序要按照ASCII碼表的順序連續排列。CCLabelAtlas的繪制效率高,但是限制性太多,沒有CCLabelBMFont靈活。
從類結構圖可以看到CCMenuItemLabel有兩個子類CCMenuItemAtlasFont和CCMenuItemFont,CCMenuItemAtlasFont是使用CCLabelAtlas建立MenuItemLabel的輔助類,CCMenuItemFont是使用CCLabelTTF建立MenuItemLabel的輔助類。如下源碼所示:
bool CCMenuItemAtlasFont::initFromString(const char *value, const char *charMapFile, int itemWidth, int itemHeight, char startCharMap, CCObject* target, SEL_MenuHandler selector)
{
CCAssert( value != NULL && strlen(value) != 0, "value length must be greater than 0");
CCLabelAtlas *label = new CCLabelAtlas();
label->initWithString(value, charMapFile, itemWidth, itemHeight, startCharMap);
label->autorelease();
if (CCMenuItemLabel::initWithLabel(label, target, selector))
{
// do something ?
}
return true;
}
bool CCMenuItemFont::initFromString(const char *value, CCObject* target, SEL_MenuHandler selector)
{
CCAssert( value != NULL && strlen(value) != 0, "Value length must be greater than 0");
m_strFontName = _fontName;
m_uFontSize = _fontSize;
CCLabelTTF *label = CCLabelTTF::labelWithString(value, m_strFontName.c_str(), (float)m_uFontSize);
if (CCMenuItemLabel::initWithLabel(label, target, selector))
{
// do something ?
}
return true;
}
2. CCMenuItemSprite和CCMenuItemImage :本質上都是使用圖檔建立菜單項,前者是使用精靈對象建立,後者使用圖檔名稱建立,CCMenuItemImage是CCMenuItemSprite的子類。可以使用三套圖檔:未選中狀态、選中狀态、不可用狀态,前面兩種狀态的圖檔是必需的,不可用狀态的圖檔可選。如下代碼所示:
static CCMenuItemSprite * itemFromNormalSprite(CCNode* normalSprite, CCNode* selectedSprite, CCNode* disabledSprite = NULL);
static CCMenuItemImage* itemFromNormalImage(const char *normalImage, const char *selectedImage);
static CCMenuItemImage* itemFromNormalImage(const char *normalImage, const char *selectedImage, const char *disabledImage);
3. CCMenuItemToggle : 開關菜單
它是一個容器,可以切換包含的子項(可以是任何的MenuItem對象)。它封裝了一個CCMutableArray<CCMenuItem*>*類型的屬性m_pSubItems。代碼示例:
static CCMenuItemToggle* itemWithTarget(CCObject* target, SEL_MenuHandler selector, CCMenuItem* item, ...);
CCMenuItemToggle* item1 = CCMenuItemToggle::itemWithTarget(this,menu_selector(MenuLayer4::menuCallback),
CCMenuItemFont::itemFromString( "On" ),
CCMenuItemFont::itemFromString( "Off"),NULL );
b. 分析了菜單的各個相關類的原理和用法後,現在來看看如何使用它們,下面示例代碼整合了各種菜單項的建立:
void MenuLayer::onEnter()
{
CCLayer::onEnter();
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
/**---CCMenuItemLabel:由指定的字元串标簽建立菜單--**/
//CCMenuItemFont:内部使用CCLabelTTF
CCMenuItemFont::setFontName("Arial");
CCMenuItemFont::setFontSize(22);
CCMenuItemFont* pFontMenuItem = CCMenuItemFont::itemFromString("font item", this, menu_selector(MenuLayer::menuCallback));
CCMenu* pFontMenu = CCMenu::menuWithItems(pFontMenuItem,NULL);
pFontMenu->setPosition( ccp(winSize.width/2,winSize.height - 30) );
this->addChild(pFontMenu);
//CCMenuItemAtlasFont:内部使用CCLabelAtlas
CCMenuItemAtlasFont* pAtlasFontMenuItem = CCMenuItemAtlasFont::itemFromString("123456789", s_imgPathNum, 15, 19, '0', this, menu_selector(MenuLayer::menuCallback));
CCMenu* pAtlasFontMenu = CCMenu::menuWithItems(pAtlasFontMenuItem,NULL);
pAtlasFontMenu->setPosition( ccp(winSize.width/2,winSize.height - 60) );
this->addChild(pAtlasFontMenu);
//CCLabelBMFont
CCLabelBMFont* pBMFontLabel = CCLabelBMFont::labelWithString("configuration", s_imgPathBMFont);
CCMenuItemLabel* pItemBMFontLabel = CCMenuItemLabel::itemWithLabel(pBMFontLabel, this, menu_selector(MenuLayer::menuCallback));
CCMenu* pBMFontMenu = CCMenu::menuWithItems(pItemBMFontLabel,NULL);
pBMFontMenu->setPosition( ccp(winSize.width/2,winSize.height - 90) );
this->addChild(pBMFontMenu);
/**--CCMenuItemSprite:由指定的精靈類建立菜單--**/
CCSprite* spriteNormal = CCSprite::spriteWithFile(s_imgPathMenuItem, CCRectMake(0,23*2,115,23));
CCSprite* spriteSelected = CCSprite::spriteWithFile(s_imgPathMenuItem, CCRectMake(0,23*1,115,23));
CCSprite* spriteDisabled = CCSprite::spriteWithFile(s_imgPathMenuItem, CCRectMake(0,23*0,115,23));
CCMenuItemSprite* pMenuItemSprite = CCMenuItemSprite::itemFromNormalSprite(spriteNormal, spriteSelected, spriteDisabled, this, menu_selector(MenuLayer::menuCallback));
CCMenu* pSpriteMenu = CCMenu::menuWithItems(pMenuItemSprite,NULL);
pSpriteMenu->setPosition( ccp(winSize.width/2,winSize.height - 120) );
this->addChild(pSpriteMenu);
//CCMenuItemImage:由指定的圖檔檔案名建立菜單
CCMenuItemImage* pMenuItemImage = CCMenuItemImage::itemFromNormalImage(s_imgPathCloseNormal, s_imgPathCloseSelected, this, menu_selector(MenuLayer::menuCallback) );
CCMenu* pImageMenu = CCMenu::menuWithItem(pMenuItemImage);
pImageMenu->setPosition( ccp(winSize.width/2,winSize.height - 150) );
this->addChild(pImageMenu);
//CCMenuItemToggle:開關菜單,切換效果
//這裡隻使用了CCMenuItemFont,還可以使用其他的CCMenuItem
CCMenuItemToggle* pMenuItemToggle = CCMenuItemToggle::itemWithTarget(this, menu_selector(MenuLayer::menuCallback),
CCMenuItemFont::itemFromString( "On" ),
CCMenuItemFont::itemFromString( "Off"),
NULL );
CCMenu* pToggleMenu = CCMenu::menuWithItems(pMenuItemToggle,NULL);
pToggleMenu->setPosition( ccp(winSize.width/2,winSize.height - 180) );
this->addChild(pToggleMenu);
}
運作效果如下:
程式使用的圖檔素材:
CCLabelBMFont代碼段使用的素材是:cocos2d-x安裝目錄/tests/Resources/fonts/bitmapFontTest3.fnt和對應的png檔案
ps:CCMenuItem預設使用的字型是Marker Felt,字型大小是32,在CCMenuItem.h中定義了:
#define kCCItemSize 32
static unsigned int _fontSize = kCCItemSize;
static std::string _fontName = "Marker Felt";
轉載請注明來自:Alex Zhou,本文連結:http://codingnow.cn/android/832.html