修复cocos2d-jsv3.1文本换行bug

本文作者:IMWeb vienwu 原文出处:IMWeb社区 未经同意,禁止转载

使用cocos2d-js版开发跨平台手游非常简单,并且在手机端也拥有不错的性能。但因为推出时间并不够久,用户也不够多,项目里仍然存在不少bug,这里介绍一个常见的bug和个人解决方案。

大段中文文字无法自动换行并且在不同终端行为不一致的bug修复

这个bug具体表现为,js版的cc.LabelBMFont类实现存在缺陷。 该类中,判断是否自动换行时,首先检测字符是否结束或者是否存在空格,满足条件后才会换行。 当字符串为英文时,此逻辑可以良好执行,但面对中文时就不能正常处理了。 理论上,在cocos2d-html5/cocos2d/labels/cclabelbmfont.js大约736行if (!self._lineBreakWithoutSpaces) {应该判断是否为中文,或者在后续的寻找空格逻辑中,增加寻找中文的判断。

其次,cocos2d-js在手机端执行时,会将js代码编译为jsb字节码,调用的cc.LabelBMFont类是c++实现的,并且该类实现的算法和web端的实现不同,导致字体大小、换行行为不一致,尤其在单独控制某个文本字符颜色时,定位某个文本的索引都会不同。

另,cocos2d自身不支持单独设置一段文本中某个字符的颜色,如需要实现该效果,只能使用cc.LabelBMFont类(不支持cc.LabelTTF),再单独查找到对应的cc.Sprite并设置颜色。所以这里我重写了一个类单独处理文本,支持使用类似ubb的方式单独设置某部分文本的颜色,例如:

var text = '这是一段测试文本,[color=#ff0000]这里是红色[/color],[color=green]这里是绿色[/color]';
var label = new game.Label(text,24,'#ffffff',100,50);

以上代码即可实现默认颜色为#ffffff即白色,这里是红色会显示为红色,这里是绿色会显示为绿色,字体大小24px,最大宽度100px,超过即会换行,最大高度为50px,超过时会自动添加一个滚动条。

以下为实现代码:(使用coffeescript编写,可使用相应工具转换为js代码)

注:cc.LabelBMFont使用了fnt字体文件,需要自行生成对应的fnt文件和png资源。
# 重新实现label类,by ying 

defaultSize = 32
game.Label = cc.Sprite.extend
    _str:''
    ctor:(str,fontSize=24,color='#ffffff',width=0,height=0)->
        this._super()
        this._str = String(str) or ''
        this._color = color or '#ffffff'
        this._fontSize=fontSize
        this._contentWidth = width
        this._contentHeight = height

        this.anchorY=1
        return if not str
        this.updateString()
    updateString:->
        locStr = String(this._str)
        _str = locStr.replace /\[color=#\w*\]/gi,''
        _str = _str.replace /\[\/color\]/gi,''

        color = this._color
        typeof color is 'string' and color = cc.color color
        colorList = [color]

        this.removeAllChildren()
        if cc.sys.isNative
            label=new cc.LabelBMFont _str,game.res.myfont,Math.floor(this._contentWidth*defaultSize/this._fontSize)
        else
            label=new cc.LabelBMFont _str,game.res.myfont,this._contentWidth
        label.setLineBreakWithoutSpace true
        label.setScale this._fontSize/defaultSize
        if cc.sys.isNative
            label.attr anchorY:0,anchorX:0,color:color
        else
            label.attr anchorY:0,anchorX:0,color:cc.color '#ffffff'
        maxWidth=label.width*label.getScaleX()
        maxHeight=label.height*label.getScaleY()

        len=locStr.length or 0
        tag=skip=i=0

        if this._contentWidth>0 and this._contentHeight>0
            size = cc.size this._contentWidth,this._contentHeight
            labelBgSprite=new cc.Sprite()
            labelBgSprite.attr anchorX:0,anchorY:0,width:size.width,height:maxHeight
            labelBgSprite.addChild label
            scroll=new cc.ScrollView size,labelBgSprite
            scroll.attr direction:cc.SCROLLVIEW_DIRECTION_VERTICAL
            scroll.setContentOffset x:0,y:scroll.minContainerOffset().y
            this.addChild scroll
            this.width = this._contentWidth
            this.height = this._contentHeight
            this.scroll = scroll
        else
            this.addChild label
            this.width=maxWidth
            this.height=maxHeight

        if locStr.indexOf('[color=') isnt -1 or  not cc.sys.isNative
            while i<len
                char=locStr[i]
                key=char.charCodeAt 0
                if char is '[' and locStr.substr(i,7) is '[color='
                    colorList.push cc.color(locStr.substr(i+7,7))
                    i=i+15
                    continue
                if char is '[' and locStr.substr(i,8) is '[/color]'
                    colorList.pop()
                    i=i+8
                    continue
                if key is 10
                    i++
                    skip++
                    continue
                while true
                    break if tag+skip>len
                    sprite=label.getChildByTag tag+skip
                    if not sprite
                        skip++
                    else
                        if cc.sys.isNative
                            nextSprite=label.getChildByTag tag+skip+1
                            break if not nextSprite
                            break if nextSprite.getPositionX()>=sprite.getPositionX()
                            skip++
                        else
                            break if sprite.visible
                            skip++
                sprite.color = colorList[colorList.length-1]  if sprite
                i++
                tag++
        # cc.log 'hello',this.width,this.height,this.anchorX,this.anchorY
    setString:(str)->
        this._str = String(str) or ''
        this.updateString()
game.LabelTTF = cc.Sprite.extend
    _str:'' # 文本
    _fontSize:24 # 文字大小
    _color:'#ffffff' # 文本颜色
    _charSprites:null
    _space:0 # 水平间距
    _lineHeight:2 # 行距
    _contentWidth:0 # 最大宽度 自动换行
    _contentHeight:0 # 最大高度,超过最大高度自动加滚动条
    ctor:(str,fontSize=24,color='#ffffff',width=0,height=0)->
        this._super()
        color = '#ffffff' if not color
        this._str = String(str) or ''
        this._fontSize = fontSize
        # this._lineHeight = Math.round this._fontSize * 0.5
        this._color = color
        this._charSprites = []
        this._contentWidth = width
        this._contentHeight = height
        this.anchorY=1
        return if not str
        this.updateString()
    updateString:->
        locStr = String this._str
        color = this._color
        fontSize = this._fontSize
        typeof color is 'string' and color = cc.color color
        colorList = [color]
        len = locStr.length or 0
        this.removeAllChildren()
        this._charSprites=[]
        posX = maxWidth = maxHeight = i = 0
        rows = 1
        fontHeight = 0 # 字体的高度和fontSize不一定相同
        if locStr.indexOf('[color=') is -1 and this._contentWidth is 0 # 不需要区分颜色和换行
            sprite = this.getStrSprite locStr,fontSize
            sprite.attr x:0,y:-1*fontSize,anchorY:0,anchorX:0,fillStyle:color
            maxWidth = sprite.width
            maxHeight = fontHeight = sprite.height
            this._charSprites.push sprite
        else
            while i<len
                char = locStr[i]
                key = locStr.charCodeAt i
                if char is '[' and locStr.substr(i,7) is '[color='
                    colorList.push cc.color locStr.substr i+7,7
                    i=i+15
                    continue
                if char is '[' and locStr.substr(i,8) is '[/color]'
                    colorList.pop()
                    i=i+8
                    continue
                if key is 10 # 换行
                    rows++
                    posX=0
                    i++
                    continue
                sprite = this.getStrSprite char,fontSize
                this._charSprites.push sprite
                fontHeight<sprite.height and fontHeight=sprite.height # 获取一个最大的字体高度用于计算
                sprite.attr x:posX,anchorY:0,anchorX:0,fillStyle:colorList[colorList.length-1]
                posX+=sprite.width+this._space # 增加字间距
                if this._contentWidth>0 and posX>this._contentWidth # 换行
                    sprite.attr x:0
                    rows++
                    posX=sprite.width+this._space
                sprite.attr y:(rows-1)*(fontHeight+this._lineHeight)*-1-fontHeight
                maxWidth<posX and maxWidth=posX
                i++
        maxHeight = rows * fontHeight + (rows-1) * this._lineHeight
        if this._contentWidth>0 and this._contentHeight>0
            # 如果设置了内容最大宽度高度,则添加一个可滚动层
            size = cc.size this._contentWidth,this._contentHeight
            labelBgSprite = new cc.Sprite()
            for sprite in this._charSprites
                sprite.attr y:maxHeight+sprite.y
                labelBgSprite.addChild sprite
            labelBgSprite.attr width:maxWidth,height:maxHeight,anchorX:0,anchorY:0
            scroll = new cc.ScrollView size,labelBgSprite
            scroll.attr direction:cc.SCROLLVIEW_DIRECTION_VERTICAL
            scroll.setContentOffset x:0,y:scroll.minContainerOffset().y
            this.addChild scroll
            this.width = this._contentWidth
            this.height = this._contentHeight
            this.scroll = scroll
        else
            this.width = maxWidth
            this.height = maxHeight
            for sprite in this._charSprites
                # 最大高度+偏移+向下移动一小部分(行距和字体刚度不同,增加这部分使得单行文字可以上下居中)
                sprite.attr y:maxHeight+sprite.y-(fontHeight-fontSize)
                this.addChild sprite
    setString:(str)->
        this._str = String(str) or ''
        this.updateString()
    getStrSprite:(str,fontSize)->
        new cc.LabelTTF str,'',fontSize

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Sorrower的专栏

Android绘制(三):Path结合属性动画, 让图标动起来!

21220
来自专栏iOS开发攻城狮的集散地

iOS 封装跑马灯和轮播效果

1.1K40
来自专栏Coding迪斯尼

对Box2D的物理世界进行图像美化和关卡选择设计

我们用Box2D绘制了很多几何图形,例如圆形,矩形,复杂一点就是两个矩形交叉的合在一起,中间再加个圆形。显然这种界面“太素”了,一个丰富多彩,五彩斑斓的游戏世界...

10210
来自专栏偏前端工程师的驿站

CSS魔法堂:再次认识font

一、前言                                 文字承载着站点内涵,而良好的字体、排版则为用户提供舒适的阅读体验。本文打算对字体稍微深...

322100
来自专栏進无尽的文章

实践-小效果 III

大家都知道这是通过 CAShapeLayer 和 CABasicAnimation 结合起来实现的,可是其中还是有需要注意的地方,实现的步骤大致如下:

12120
来自专栏程序员互动联盟

【专业技术】Win32创建异形窗口

大家都见过在windows下各种气泡窗口、输入法窗口已经其他一些窗口,这些窗口看起来不像传统的windows窗那样,上面是标题栏,下面是窗口的客户区。这...

885110
来自专栏非著名程序员

基础篇章:关于 React Native 之 Modal 组件的讲解

(友情提示:RN学习,从最基础的开始,大家不要嫌弃太基础,会的同学请自行略过,希望不要耽误已经会的同学的宝贵时间) Modal是模态视图,它的作用是可以用来覆盖...

28370
来自专栏青玉伏案

iOS开发之画图板(贝塞尔曲线)

  贝塞尔曲线,听着挺牛气一词,不过下面我们在做画图板的时候就用到贝塞尔绘直线,没用到绘制曲线的功能。如果会点PS的小伙伴会对贝塞尔曲线有更直观的理解。这篇博文...

290100
来自专栏九彩拼盘的叨叨叨

Markdown 使用参考h1

Markdown是一种易读易写的标记语言。它能被生成HTML。Markdown的目标是:成为一种适用于网络的书写语言。

10840
来自专栏24K纯开源

用Qt写软件系列五:一个安全防护软件的制作(1)

引言       又有许久没有更新了。Qt,我心爱的Qt,为了找工作不得不抛弃一段时间,业余时间来学一学了。本来计划要写一系列关于Qt组件美化的博文,但是写了几...

24670

扫码关注云+社区

领取腾讯云代金券