Cocos2d之Label

Cocos2d之Label


概览

本文主要分为以下几部分

  1. 以 Cocos2dx V3.17.2 版本,剖析Label框架
  2. 看项目组所用的框架,对比分析变动
  3. 了解相关概念后,针对具体问题,进行探索,寻求解决方案






引擎

版本 V 3.17.2

Label框架,大体分为三部分:

  • Label,面像引擎使用者的显示对象,提供文本标签操作方法
  • Font,描述一个字体所能表示的字符集,及某个字体如何显示,可以用这些信息创建FontAtlas
  • FontAtlas,根据Font信息来创建FontAtlas,缓存、管理字集所用的纹理

再加上一些辅助工具类

  • FontAtlasCache,对FontAtlas对象缓存管理
  • LabelTextFormatter,提供Label静态方法,如换行、对齐等




相关文件与类

关联文件

  • Label
    • CCLabel.h & CCLabel.cpp
    • CCLabelAtlas.h & CCLabelAtlas.cpp
    • CCLabelTTF.h & CCLabelTTF.cpp
    • CCLabelBMFont.h & CCLabelBMFont.cpp
    • CCLabelTextFormatter.cpp
  • Font
    • CCFont.h
    • CCFontAtlas.h & CCFontAtlas.cpp
    • CCFontAtlasCache.h & CCFontAtlasCache.cpp
    • CCFontFNT.h & CCFontFNT.cpp
    • CCFontCharMap.h & CCFontCharMap.cpp
    • CCFontFreeType.h & CCFontFreeType.cpp

关联类

  • Label
    • Label
    • LabelTTF
    • LabelAtlas
    • LabelBMFont
    • LabelLetter
    • LabelProtocol
  • Font
    • Font
    • FontAtlas
    • FontAtlasCache
    • FontFreeType
    • FontCharMap
    • FontFNT
    • BMFontConfiguration



流程

此处流程,仅讨论 Label相关,不涉及过去的 LabelTTF、LabelAtlas、LabelBMFont 等

主要是分为

  • create,创建
  • update,更新


create 创建

关于创建文本,内部可分为两类

  • 使用FontAtlas来绘制的;TTF、BMFont、CharMap 类型
  • 不使用FontAtlas来绘制的;STRING_TEXTURE 类型

使用FontAtlas来绘制的文本,流程为

  1. 通过配置文件,去FontAtlasCache获得 FontAtlas 配置
    • FontAtlasCache 在缓存FontAtlas时,每个类型文件,缓存的哈希key取值不同(例如TTF格式文件,通过文件名、字体大小、字体描边大小 生成索引)
  2. Label与FontAtlas对应关系为一对一,即同一时刻,只会从一个FontAtlas中获取配置、图集等
    • 最终都会调用 Label:setFontAtlas 来 释旧创新
  3. 创建流程,主要结果就是设置Label的字体FontAtlas配置(_fontConfig)与文本类型(_currentLabelType)

不使用FontAtlas来绘制文本,流程为

  1. 设置一些配置,字体文件名、字体大小、文本内容等
  2. 主要结果也是设置 文本类型(_currentLabelType)


visit 绘制

visit流程为

  1. updateContent
    • 根据当前文本类型及过去文本类型,清理缓存
    • 根据不同文本类型,使用不同方法创建文本纹理
    • 在当前文本纹理上,加持其他文本特效
  2. draw
    • 判断可见性
    • 根据对应绘制信息,加入绘制队列




文本,Label

Label是Node派生类,它知道如何去绘制文本。
可以通过以下几种方式创建:

  • TTF文件,LabelTTF
  • FNT文件,LabelBMFont
  • 指定标准plist/png,LabelAtlas
  • 内置系统字,LabelTTF

Label并非LabelTTF、LabelBMFont、LabelAtlas的父类,而是用来统一规范各种Label的使用,是Cocos V3.0版本新加的统一管理类。
Cocos V2.0版本,创建文本要根据文件,采用不同类名去创建;于是Cocos V3.0 版本,新增Label类,统一管理Label创建与操作,当然也可以用之前方法去创建(不建议)。
即使用之前的方式创建文本,LabelTTF与LabelBMFont也都是通过调用Label来创建,所有接口也都是通过调用Label实现

文本概览


一些定义

重要结构与枚举

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
// 重绘类型
enum class Overflow
{
//In NONE mode, the dimensions is (0,0) and the content size will change dynamically to fit the label.
NONE,
/**
*In CLAMP mode, when label content goes out of the bounding box, it will be clipped.
*/
CLAMP,
/**
* In SHRINK mode, the font size will change dynamically to adapt the content size.
*/
SHRINK,
/**
*In RESIZE_HEIGHT mode, you can only change the width of label and the height is changed automatically.
*/
RESIZE_HEIGHT
};

// 文本类型
enum class LabelType {
TTF,
BMFONT,
CHARMAP,
STRING_TEXTURE
};

// Label特效
enum class LabelEffect {
NORMAL,
OUTLINE,
SHADOW,
GLOW,
ITALICS,
BOLD,
UNDERLINE,
STRIKETHROUGH,
ALL
};

// 当Label创建时加载的字符集合
enum class GlyphCollection {
DYNAMIC,
NEHE,
ASCII,
CUSTOM
};

struct LetterInfo
{
char32_t utf32Char;
bool valid;
float positionX;
float positionY;
int atlasIndex;
int lineIndex;
};


创建方法

  1. 创建空对象,默认是系统文本
  2. 创建系统文本,基于平台提供的API
  3. 创建TTF文本,基于FreeType2
  4. 创建FNT文本,基于Bitmap Font File
  5. 创建CharMap文本,基于Char Map Configuration


支持效果

各种Label效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 开启阴影;最后参数模糊半径无用,不支持模糊
void enableShadow(const Color4B& shadowColor = Color4B::BLACK,const Size &offset = Size(2,-2), int blurRadius = 0);

// 开启描边;仅支持 TTF & STRING_TEXTURE(系统字)
void enableOutline(const Color4B& outlineColor,int outlineSize = -1);

// 开启外发光;仅支持 TTF
void enableGlow(const Color4B& glowColor);

