Cocos2dx CCLabel

本文对CCLabel的实现进行相关介绍:

1 一般性绘制流程

绘制流程与之前介绍的Render常规性流程保持一致,其入口为visit:

void Label::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    //...
    if (_systemFontDirty || _contentDirty) // 文本内容发生更新 
    {
        updateContent(); 
    }
    //...
    if (!_children.empty())
    {
        sortAllChildren();
        int i = 0;
        for (; i < _children.size(); i++)
        {
            auto node = _children.at(i);

            if (node && node->getLocalZOrder() < 0)
                node->visit(renderer, _modelViewTransform, flags);
            else
                break;
        }
        this->drawSelf(visibleByCamera, renderer, flags); // 绘制自身
        for (auto it = _children.cbegin() + i; it != _children.cend(); ++it)
        {
            (*it)->visit(renderer, _modelViewTransform, flags);
        }
    }
    else
    {
        this->drawSelf(visibleByCamera, renderer, flags);
    }
}

visit中主要包含两部分:1) 文本信息更新;2)绘制自身。

1.1 文本信息更新

visit中当发现文本信息发生任何更新时,执行updateContent操作。这里用了两个变量记录这一变化:_systemFontDirty(系统字体设置发生相关更改时置为true) 与_contentDirty(自定义字体发生任何属性变化时置为true)。

updateContent方法首先对自定义字体做了是否清除的判断:使用了自定义字体后重设系统字体,则将原有自定义字体删除,删除时清空一个相对重要的辅助自定义字体显示的数组_batchNodes。接着,基于字体类型更新文本:如果是自定义字体,则计算所有字符属性以供后续布局及绘制,如果是系统字体,则直接创建一个承载该字体的sprite。

void Label::updateContent()
{
    if (_systemFontDirty) // 系统字体属性发生变更
    {
        if (_fontAtlas) // 说明在设置系统字体属性之前 已设置了自定义字体 此时需要做一些自定义字体相关的清空操作
        {
            _batchNodes.clear(); // _batchNodes变量的意义将在后面介绍
            FontAtlasCache::releaseFontAtlas(_fontAtlas);
            _fontAtlas = nullptr;
        }
        _systemFontDirty = false;
    }
    if (_fontAtlas) // 自定义字体
    {
        std::u16string utf16String;
        if (StringUtils::UTF8ToUTF16(_utf8Text, utf16String)) // _utf8Text在label调用setString时赋值 记录了label的文本
        {
            _utf16Text = utf16String;
        }
        computeHorizontalKernings(_utf16Text); // 计算文字的水平字距 计算结果将用于后续的字符位置计算
        updateFinished = alignText(); // 计算每个字符属性
    }
    else
    {
        auto fontDef = _getFontDefinition();
        createSpriteForSystemFont(fontDef);
        if (_shadowEnabled)
        {
            createShadowSpriteForSystemFont(fontDef);
        }
    }
    if(updateFinished){
        _contentDirty = false;
    }
}

对于自定义字体,其主要工作在alignText内部。1)该过程首先判断:如果有新字加入,则基于该新字的纹理创建对应的SpriteBatchNode,并将其缓存至_batchNodes中,可以发现,_batchNodes是动态维护的,以此保证只为同一个字创建一次SpriteBatchNode,而在后续绘制阶段,只需遍历_batchNodes即可而无需重复的为文本创建SpriteBatchNode。当然,这种动态维护的做法对程序的灵活性有一定要求。

bool Label::alignText()
{
    //...
    bool ret = true;
    do {
        _fontAtlas->prepareLetterDefinitions(_utf16Text); // 生成新增字的定义(此处旧的不会被删)
        auto& textures = _fontAtlas->getTextures(); // 新增字的纹理也在其内
        if (textures.size() > _batchNodes.size()) // 基于新纹理创建 SpriteBatchNode 此处可以发现,_batchNodes用于缓存所有纹理的纹理对应的SpriteBatchNode
        {
            for (auto index = _batchNodes.size(); index < textures.size(); ++index)
            {
                auto batchNode = SpriteBatchNode::createWithTexture(textures.at(index));
                if (batchNode)
                {
                    _isOpacityModifyRGB = batchNode->getTexture()->hasPremultipliedAlpha();
                    _blendFunc = batchNode->getBlendFunc();
                    batchNode->setAnchorPoint(Vec2::ANCHOR_TOP_LEFT);
                    batchNode->setPosition(Vec2::ZERO);
                    _batchNodes.pushBack(batchNode);
                }
            }
        }
        if (_batchNodes.empty())
        {
            return true;
        }
        //...
        if (_maxLineWidth > 0.f && !_lineBreakWithoutSpaces)
        {
            multilineTextWrapByWord(); // 多行文本解缠 实际上就是计算文本中每个字的布局属性letterInfo
        }
        else
        {
            multilineTextWrapByChar();
        }
        //...
        if(!updateQuads()){ // 基于文本中每个字的布局属性更新_batchNodes中每个SpriteBatchNode的quad绘制属性
            ret = false;
            if(_overflow == Overflow::SHRINK){
                this->shrinkLabelToContentSize(CC_CALLBACK_0(Label::isHorizontalClamp, this));
            }
            break;
        }
        //...
    }while (0);
    return ret;
}

