前情提要
今天在开发游戏引导框架时,遇到这样的需求:人物对话文本支持打字机效果,且需要个别文字高亮。如果仅仅是前者的需求,是挺好实现的,创建一个Label
,通过getLetter(index)
获取每个字,调用setVisible(isVisible)
即可;但是个别文字高亮是RichText才有的功能,于是难点变成了如何获取RichText里的每个字?
代码分析
话不多说,看源代码,从UIRichText.cpp
文件的formatText
方法中,我们发现RichText
的本质就是多个Label
的拼接:
1 | void RichText::formatText(bool isForce) |
这段代码的逻辑是通过读取insertElement()
方法传入的RichElement
,根据RichElement类别的不同,创建Label
、Sprite
、Node
等,放入RichText这个容器中,因为在当前情境下,只有Label
被创建,所以其他不在考虑范围。
有了Label
就可以拿到每个文字了,那么Label
从哪里获取呢?我们把上面代码再精简下:
1 | void RichText::formatText(bool isForce) |
发现Label
被传进了pushToContainer(render)
方法中,这个方法的代码很简单:
1 | void RichText::pushToContainer(cocos2d::Node *renderer) |
所以思路就变成了如何从_elementRenders
中获取所有的Label
?
逻辑编写
基于上述梳理,编写获取RichText
中所有文字的逻辑如下:
1 | void RichText::getAllLetters() |
编写完成,将CPP
转为Lua
,看一下效果!
1 | 代码: |
为什么会出现这样的情况呢?
打断点,进入getAllLetters()
,傻眼了,_elementRenders
是个空数组。原因是formatText()
方法的最后,RichText调用了formarRenderers()
方法,我们来简单地看一下formarRenderers
的逻辑:
1 | void RichText::formarRenderers() |
前面做了什么逻辑我们不关心,我们关心的是方法的最后调用了_elementRenders.clear()
,也就是说:每次执行formatText()
后,_elementRenders
都会被清空,它只是一个临时变量,所以接下来要做的就是在_elementRenders
被清掉前,遍历获取每一个Letter并存下来。
于是代码变成了这样:
1 | UIRichText.h |
终于,CPP层间的逻辑算是实现了。但这时候,又有新的问题出现了。
解决LabelLetter无法转Lua问题
按照新的逻辑,从Lua层面调用getAllLetters()
方法,发现获取的结果依然是空table;但是在CPP层面,却是可以获取到数据的。那么问题就出在toLua
的过程。
说明:tolua是cocos2d-x提供的lua-binding工具,位于项目tools/tolua目录下。
继续断点调试,将问题定位在了下面这个方法:
1 | /** |
问题的症结出在了g_luaType
这里,在这个数组里,找不到LabelLetter
类,虽然getAllLetters()
返回的是Sprite
的数组,但本质上,Label::getLetter()
中返回的Sprite是通过LabelLetter
创建的,而LabelLetter
是在CCLabel.cpp
里面定义的,g_luaType
根本不知道LabelLetter
的存在。
既然如此,就一改到底吧!
1 | template <class T> |
在获取typeName以后,判断typeName的类别,若是class cocos2d::LabelLetter
,则将其强行改为class cocos2d::Sprite
。
至此,RichText可算可以调用getAllLetters()
拿到所有的文字了,本教程也算告一段落。
写在最后
本文的逻辑重在实现思路,其实其中也存在很多待优化的地方,如:富文本存在表情图片时如何支持打字机效果?又如:后面转Lua裸写类别名的判断不够优雅。还有诸如:不需要打字机效果的文本存_letters会造成内存浪费等。感兴趣的读者欢迎在本文的基础上进行优化修改!感谢阅读~