// 开启斜体;通过 setRotationSkewX(12) 实现
void enableItalics();

// 开启加粗;通过横向偏移阴影实现
void enableBold();

// 开启下划线;创建DrawNode画线,与删除线共用DrawNode
void enableUnderline();

// 开启删除线;创建DrawNode画线,与下划线共用DrawNode
void enableStrikethrough();

// 取消某种效果
void disableEffect();
void disableEffect(LabelEffect effect);

各种对齐

1
2
3
4
5
6
7
8
// 水平对齐与垂直对齐
void setAlignment(TextHAlignment hAlignment,TextVAlignment vAlignment);

// 水平对齐
void setHorizontalAlignment(TextHAlignment hAlignment)

// 垂直对齐
void setVerticalAlignment(TextVAlignment vAlignment)

设置尺寸与换行相关

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
// 允许换行
void enableWrap(bool enable);

// 设置最大长度;会被 setDimensions设置的宽度冲掉
void setMaxLineWidth(float maxLineWidth);

// 设置宽度;本质是调用 setDimensions
void setWidth(float width)

// 设置高度;本质是调用 setDimensions
void setHeight(float height)

// 设置尺寸
void setDimensions(float width, float height);

// 设置换行标准;逐字符或逐单词
void setLineBreakWithoutSpace(bool breakWithoutSpace);

// 设置行高;每行最终高度 = 行高+行间距
void setLineHeight(float height);

// 设置行间距;每行最终高度 = 行高+行间距
void setLineSpacing(float height);

// 设置字符间距;不支持 STRING_TEXTURE(系统字)
void setAdditionalKerning(float space);

其他绘制项支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 修改文本颜色;仅支持 TTF & STRING_TEXTURE(系统字)
void setTextColor(const Color4B &color);

// 设置颜色混合
void setBlendFunc(const BlendFunc &blendFunc);

// 透明度是否影响色值
void setOpacityModifyRGB(bool isOpacityModifyRGB);

// 设置Overflow类型
// 仅 TTF 和 BMFont 支持所有Overflow类型
// CharMap 不支持 SHRINK
// STRING_TEXTURE 仅支持 NORMAL & RESIZE_HEIGHT
void setOverflow(Overflow overflow);


配件类

LabelLetter

LabelLetter用于在没有SpriteBatchNode情况下更新纹理。
它继承自Sprite类,它并不会被直接绘制,用来更新纹理,矩阵变换及颜色等。

LabelProtocol

Label通用接口,只有 setString/getString

LabelTextFormatter

提供Label一些文本格式处理方法

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
// 计算对齐偏移
void Label::computeAlignmentOffset();

// 获取首字符长度
int getFirstCharLen(const std::u32string& utf32Text, int startIndex, int textLen) const;

// 获取首词长度
int getFirstWordLen(const std::u32string& utf32Text, int startIndex, int textLen) const;

// 获取字符配置项
bool getFontLetterDef(char32_t character, FontLetterDefinition& letterDef) const;

// 更新BMFont缩放系数
void updateBMFontScale();

// 逐字符分割多行文本
bool multilineTextWrapByChar();

// 逐单词分割多行文本
bool multilineTextWrapByWord();

// 多行文本处理
bool multilineTextWrap(const std::function<int(const std::u32string&, int, int)>& lambda);

// 横向超框
bool isHorizontalClamp();

// 纵向超框
bool isVerticalClamp();

// 根据尺寸,调整文本字号
void shrinkLabelToContentSize(const std::function<bool()>& lambda);

// 记录字符信息
void recordLetterInfo(const cocos2d::Vec2& point, char32_t utf32Char, int letterIndex, int lineIndex);

// 记录非法字符(非法占位符、无配置项字符)信息
void Label::recordPlaceholderInfo(int letterIndex, char32_t utf32Char);


文本(旧),LabelTTF、LabelBMFont、LabelAtlas

LabelTTF

LabelTTF是绘制文本的TextureNode的派生类,因此所有TextureNode的特性都可适用于LabelTTF。
但LabelTTF效率较低,可以考虑使用LabelAtlas或LabelBMFont来替代,所需的TTF文件可放在程序可访问的地方来使用。

  • 资源
    • ttf格式文件
  • 优点
    • 创建方便
    • 支持系统字体(节省资源)
    • 适用性广(用系统字库,能显示的内容多)
  • 缺点
    • 创建效率低
    • 文字更新效率低
    • 效果简陋(丑)

创建方式

  1. 指定 显示文本、字体名、字体大小、文本尺寸、对齐方式
  2. 指定 字体配置项、显示文本

字体配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct CC_DLL FontDefinition
{
public:
std::string _fontName;
int _fontSize;
TextHAlignment _alignment;
TextVAlignment _vertAlignment;
float _lineSpacing;
Size _dimensions;
Color3B _fontFillColor;
GLubyte _fontAlpha;
FontShadow _shadow;
FontStroke _stroke;
bool _enableWrap;
int _overflow; // Label:Overflow枚举值
};

支持效果

其中只有TTF与SystemFont支持 描边、修改字体颜色

阴影

  • LabelTTF;无效参数:参数3 模糊参数,参数4 更新纹理;无法控制阴影颜色
  • Label;无效参数:参数3 模糊参数
    1
    2
    3
    4
    5
    6
    7
    void LabelTTF::enableShadow(const Size &shadowOffset, float shadowOpacity, float shadowBlur, bool /*updateTexture*/)
    {
    Color4B temp(Color3B::BLACK);
    temp.a = 255 * shadowOpacity;
    _renderLabel->enableShadow(temp,shadowOffset,shadowBlur); // CCLabel:enableShadow
    _contentDirty = true;
    }

描边

  • LabelTTF,无法控制描边透明度
    1
    2
    3
    4
    5
    void LabelTTF::enableStroke(const Color3B &strokeColor, float strokeSize, bool /*updateTexture*/)
    {
    _renderLabel->enableOutline(Color4B(strokeColor),strokeSize); // CCLabel:enableOutline
    _contentDirty = true;
    }