多行文本解缠用于将Label中的文本解析成多行字符,并计算每个字符的位置及其他属性信息。

bool Label::multilineTextWrap(std::function<int(const std::u16string&, int, int)> nextTokenLen)
{
    int textLen = getStringLength();
    int lineIndex = 0;
    float nextTokenX = 0.f; // 下一个字符起点X位置
    float nextTokenY = 0.f; // 下一个字符起点Y位置
    float longestLine = 0.f;
    float letterRight = 0.f; // 记录当前行长度

    auto contentScaleFactor = CC_CONTENT_SCALE_FACTOR();
    float lineSpacing = _lineSpacing * contentScaleFactor;
    float highestY = 0.f;
    float lowestY = 0.f;
    FontLetterDefinition letterDef;
    Vec2 letterPosition;
    for (int index = 0; index < textLen; )
    {
        auto character = _utf16Text[index];
        if (character == '\n') // 换行
        {
            _linesWidth.push_back(letterRight); // 保存当前行
            letterRight = 0.f;
            lineIndex++;
            nextTokenX = 0.f;
            nextTokenY -= _lineHeight*_bmfontScale + lineSpacing;
            recordPlaceholderInfo(index, character);
            index++;
            continue;
        }

        auto tokenLen = nextTokenLen(_utf16Text, index, textLen); // 计算下一个字|单词字符长度
        float tokenRight = letterRight; // 为何新建两个变量:因为下一个单词有可能因为过长超出当前行而导致处理失效
        float nextLetterX = nextTokenX; // 单词处理成功 则用新变量更新旧变量;如不成功,基于旧变量重新开始处理
        bool newLine = false; // 单词过长 当前行承载不了,就需要切换至下一行
        for (int tmp = 0; tmp < tokenLen;++tmp) // 开始处理下一个单词中的所有字符
        {
            int letterIndex = index + tmp;
            character = _utf16Text[letterIndex];
            if (character == '\r') // 回至当前行起点处
            {
                recordPlaceholderInfo(letterIndex, character);
                continue;
            }
            if (_fontAtlas->getLetterDefinitionForChar(character, letterDef) == false)
            {
                recordPlaceholderInfo(letterIndex, character);
                CCLOG("LabelTextFormatter error:can't find letter definition in font file for letter: %c", character);
                continue;
            }

            auto letterX = (nextLetterX + letterDef.offsetX * _bmfontScale) / contentScaleFactor; // 当前单词当前字符起点
            if (_enableWrap && _maxLineWidth > 0.f && nextTokenX > 0.f && letterX + letterDef.width * _bmfontScale > _maxLineWidth
                && !StringUtils::isUnicodeSpace(character))
            {
                _linesWidth.push_back(letterRight);
                letterRight = 0.f;
                lineIndex++;
                nextTokenX = 0.f;
                nextTokenY -= (_lineHeight*_bmfontScale + lineSpacing);
                newLine = true;
                break;
            }
            else
            {
                letterPosition.x = letterX;
            }
            letterPosition.y = (nextTokenY - letterDef.offsetY * _bmfontScale) / contentScaleFactor;
            recordLetterInfo(letterPosition, character, letterIndex, lineIndex);

            if (_horizontalKernings && letterIndex < textLen - 1)
                nextLetterX += _horizontalKernings[letterIndex + 1];
            nextLetterX += letterDef.xAdvance * _bmfontScale + _additionalKerning; // 更新下一个字符起点
            tokenRight = letterPosition.x + letterDef.width * _bmfontScale; // 更新当前行长度
        }

        if (newLine)
        {
            continue; // 单词处理失败 从新行重新开始处理
        }

        // 当前单词处理完毕  更新旧变量
        nextTokenX = nextLetterX;
        letterRight = tokenRight;        
        index += tokenLen;
    }
    // 保存最后一行(最后一行不会触发上面的 超出当前行后保存当前行宽 的条件)
    _linesWidth.push_back(letterRight);
    _numberOfLines = lineIndex + 1;
    //...
    return true;
}

