转自:http://codingnow.cn/cocos2d-x/832.html
学习cocos2d-x中的菜单主要需要了解:菜单(CCMenu)和菜单项(CCMenuItem)以及CCMenuItem的具体子类。
a. 下面来学习一下相关的类。
1. CCMenu
菜单,是CCLayer的子类,是一个层(容器),可以往里面添加菜单项。下面是它的类结构图:
CCMenu默认接受触屏事件的优先级是-128(优先级很高,因为值越小,响应触屏事件的优先级越高),可以通过继承它实现自定义的效果,创建CCMenu对象的函数:
1
2
|
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的子类,用来渲染文字标签的,可以指定字体,每次设置字符串内容时都需要重新创建纹理和渲染,性能不好,可以看它的相关源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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格式的文件(字库),如:
1
|
CCLabelBMFont *label = CCLabelBMFont::labelWithString(
"Bitmap Font Atlas"
,
"fonts/bitmapFontTest.fnt"
);
|
这个fnt文件包含了这些信息:对应图片的名字(图片包含了所有你要绘制的字符)、图片中的字符对应的unicode编码、字符在图片中的坐标、宽高等。初始化CCLabelBMFont对象时,会把图片添加到缓存(CCTextureCache)中,解析fnt文件,把fnt文件中对应的信息保存到一个ccBMFontDef类型的数组里面,数组的索引是charId(字符的unicode编码值),ccBMFontDef是一个结构体:
1
2
3
4
5
6
7
8
9
10
11
12
|
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的部分代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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对象的代码如下:
1
2
3
|
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)中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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类,分别表示左上,左下,右上,右下。看源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
//! 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码值的偏移量,得到该字符在图片上的区域的,然后生成绘制矩形所需要的数据,源码:
1
2
3
4
5
6
7
8
9
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
|
//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的辅助类。如下源码所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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的子类。可以使用三套图片:未选中状态、选中状态、不可用状态,前面两种状态的图片是必需的,不可用状态的图片可选。如下代码所示:
1
2
3
4
|
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。代码示例:
1
2
3
4
5
|
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. 分析了菜单的各个相关类的原理和用法后,现在来看看如何使用它们,下面示例代码整合了各种菜单项的创建:
1
2
3
4
5
6
7
8
9
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
|
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中定义了:
1
2
3
|
#define kCCItemSize 32
static
unsigned
int
_fontSize = kCCItemSize;
static
std::string _fontName =
"Marker Felt"
;
|