修改字体颜色

  • LabelTTF,无效参数:参数2 更新纹理;无法控制透明度
    1
    2
    3
    4
    void LabelTTF::setFontFillColor(const Color3B &tintColor, bool /*updateTexture*/)
    {
    _renderLabel->setTextColor(Color4B(tintColor));
    }

颜色混合

  • LabelTTF同Label一致
    1
    2
    3
    4
    void LabelTTF::setBlendFunc(const BlendFunc &blendFunc)
    {
    _renderLabel->setBlendFunc(blendFunc);
    }

水平/垂直 翻转

  • Label 使用 setScaleX/Y 实现,替代 setFlippedX/Y
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void LabelTTF::setFlippedX(bool flippedX)
    {
    if (flippedX)
    {
    _renderLabel->setScaleX(-1.0f); // CCLabel:setScaleX
    }
    else
    {
    _renderLabel->setScaleX(1.0f);
    }
    }


LabelBMFont

LabelBMFont是使用FNT配置文件和图片来显示的一种文本标签,它的效率高于LabelTTF,但只能显示图片中的字符。
每个字符都是一张图片(Sprite),因此所有的字符都可以适应Sprite的特性。
所有内置字符锚点都是(0.5, 0.5),不推荐修改,有可能会影响绘制。
LabelBMFont与Label、LabelAtlas一样,实现了LabelProtocol接口;
LabelBMFont有Label的灵活性、LabelAtlas的速度,并且支持所有Sprite的特性,因此尽量使用它。

  • 资源
    • fnt格式文件及图集
  • 优点
    • 效率高
    • 每个字符都是图片,支持Sprite特性
      • 例如:旋转、缩放 等
    • 可以对每个字体进行自定义优化
  • 缺点
    • 只能显示图集内的字符

创建方式

  1. 指定 显示文本、fnt文件名、单字宽度、对齐方式、字符偏移
  2. 直接创建一个对象,后续根据配置文件初始化

字符偏移,指解析fnt文件,得到对应字符位置后,再加上这个偏移,去图集中截取对应区域显示。
圆心为字符左上角,向右为正向x轴,向下为正向y轴;
这个偏移对整个fnt文件生效,就是第一次载入fnt文件时,就赋值,后续载入即使没有设置偏移,也会采取之前默认的偏移。

fnt文件的映射,是一个ASCII码对应图片区域的映射。
可以根据自己的需要,进行自定义映射,比如做一个小笑脸表情,我们可以设定它的ASCII映射码为97,那在设置文本为a的时候,就会显示这个笑脸。

fnt文件格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
info face="Arial" size=32 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
common lineHeight=32 base=26 scaleW=256 scaleH=128 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
page id=0 file="font_zhandouli2.png"
chars count=12
char id=43 x=103 y=41 width=21 height=40 xoffset=0 yoffset=0 xadvance=21 page=0 chnl=15
char id=48 x=192 y=0 width=26 height=40 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
char id=49 x=125 y=41 width=19 height=40 xoffset=0 yoffset=0 xadvance=19 page=0 chnl=15
char id=50 x=219 y=0 width=26 height=40 xoffset=0 yoffset=0 xadvance=26 page=0 chnl=15
char id=51 x=0 y=41 width=25 height=40 xoffset=0 yoffset=0 xadvance=25 page=0 chnl=15
char id=52 x=108 y=0 width=27 height=40 xoffset=0 yoffset=0 xadvance=27 page=0 chnl=15
char id=53 x=26 y=41 width=25 height=40 xoffset=0 yoffset=0 xadvance=25 page=0 chnl=15
char id=54 x=136 y=0 width=27 height=40 xoffset=0 yoffset=0 xadvance=27 page=0 chnl=15
char id=55 x=78 y=41 width=24 height=40 xoffset=0 yoffset=0 xadvance=24 page=0 chnl=15
char id=56 x=52 y=41 width=25 height=40 xoffset=0 yoffset=0 xadvance=25 page=0 chnl=15
char id=57 x=164 y=0 width=27 height=40 xoffset=0 yoffset=0 xadvance=27 page=0 chnl=15
char id=97 x=0 y=0 width=107 height=40 xoffset=0 yoffset=0 xadvance=107 page=0 chnl=15

支持效果

获取单个字符(图片),索引从0开始

  • 获取字符后,可进行任何对图片的操作
  • 每个字符锚点是 (0.5, 0.5)
1
2
3
4
5
6
7
8
9
Node* LabelBMFont::getChildByTag(int tag) const
{
return _label->getLetter(tag);
}

Sprite* LabelBMFont::getLetter(int ID)
{
return _label->getLetter(ID);
}

设置 宽度 与 自动换行 / 空格换行

1
2
3
4
5
6
7
8
9
10
11
12
13
// 设置最大宽度
void LabelBMFont::setWidth(float width)
{
_label->setMaxLineWidth(width);
this->setContentSize(_label->getContentSize());
}

// 设置 空格换行 / 自动换行,默认空格换行
void LabelBMFont::setLineBreakWithoutSpace( bool breakWithoutSpace )
{
_label->setLineBreakWithoutSpace(breakWithoutSpace);
this->setContentSize(_label->getContentSize());
}

修改字体颜色,透明度

  • 可整体修改,也可通过获取单个字符,单个修改
  • 使用 setOpacity 调整透明度
1
2
3
4
5
6
7
8
9
10
11
12
13
void LabelBMFont::setColor(const Color3B& color)
{
_label->setColor(color);
}

// 透明度是否影响RBG色值
void LabelBMFont::setOpacityModifyRGB(bool var)
{
_label->setOpacityModifyRGB(var);
for(const auto &child : _children) {
child->setOpacityModifyRGB(var);
}
}

支持文本对齐

1
2
3
4
5
void LabelBMFont::setAlignment(TextHAlignment alignment)
{
_label->setAlignment(alignment);
this->setContentSize(_label->getContentSize());
}

颜色混合

1
2
3
4
void LabelBMFont::setBlendFunc(const BlendFunc &blendFunc)
{
_label->setBlendFunc(blendFunc);
}


LabelAtlas

