关于NGUI的文字破碎网上好像都没有什么根本的解决办法。比如很多文章只是建议扩充动态字体的Texture。这个方法既不治标也不治本。
但是,UGUI就没有文字破碎的问题。这说明什么?可能文字破碎只是NGUI的一个Bug。那么我们改了这个Bug不就好了么,反正代码都是能看到的。不过既然这个Bug能流传这么多年,说明两件事:
第一,大家并不重视这个Bug。
第二,可能它本身也不是那么好解决。
这个Bug应该就是这样,大家对它的重视好像还没有到足够根本解决它的程度。那么,还是先分析一下NGUI的渲染逻辑。首先,NGUI的每帧的渲染逻辑都先从UIPanel的一个LateUpdate开启。写出来大致是这样:
NGUI_LateUpdate { //Update each panel in order 更新每个UIPanel() //Update all draw calls,making them draw in the right order 更新每个UIDrawCall的位置,裁剪,排序等 () }
这个方法其实我们可以不用管更新DrawCall的部分,因为我们知道文字破碎其实只是和每个点中的UV数据有关,而给DrawCall排序的时候,这个UV数据已经不可改变了。我们就看更新每个UIPanel的方法,这个方法大致是这样的:
UIPanel_UpdateSelf { 更新位置矩阵 更新GameObject层 更新每个widget 更新DrawCall }
这里会改变UV数据的只有第三个更新每个Widget层,所以我们接着来看更新每个Widget的方法,这个方法有点复杂但大致是这样的:
UIPanel_UpdateWidgets { …… 遍历每个Widget { …… if(这个widget需要更新) { 更新这个Widget() …… } } }
这里只有更新Widget会触发UV的重建我们再看这个方法,Widget的UpdateGeometry到这里我们把最开始的LateUpdate深入四层了,不过,再接再厉,这个方法大致是这样的:
Widget_UpdateGeometry { 各种条件赋值 if(全量更新条件满足) { 更新UIWidget的全部顶点数据,包括位置,UV,顶点颜色 } elseif(只更新位置数据的条件满足) { 更新UIWidget的全部顶点位置数据 } }
终于,到UILabel了,让我们看看UILabel里是如何更新顶点数据的
UILabel_OnFill { …… 更新NGUIText参数 计算当前UILabel顶点数据 …… }
这里计算更新Label用到了一个静态类NGUIText因为动态字体所需要的数据太多所以专门拿出来一个静态类用来放一些参数和方法。这里和UGUI是有区别的,有兴趣的朋友可以自己去看一下UGUI的源码。那么我们如果想使用NGUIText的Print来计算动态字体的UV和位置等信息,我们必须要让UILabel_UpdateNGUIText和NGUIText_Print连用现在我们继续展开NGUIText_Print:
NGUIText_Print { …… 向字体请求数据 NGUIText_Prepare 计算顶点数据 }
这里,在向字体请求数据时会触发字体FontTexture的更新,NGUI中用来注册这个回调的是UILabel的OnFontChanged方法然而,这个方法不是很稳定,NGUI版本不同可能写法都不一样,总是改来改去的。但大致逻辑是这样的:
UILabel_OnFontChanged { 收集所有UILabel,并向该字体请求数据 更新这些UILabel的UIDrawCall }
问题出现在更新所有UILabel的UIDrawCall上。假设本次的FontTexture更新是由NGUIText_Print中的Prepare逻辑触发的那么更新UILabel的时候就不能更新它对应的顶点数据,因为此时NGUIText被某一个单独的UILabel占用着,别的UILabel一更新那么这个正在Print的的UILabel数据就错了那说,如果我不更新顶点的UV数据呢,那么更是错的,因为除了当前这个被Print的UILabel之前更新的UILabel它的UV全是错的。UV一错,就是我们能够看到的文字破碎的情况了。
那么,怎么改?
我只能说我的思路。相信既然Bug明确了个人有个人的写法,那么对于我来说,首先就不能够在OnFontChanged中这么草率的更新UIDrawCall。要看OnFontChanged的触发,是否是由于UIPanel的LateUpdate主方法中更新产生的。而在UIPanel_LateUpdate中也要判断是否是触发过OnFontChanged。那么这两个函数写起来应该像这样的:
UIPanel_LateUpdate改 { UIPanel_LateUpdate_Flag = true for( int i = 0 : panel总数 ) { panel_updateSelf if(UILabel_OnFontChanged_Flag) { UILabel_OnFontChanged_Flag = false for(int j = i :0) { 清除panel更新标志 } i = 0 } } UIPanel_LateUpdate_Flag = false }
UILabel_OnFontChanged改 { UILabel_OnFontChanged_Flag = true changeIndex++ if(changeIndex == 1) { for(当前label) { 请求Font数据 } if(!UIPanel_LateUpdate_Flag) { UILabel_OnFontChanged_Flag = false 更新label顶点和对应DrawCall } } changeIndex-- }
改成这样之后剩下的工作大概是要处理一系列因此改动而产生的Bug。