由于_batchNodes只增不减,因此每当文本内容发生变化时都需要对其更新。更新流程是:首先删除所有batchNode的quads,之后基于_letterinfo将新的quad信息输入。

1.2 绘制自身

绘制自身,分为两种情形,一是基于系统自带字体绘制,二是基于自定义字体绘制。绘制系统字体十分简单,直接绘制承载该字体的sprite即可,其方式就是sprite->visit();对于TTF类型的艺术字,需创建一个自定义绘制命令,该命令将处理一些文本设定的特效。关于Render层面如何处理这些绘制命令,可参考之前的博文。

void Label::drawSelf(bool visibleByCamera, Renderer* renderer, uint32_t flags)
{
    if (_textSprite)
    {
        if (_shadowNode)
        {
            _shadowNode->visit(renderer, _modelViewTransform, flags);
        }
        _textSprite->visit(renderer, _modelViewTransform, flags);
    }
    else if (visibleByCamera && !_utf8Text.empty())
    {
        draw(renderer, _modelViewTransform, flags);
    }
}
void Label::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    //...
    if (!_shadowEnabled && (_currentLabelType == LabelType::BMFONT || _currentLabelType == LabelType::CHARMAP))
    {
        // BMFONT|CHARMAP 使用 QuadCommand 进行绘制
        auto textureAtlas = _batchNodes.at(0)->getTextureAtlas();
        _quadCommand.init(_globalZOrder, textureAtlas->getTexture()->getName(), getGLProgramState(), 
            _blendFunc, textureAtlas->getQuads(), textureAtlas->getTotalQuads(), transform, flags);
        renderer->addCommand(&_quadCommand);
    }
    else
    {
        // TTF类型 使用自定义类型绘制
        _customCommand.init(_globalZOrder, transform, flags);
        _customCommand.func = CC_CALLBACK_0(Label::onDraw, this, transform, transformUpdated);
        renderer->addCommand(&_customCommand);
    }
}
void Label::onDraw(const Mat4& transform, bool transformUpdated)
{
    auto glprogram = getGLProgram();
    glprogram->use();
    GL::blendFunc(_blendFunc.src, _blendFunc.dst);
    glprogram->setUniformsForBuiltins(transform);
    if (_currentLabelType == LabelType::TTF)
    {
        switch (_currLabelEffect) {
        case LabelEffect::OUTLINE: // 使用描边特效
            glprogram->setUniformLocationWith4f(_uniformTextColor, // 文本颜色 装载至OpenGL
                _textColorF.r, _textColorF.g, _textColorF.b, _textColorF.a);
            glprogram->setUniformLocationWith4f(_uniformEffectColor, // 描边颜色信息 装载至OpenGL
                _effectColorF.r, _effectColorF.g, _effectColorF.b, _effectColorF.a);
            for (auto&& batchNode : _batchNodes)
            {
                batchNode->getTextureAtlas()->drawQuads(); 
            }
            //draw text without outline
            glprogram->setUniformLocationWith4f(_uniformEffectColor,
                _effectColorF.r, _effectColorF.g, _effectColorF.b, 0.f); // alpha置0 用于下方绘制不带描边的文本
            break;
        case LabelEffect::GLOW: // 发光特效
            glprogram->setUniformLocationWith4f(_uniformEffectColor,
                _effectColorF.r, _effectColorF.g, _effectColorF.b, _effectColorF.a);
        case LabelEffect::NORMAL:
            glprogram->setUniformLocationWith4f(_uniformTextColor,
                _textColorF.r, _textColorF.g, _textColorF.b, _textColorF.a);
            break;
        default:
            break;
        }
    }
    for (auto&& batchNode : _batchNodes)
    {
        batchNode->getTextureAtlas()->drawQuads();
    }
}

猜你喜欢

转载自blog.csdn.net/XIANG__jiangsu/article/details/80527829