LabelAtlas常用来显示数字,对应Label中的CharMap。
效率很高,可替代Label;相较于Label,LabelAtlas对每个字符有固定宽高;但是灵活性有限,不如使用LabelBMFont。

  • 资源
    • plist文件与图集
    • 单图集
  • 优点
    • 效率高
  • 缺点
    • 只能显示有限的ASCII码字符
    • 功能支持有限

创建方式

  1. 直接创建对象,后续初始化
  2. 指定 显示文本、图集文件名、单字符宽高、起始字符ASCII码
  3. 指定 显示文本、plist文件名

即使使用plist文件,在文件内也要注明 图集文件名、单字符宽高、起始字符ASCII码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>version</key>
<integer>1</integer>
<key>textureFilename</key>
<string>larabie-16.png</string>
<key>itemWidth</key>
<integer>20</integer>
<key>itemHeight</key>
<integer>40</integer>
<key>firstChar</key>
<integer>65</integer>
</dict>
</plist>

起始字符ASCII码是用来做差值,获取对应图片索引。

  • 我们起始字符ASCII码设置为 48(0的ASCII码)
  • setString(“4”),4的ASCII码为 52;因此索引为 52-48 = 4;索引从0开始
  • 取图集中第5个图片返回(此处第n个图片,是根据每个图片固定宽高,来获取要截取的区域)

可以发现,这里的映射也是自定义映射;CCLabelBMFont中想做的 表情图片显示,也是可以实现的。

  • 创建9个表情在图集中,设定好宽高
  • 起始字符设置为48
  • 输入数字0-8,对应显示9个表情图片

支持效果

修改字体颜色,透明度

  • 继承自AtlasNode,相关接口都是AtlasNode提供
  • 使用 setOpacity 调整透明度
  • 使用 setColor 调整颜色
  • 使用 setOpacityModifyRGB 设置透明度是否影响RBG色值
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
// CCAtlasNode.h 
virtual void setOpacityModifyRGB(bool isOpacityModifyRGB) override;
virtual void setColor(const Color3B& color) override;
virtual void setOpacity(GLubyte opacity) override;

// CCLabelAtlas.cpp
void LabelAtlas::updateColor()
{
if (_textureAtlas)
{
Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity );
if (_isOpacityModifyRGB)
{
color4.r *= _displayedOpacity/255.0f;
color4.g *= _displayedOpacity/255.0f;
color4.b *= _displayedOpacity/255.0f;
}
auto quads = _textureAtlas->getQuads();
ssize_t length = _string.length();
for (int index = 0; index < length; index++)
{
quads[index].bl.colors = color4;
quads[index].br.colors = color4;
quads[index].tl.colors = color4;
quads[index].tr.colors = color4;
_textureAtlas->updateQuad(&quads[index], index);
}
}
}




字体,Font

Font类直接继承自Ref,有3种派生类

  • FontCharMap
  • FontFNT
  • FontFreeType

不同字体类分别负责对不同格式文件解析,最终都是创建 FontAtlas 对象。

1
FontAtlas * XXX::createFontAtlas()

总的来说,可以分为以下几部分

  • 各式字体文件格式,通过Font派生类解析并存储相应文件的配置(FontCharMap、FontFNT、FontFreeType)
  • 所有Font派生类,都需要支持创建FontAtlas对象
  • Label直接使用FontAtlas对象,而不知晓解析方式等;为便于重用,设置缓存池 FontAtlasCache
  • 这样 Label - FontAtlas - Font 之间做到解耦

FontAtlas & FontAtlasCache

FontAtlas 是每种字体对应的图集,它缓存并管理一系列 Texture2D 对象,以及每个字符的配置结构。Label可以使用它更方便的渲染文字。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct FontLetterDefinition
{
float U;
float V;
float width;
float height;
float offsetX;
float offsetY;
int textureID;
bool validDefinition;
int xAdvance;
bool rotated;
};

FontAtlasCache 意如其名,做FontAtlas的缓存。
针对不同字体提供不同方法

  • TTF
    • 根据TTF配置获取
    • 支持根据文件名卸载
  • FNT
    • 根据FNT文件获取
    • 支持根据配置重载
  • CharMap
    • 根据plist、图集等配置获取

TTF配置

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
typedef struct _ttfConfig
{
std::string fontFilePath;
float fontSize;

GlyphCollection glyphs;
const char *customGlyphs;

bool distanceFieldEnabled;
int outlineSize;

bool italics;
bool bold;
bool underline;
bool strikethrough;

_ttfConfig(const std::string& filePath = "",float size = CC_DEFAULT_FONT_LABEL_SIZE, const GlyphCollection& glyphCollection = GlyphCollection::DYNAMIC,
const char *customGlyphCollection = nullptr, bool useDistanceField = false, int outline = 0,
bool useItalics = false, bool useBold = false, bool useUnderline = false, bool useStrikethrough = false)
: fontFilePath(filePath)
, fontSize(size)
, glyphs(glyphCollection)
, customGlyphs(customGlyphCollection)
, distanceFieldEnabled(useDistanceField)
, outlineSize(outline)
, italics(useItalics)
, bold(useBold)
, underline(useUnderline)
, strikethrough(useStrikethrough)
{
if(outline > 0)
{
distanceFieldEnabled = false;
}
}
} TTFConfig;


FontFNT

FontFNT实现对BMfont的解析,通过BMFontConfiguration类来解析fnt文件,将文件内所有字符及对应图片名、文字边距属性、文字配置属性保存起来,提供方便调用的接口。

创建FontFNT流程

  1. 解析fnt格式文件,得到 BMFontConfiguration 对象
  2. 使用 BMFontConfiguration 对象与 Texture2D 创建 FontFNT 对象
  3. FontFNT对象存储所有字符的配置,提供接口方便上层调用

创建FontAtlas流程

  1. 检测BMFontConfiguration是否正常
  2. 初始化 FontAtlas
  3. 设置 FontAtlas 的一些配置
  4. 载入所需的图集(当前只支持一张图集)

BMFontConfiguration支持的fnt文件配置及对应解析方法

