富文本打字机遮罩实现

很多游戏要求对话框文本一个字一个字的出现,在文本的时候很容易实现,只需要字符串拼接即可。但是在富文本中呢?因为有颜色和尺寸等字符的出现,就无法使用字符串拼接了,此时很多小伙伴就犯难了,那么怎么在富文本上也实现打字机的效果呢?接下来将提供两种实现方式。

1.剔除标签,使用字符替换,将结果保存到数组中,然后依次显示https://forum.cocos.org/t/topic/68506
这种方法就不过多阐述了,论坛的大佬早就已经给出解决方案了。
但是此种方法在原生上性能很低,每次渲染数组中的文本时GameLogic会飙升,时间长了会导致手机发热 :cry:。接下来将重点分享第二种方案。

2.在显示文本的时候只进行一次渲染,用遮罩的方式将文本遮挡起来,之后更新mask遮罩的宽度就可以实现类似打字机的效果了。


右侧的富文本中有三行,在左侧的节点树中我们可以看见富文本渲染实际上是创建了三个文本节点来完成富文本的显示(这一点在富文本组件中的私有属性_labelSegments中可以获取);因此我们可以给每个文本节点创建一个mask节点作为父节点,通过更新mask节点的宽度来实现一个字一个字的出现。
话不多说,直接上代码

/**
 * 富文本打字机
 * @param {cc.RichText} animLab 富文本组件
 * @param {string} storyStr 文本
 * @param {number} speed 打字机速度
 * @param {Function} callback 打字完成回调
 */
playTypewriterAnim(animLab: cc.RichText, storyStr: string, speed: number = 1000, callback?: Function) {
    animLab.string = '';
    animLab.node.removeAllChildren();
    animLab.string = storyStr;

    //富文本RichText组件是分为多个文本Label组件实现的,这一点在私有属性_labelSegments中可以发现
    let _labelSegments = animLab._labelSegments;
    let delay = 0;
    let tweenAnim = [];
    for (let i = 0; i < _labelSegments.length; i++) {
        const element = _labelSegments[i];
        console.log(element.getComponent(cc.Label).string + ' x:' + element.x + ' y:' + element.y);
        const maskNode = new cc.Node();
        maskNode.name = `mask${i}`;
        maskNode.addComponent(cc.Mask);
        maskNode.anchorX = element.anchorX;
        maskNode.anchorY = element.anchorY;
        maskNode.x = element.x + animLab.node.width / 2;
        maskNode.y = element.y - animLab.node.height / 2;
        maskNode.setContentSize(element.getContentSize());
        element.parent = maskNode;
        element.x = 0;
        element.y = 0;
        element.anchorX = 0.5;
        element.anchorY = 0.5;
        animLab.node.addChild(maskNode);
        let width = maskNode.width;
        let dur = width / speed;
        tweenAnim.push({ node: maskNode, delay: delay, dur: dur, width: width })
        delay += dur;
    }
    animLab.string = '';
    let parentY = animLab.node.y;
    animLab.node.y = 2600;
    this.scheduleOnce(() => {
        for (let i = 0; i < tweenAnim.length; i++) {
            const element = tweenAnim[i];
            element.node.width = 0;
            if (i < tweenAnim.length - 1) {
                cc.tween(element.node).delay(element.delay).to(element.dur, { width: element.width }).start();
            } else {
                cc.tween(element.node).delay(element.delay).to(element.dur, { width: element.width }).call(() => {
                    if (callback) callback();
                }).start();
            }
        }
        animLab.node.y = parentY;
    }, 0);
}

运行结果可以看见每个文本上都覆盖了一个mask节点

附Deom:typewriter.zip (816.6 KB)

1赞

这样 drawcall 应该增加了吧?

你这对话,废话文学哈哈哈哈 :rofl: :rofl: :rofl: :rofl:

会升高,但是在原生上比起一个字一个字渲染性能上要好得多 :rofl:

百度的哈哈哈

mark!!

一个字一个mask太伤了,改成一行一个感觉会好点

这就是一行一个哈

:astonished:不好意思,没细看哈哈,那这效果感觉就很理想了