1
BMFontConfiguration::parseConfigFile
关键字 方法
info face parseInfoArguments
common lineHeight parseCommonArguments
page id parseImageFileName
chars c 忽略解析
char parseCharacterDefinition
kerning first parseKerningEntry

此处也支持解析二进制文本,解析规则基于文件格式 Bitmap Font Generator

1
BMFontConfiguration::parseBinaryConfigFile

注意:

  • 目前BMFont只支持单张图集
    • 若想调整,可尝试修改 FontAtlas * FontFNT::createFontAtlas 内关于图集ID的处理
  • 解析二进制内容,仅支持版本3

The first three bytes are the file identifier and must always be 66, 77, 70, or “BMF”. The fourth byte gives the format version, currently it must be 3.

Version 1 (introduced with application version 1.8).
Version 2 (introduced with application version 1.9) added the outline field in the infoBlock and the encoded field in the commonBlock.
Version 3 (introduced with application version 1.10) removed the encoded field in the commonBlock, and added the alphaChnl, redChnl, greenChnl, blueChnl instead.

The size of each block is now stored without accounting for the size field itself. The character id in the charsBlock and the kerningPairsBlock was increased to 4 bytes to support the full unicode range.


FontFreeType

FontFreeType实现对ttf格式文件处理,使用第三方库FreeType来解析TTF。
内部使用一个cache来缓存ttf文件信息,提供获取GLyphBitmap的方法来得到指定文字的位图。

流程:

  1. 初始化FontFreeType对象
    • FT_Stroker_New,创建FT_Stroker对象
    • FT_Stroker_Set,调整FT_Stroker对象
  2. 设置字符集 GlyphCollection
  3. 初始化FT_Face对象(FreeType库返回对象的句柄)
    1. 根据字体名先在本地缓存中查找
      • 存在,则引用计数+1
    2. 创建FT_Face对象流程
      1. FT_New_Memory_Face
      2. FT_Select_Charmap
      3. FT_Set_Char_Size
  4. 释放FontFreeType对象
    • FT_Stroker_Done,释放FT_Stroker对象
    • FT_Done_Face,释放FT_Face对象
    • 将对应字体名引用从缓存中-1

创建 FontAtlas流程

  • 初始化 FontAtlas 对象
  • 若当前字符集 非GlyphCollection::DYNAMIC
    • 准备文本的所有字符配置项,FontAtlas::prepareLetterDefinitions
      • 获取文本中未显示过的字符
      • 遍历所有新字符,生成配置项 FontLetterDefinition ,缓存
      • 更新当前页图集信息


FontCharMap

实现LabelAtlas图片的解析,创建方式如下

  • 通过纹理、单字符宽、单字符高、起始字符
  • 通过plist文件

流程

  1. 通过图片文件名或解析plist,获取图片并载入缓存
  2. 初始化 FontCharMap(根据图集、单字符宽、单字符高、起始字符)

创建 FontAtlas 流程

  1. 初始化 FontAtlas
  2. 设置 FontLetterDefinition 基础配置
  3. 遍历所有字符填充到 FontAtlas 字符配置
  4. 载入FontAtlas图集




其他

GUI中的文本

CocosStudio 中的文本控件

  • 文本 - UIText - LabelType::TTF / LabelType::STRING_TEXTURE
  • 艺术数字 - UITextAtlas - LabelType::CHARMAP
  • FNT字体 - UITextBMFont - LabelType::BMFont

一般主要用UIText控件来实现,较少用到其他两个。
这三种文本,都可以通过 getVirtualRenderer 获取到Label节点,对它进行操作。
本质上来讲,都是通过Label来实现的。


GUI & UIWidget

GUI 即 图形用户界面(Graphical User Interface)。
相对于用代码来创建各种按钮、图片等,使用可视化的拖动指定控件方式创建按钮、图片等,是更加符合开发者需要且更简便快捷的。

对于 cocos2d-x 来讲,GUI有:

  • CocosBuilder
  • CocosStudio(常用)
  • FairyGUI(非官方)

在CocosStudio中配合使用的控件,都继承自同一个父类UIWidget。
UIWidget主要功能为:

  • 实现一系列位置、布局、自适应相关功能
  • 封装了触摸处理及控件焦点管理


FreeType库

简介(官网

  • 一款 免费、开源、可移植、高质量的 字体引擎
  • 支持多种字体格式文件,并提供统一的接口
  • 支持单色位图、反走样位图渲染
  • 用户可灵活根据需要裁剪

一些概念:

句柄库

  • 含义:FreeType句柄库,用来加载多个字体外观
  • 类型:FT_Library
  • 方法
    • 创建:FT_Init_FreeType
    • 释放:FT_Done_FreeType

字体外观

  • 含义:每种字体对应一个字体外观,通过句柄库载入,字体外观中包含多个字形的配置信息
  • 类型:FT_Face
  • 方法
    • 创建:FT_New_Memory_Face
    • 释放:FT_Done_Face

字形

  • 含义:每个字符都可视作一个字形
  • 类型:FT_GlyphSlot(设置不同字符,将字形载入到制定槽位中)
  • 方法(在 freetype.h 中有API介绍)
    • 从字体外观中载入对应字符的信息: FT_Load_Char
    • 设置字符大小: FT_Set_Char_Size
    • 获取字形索引: FT_Get_Char_Index

关于字形的参数,水平和垂直有所不同

水平布局

FreeType_水平布局

垂直布局

FreeType_垂直布局






项目

待补充



探索

支持多TTF

问题

在支持多语言版本时,玩家可随意输入自己名称,在界面中显示名称时,无法使用单一的国家TTF文本来显示名称,不得已使用系统字,系统字不够美观,效果差。
如果单Label可支持多TTF,就可以让玩家输入的内容,都用TTF样式文本显示,美观度得到保证。


基础

首先,确定TTF 文本的创建流程

  1. FreeType2 库,存所有字符配置信息;根据不同字符返回对应字符的配置信息(宽、高、矩形、偏移)及 位图绘制信息(获取字符信息文本)
  2. 将字符位图信息拼接到当前纹理信息中(拼接纹理信息文本),将字符配置信息缓存
  3. 将当前纹理根据新纹理信息更新(绘制纹理信息)
  4. 遍历所有显示字符,根据字符配置信息,将对应位置纹理绘制到本Label纹理中(加入绘制队列)

这里面,涉及到两个纹理:

  • 使用同TTF文件时,共用的纹理
  • 每个Label使用的自己的纹理

每次新字符都要经历:

  1. 从TTF获取绘制信息,绘制到 TTF文件共用纹理上
  2. 再从TTF文件共用纹理,绘制到 Label自己的纹理上


方案一

思路

Label与TTF文件对应关系

  • Label : FontAtlas = 1 : 1
  • FontAtlas : FT_Face = 1 : 1

此处Label与FontAtlas实际并不是1:1;实际上不同字体大小和描边大小,会使用不同的FontAtlas,为了展述逻辑方便,刻意忽略了这点

有两个方式实现:

  1. 多FontAtlas
    • Label : FontAtlas 变为 1 : n
  2. 多FT_Face
    • FontAtlas : FT_Face 变为 1 : n

相对于多FT_Face,多FontAtlas会导致单个Label使用多个纹理,会增加DC;带来额外负担,所以,使用多FT_Face来实现。


实现

此处,只简要展示一些重要方法

Label支持多TTF创建方式

1
2
3
4
5
6
7
8
9
/**
* Allocates and initializes a Label, base on FreeType2 with multi font file path.
*
* @param text The initial text
* @param fontFilePathList Multi font file.
* @param fontSize The font size.
*/
static Label* createWithMultiTTF(const std::string& text, std::vector<std::string> fontFilePathList, float fontSize);
bool initWithMultiTTF(const std::string& text, std::vector<std::string> fontFilePathList, float fontSize);

注意tolua的支持

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
static int lua_cocos2dx_Label_createWithTTF02(lua_State* L)
{
if (nullptr == L)
return 0;

int argc = 0;

tolua_Error tolua_err;
if (!tolua_isusertable(L, 1, "cc.Label", 0, &tolua_err)) goto tolua_lerror;

argc = lua_gettop(L) - 1;
if (argc == 3)
{
if (!tolua_isstring(L, 2, 0, &tolua_err) ||
!tolua_istable(L, 3, 0, &tolua_err) ||
!tolua_isnumber(L, 4, 0, &tolua_err))
{
goto tolua_lerror;
}
else
{
std::string text = tolua_tostring(L, 2, "");

std::vector<std::string> fontFileList;
if (tolua_istable(L, 3, 0, &tolua_err)) {
size_t len = lua_objlen(L, 3);

for (size_t i = 0; i < len; ++i) {
lua_pushnumber(L, i + 1);
lua_gettable(L, 3);

fontFileList.push_back(lua_tostring(L, -1));
lua_pop(L, 1);
}
}

float fontSize = tolua_tonumber(L, 4, 0);

cocos2d::Label* ret = cocos2d::Label::createWithMultiTTF(text, fontFileList, fontSize);

int ID = ret ? (int)(ret->_ID) : -1;
int* luaID = ret ? &(ret->_luaID) : nullptr;
toluafix_pushusertype_ccobject(L, ID, luaID, (void*)ret, "cc.Label");
return 1;
}
}

tolua_lerror:
return lua_cocos2dx_Label_createWithTTF00(L);
}

从 FontFreeType中获取字形信息的时候,要遍历所有FT_Face

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
unsigned char* FontFreeType::getGlyphBitmap(uint64_t theChar, long &outWidth, long &outHeight, Rect &outRect,int &xAdvance)
{
if (_isMultiTTF) {
bool findChar = false;
unsigned char* ret = nullptr;

for (int i = 0; i < _fontRefList.size(); i++) {
FT_Face face = _fontRefList.at(i);

if (_distanceFieldEnabled) {
if (FT_Load_Char(face, theChar, FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT))
continue;
}
else {
if (FT_Load_Char(face, theChar, FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT))
continue;
}

auto& metrics = face->glyph->metrics;
outRect.origin.x = metrics.horiBearingX >> 6;
outRect.origin.y = -(metrics.horiBearingY >> 6);
outRect.size.width = (metrics.width >> 6);
outRect.size.height = (metrics.height >> 6);

xAdvance = (static_cast<int>(face->glyph->metrics.horiAdvance >> 6));

outWidth = face->glyph->bitmap.width;
outHeight = face->glyph->bitmap.rows;

ret = face->glyph->bitmap.buffer;
//if (ret && outWidth > 0 && outHeight > 0) {
if(ret) {
//unsigned char t = *ret;
//if (t != unsigned char(128) && t != unsigned char(176) && t != unsigned char(20) && t != unsigned char(104)) {
findChar = true;
break;
//}
}
}

if (findChar) {
return ret;
}
else {
outRect.size.width = 0;
outRect.size.height = 0;
xAdvance = 0;

return nullptr;
}
}
else {
bool invalidChar = true;
unsigned char* ret = nullptr;

do
{
if (_fontRef == nullptr)
break;

if (_distanceFieldEnabled)
{
if (FT_Load_Char(_fontRef, theChar, FT_LOAD_RENDER | FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT))
break;
}
else
{
if (FT_Load_Char(_fontRef, theChar, FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT))
break;
}

auto& metrics = _fontRef->glyph->metrics;
outRect.origin.x = metrics.horiBearingX >> 6;
outRect.origin.y = -(metrics.horiBearingY >> 6);
outRect.size.width = (metrics.width >> 6);
outRect.size.height = (metrics.height >> 6);

xAdvance = (static_cast<int>(_fontRef->glyph->metrics.horiAdvance >> 6));

outWidth = _fontRef->glyph->bitmap.width;
outHeight = _fontRef->glyph->bitmap.rows;
ret = _fontRef->glyph->bitmap.buffer;

if (_outlineSize > 0 && outWidth > 0 && outHeight > 0)
{
auto copyBitmap = new (std::nothrow) unsigned char[outWidth * outHeight];
memcpy(copyBitmap, ret, outWidth * outHeight * sizeof(unsigned char));

FT_BBox bbox;
auto outlineBitmap = getGlyphBitmapWithOutline(theChar, bbox);
if (outlineBitmap == nullptr)
{
ret = nullptr;
delete[] copyBitmap;
break;
}

long glyphMinX = outRect.origin.x;
long glyphMaxX = outRect.origin.x + outWidth;
long glyphMinY = -outHeight - outRect.origin.y;
long glyphMaxY = -outRect.origin.y;

auto outlineMinX = bbox.xMin >> 6;
auto outlineMaxX = bbox.xMax >> 6;
auto outlineMinY = bbox.yMin >> 6;
auto outlineMaxY = bbox.yMax >> 6;
auto outlineWidth = outlineMaxX - outlineMinX;
auto outlineHeight = outlineMaxY - outlineMinY;

auto blendImageMinX = MIN(outlineMinX, glyphMinX);
auto blendImageMaxY = MAX(outlineMaxY, glyphMaxY);
auto blendWidth = MAX(outlineMaxX, glyphMaxX) - blendImageMinX;
auto blendHeight = blendImageMaxY - MIN(outlineMinY, glyphMinY);

outRect.origin.x = blendImageMinX;
outRect.origin.y = -blendImageMaxY + _outlineSize;

unsigned char* blendImage = nullptr;
if (blendWidth > 0 && blendHeight > 0)
{
long index, index2;
blendImage = new (std::nothrow) unsigned char[blendWidth * blendHeight * 2];
memset(blendImage, 0, blendWidth * blendHeight * 2);

auto px = outlineMinX - blendImageMinX;
auto py = blendImageMaxY - outlineMaxY;
for (int x = 0; x < outlineWidth; ++x)
{
for (int y = 0; y < outlineHeight; ++y)
{
index = px + x + ((py + y) * blendWidth);
index2 = x + (y * outlineWidth);
blendImage[2 * index] = outlineBitmap[index2];
}
}

px = glyphMinX - blendImageMinX;
py = blendImageMaxY - glyphMaxY;
for (int x = 0; x < outWidth; ++x)
{
for (int y = 0; y < outHeight; ++y)
{
index = px + x + ((y + py) * blendWidth);
index2 = x + (y * outWidth);
blendImage[2 * index + 1] = copyBitmap[index2];
}
}
}

outRect.size.width = blendWidth;
outRect.size.height = blendHeight;
outWidth = blendWidth;
outHeight = blendHeight;

delete[] outlineBitmap;
delete[] copyBitmap;
ret = blendImage;
}

invalidChar = false;
} while (0);

if (invalidChar)
{
outRect.size.width = 0;
outRect.size.height = 0;
xAdvance = 0;

return nullptr;
}
else
{
return ret;
}
}

return nullptr;
}


问题

通过如此配置,已经实现了一个样例,效果如下:

实现样例

可以发现几个问题:

  • 部分文本显示异常
  • 空格消失
  • 字符间距有问题
  • 行间距有问题

部分文本显示异常

  • 主要是因为,我们是按照固定顺序来遍历所有ttf文件,判断未找到的条件是FT_Face返回的字符显示信息为nullptr或者位图宽高小于等于0。但是,如果前面的TTF文件中存储了显示不出来的字符信息且设定为占位符,就会导致不再遍历后面的ttf文件
  • 例如:
    • 韩语显示不了一些汉语,但是又不想显示为空,就拿一些看起来就有问题的字符 × 来替代,让文本显示起来更美观一些;但是这会导致我们向下查找的逻辑失效
  • 解决方案
    • 可以统计所有非法字符,如果返回的字符是非法字符,也继续向下查找
    • 可以计算字符unicode值,根据unicode来判断所属国家,然后优先去对应国家TTF文件中查找

可见,距离投入使用还有一段路程,要后续慢慢优化,


方案二

思路

方案一是修改底层的方式,在一个绘制批次中,显示来自于不同字库的字形。
这里,也可以用在业务层扩展的方式来实现具体需求。

  1. 分析输入的文本中每个字符所属的国家(通过Unicdoe值)
  2. 每个字符用对应的字库来创建
  3. 将这些文本拼接起来(使用富文本)

这样,就可以在业务层直接解决问题,虽然这样修改批次会增多,但是基于使用场景、实现复杂度、维护成本等,使用方案二性价比更高。


实现

在之前的文章中,实现了 判断字符所属国家: 字符集与字符编码
以此为基础,扩展实现。

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
-- refer to Xutf8.lua  UnicodeNationRangeList
local NationIndex2TTF = {
default = "static/common.ttf",
-- [1] = "static/common.ttf",
[2] = "static/common_en.ttf",
[3] = "static/common_cn.ttf",
[4] = "static/common_kr.ttf",
[5] = "static/common_jp.ttf",
[6] = "static/common_ta.ttf",
[7] = "static/common_rs.ttf",
[8] = "static/common_gr.ttf",
}

--[[DESC: 分析传入的文本
一般传入的结构为
{
content = 文本内容
font = 字体文件
fontsize = 字体大小
color = 文本色值
opacity = 文本透明度
outlinecolor = 描边色值
outlinesize = 描边大小
}

v: table
富文本element已有结构

return:
array<v>: 传入格式同结构的数组
]]
function RichTextFactory:analyseNationalContent(element)
local nationList = {}
local content = element.content

local curNationIdx = -1
local startPos = -1
local endPos = -1

local len = Xutf8.len(content)
for i = 1, len do
local c = Xutf8.sub(content, i, i)
local idx, tip = Xutf8.analyseCharBelong(c)

local collectEle = (i == len)

-- 不识别,一般是空格、符号等,归属于附近的语言体系
if idx == -1 then
if curNationIdx == -1 then
startPos = i
endPos = i
else
endPos = i
end
else
if curNationIdx == -1 then
curNationIdx = idx
startPos = i
endPos = i
else
if curNationIdx == idx then
endPos = i
else
collectEle = true
end
end
end

if collectEle then
local temp = clone(element)
temp.content = Xutf8.sub(content, startPos, endPos)
if NationIndex2TTF[curNationIdx] then
temp.font = NationIndex2TTF[curNationIdx]
else
temp.font = NationIndex2TTF.default
end
nationList[#nationList + 1] = temp

curNationIdx = idx
startPos = i
endPos = i
end
end

return nationList
end

最后结果:
三部分

  • 上面部分是富文本实现
  • 中间部分是系统字
  • 下面部分是TTF分别创建的文本

实现样例2


问题

每个TTF的基线不一致,导致看起来并不在同一个水平线。
调整对齐方式,所有元素在垂直方向上居中对齐。

实现样例3




获取SytemFont实际长度

问题

系统字不支持 getLetter方法。
但在富文本中,若使用系统字,就无法做到精准的分割字符串。


思路

系统字创建,是通过 Texture2D::initWithString 来创建出来的,这里面应该能获取到每个字符的举行区域,每个字符在图内的位置。
但,实际上,创建文本是直接调用字库来创建一批文本,并没有一个个传入,然后创建。

1
2
3
4
5
6
7
8
9
10
SIZE sizeWithText(const wchar_t * pszText, int nLen, DWORD dwFmt, LONG nWidthLimit)
{
// ....

// 这里显示的是 Windows平台的实现
// pszText为具体文本内容, nLen为文本长度,rc为整个文本矩形大小
DrawTextW(_DC, pszText, nLen, &rc, dwCalcFmt);

// ...
}

当然,这块也可以做成,每个字符传递一次,然后获得每个举行大小,然后再具体计算,成本过高,不推荐。
临时方案,就根据最终宽度与文本长度,进行大概的分割。




Label支持渐变色

问题

让Label支持渐变色。


思路

在updateColor的时候,要设置左上、左下、右上、右下的色值,可以在此处进行处理


方案

创建相关变量、函数并初始化,注册给lua

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
67
68
69
70
71
72
73
74
75
76
// ==== Label.h
// 0 - none, 1 - from left to right , 2 - from top to bottom
void gradientColor(int dir, Color4B color = Color4B::WHITE);

int _gradientColorDir;
Color4B _gradientColor;



// ==== lua_cocos2dx_auto.cpp
int lua_cocos2dx_Label_gradientColor(lua_State* tolua_S)
{
int argc = 0;
cocos2d::Label* cobj = nullptr;
bool ok = true;

#if COCOS2D_DEBUG >= 1
tolua_Error tolua_err;
#endif


#if COCOS2D_DEBUG >= 1
if (!tolua_isusertype(tolua_S, 1, "cc.Label", 0, &tolua_err)) goto tolua_lerror;
#endif

cobj = (cocos2d::Label*)tolua_tousertype(tolua_S, 1, 0);

#if COCOS2D_DEBUG >= 1
if (!cobj)
{
tolua_error(tolua_S, "invalid 'cobj' in function 'lua_cocos2dx_Label_gradientColor'", nullptr);
return 0;
}
#endif

argc = lua_gettop(tolua_S) - 1;
if (argc == 1)
{
int arg0;
ok &= luaval_to_int32(tolua_S, 2, (int*)&arg0, "cc.Label:gradientColor");

if (!ok)
{
tolua_error(tolua_S, "invalid arguments in function 'lua_cocos2dx_Label_gradientColor'", nullptr);
return 0;
}
cobj->gradientColor(arg0);
lua_settop(tolua_S, 1);
return 1;
}
else if(argc == 2) {
int arg0;
ok &= luaval_to_int32(tolua_S, 2, (int*)&arg0, "cc.Label:gradientColor");

cocos2d::Color4B arg1;
ok &= luaval_to_color4b(tolua_S, 3, &arg1, "cc.Label:gradientColor");

if (!ok)
{
tolua_error(tolua_S, "invalid arguments in function 'lua_cocos2dx_Label_gradientColor'", nullptr);
return 0;
}
cobj->gradientColor(arg0, arg1);
lua_settop(tolua_S, 1);
return 1;
}
luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "cc.Label:gradientColor", argc, 1);
return 0;

#if COCOS2D_DEBUG >= 1
tolua_lerror:
tolua_error(tolua_S, "#ferror in function 'lua_cocos2dx_Label_gradientColor'.", &tolua_err);
#endif

return 0;
}

调整updateColor方法

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
67
68
69
70
void Label::updateColor()
{
if (_batchNodes.empty())
{
return;
}

Color4B color4(_textColor.r, _textColor.g, _textColor.b, _displayedOpacity);
Color4B color42(_gradientColor.r, _gradientColor.g, _gradientColor.b, _displayedOpacity);

// special opacity for premultiplied textures
if (_isOpacityModifyRGB)
{
color4.r *= _displayedOpacity/255.0f;
color4.g *= _displayedOpacity/255.0f;
color4.b *= _displayedOpacity/255.0f;
color42.r *= _displayedOpacity / 255.0f;
color42.g *= _displayedOpacity / 255.0f;
color42.b *= _displayedOpacity / 255.0f;
}

cocos2d::TextureAtlas* textureAtlas;
V3F_C4B_T2F_Quad *quads;
for (auto&& batchNode:_batchNodes)
{
textureAtlas = batchNode->getTextureAtlas();
quads = textureAtlas->getQuads();
auto count = textureAtlas->getTotalQuads();

switch (_gradientColorDir)
{
case 0: {
for (int index = 0; index < count; ++index)
{
quads[index].bl.colors = color4;
quads[index].br.colors = color4;
quads[index].tl.colors = color4;
quads[index].tr.colors = color4;
textureAtlas->updateQuad(&quads[index], index);
}

break;
}
case 1: {
for (int index = 0; index < count; ++index)
{
quads[index].bl.colors = color4;
quads[index].br.colors = color42;
quads[index].tl.colors = color4;
quads[index].tr.colors = color42;
textureAtlas->updateQuad(&quads[index], index);
}

break;
}
case 2: {
for (int index = 0; index < count; ++index)
{
quads[index].bl.colors = color42;
quads[index].br.colors = color42;
quads[index].tl.colors = color4;
quads[index].tr.colors = color4;
textureAtlas->updateQuad(&quads[index], index);
}

break;
}
}
}
}

最终效果:

渐变样例







参考资料