Unity的动态图集实现
静态图集的缺陷之前项目一直是使用静态图集,为了尽可能地降低DrawCall和节约内存,遵循着两条规则:1.每个UI界面创建一个图集放独有的图片。2.通用的图统一放到一个公用图集中。但是随着项目业务的拓展,通用的图标越来越多,导致通用图集越来越大甚至2048*2048的尺寸都放不下,就算根据功能去再次划分公用图集也治标不治本,而且还会导致几个Icon跨图集渲染之间相互穿插,带来额外的DrawCall。再者有极端的场景只用到了图集里的一张小图,却要加载整张图集,从而造成了较大的内存浪费。但完全不打图集DrawCall又直接爆炸。动态图集就是为了解决此问题的较为完美的解决方案。 性能优化测试效果未使用动态图集,2张图4个Draw Call。使用动态图集,2张图3个Drwa Call,图越多解决越多。 动态图集原理动态图集的核心原理为:在运行时维护一张较大的纹理区域,在运行时动态地往纹理区域内添加或移除,现实上直接把纹理区域指定给RawIamge的texture然后设置其UV Rect即可。由于同时渲染的icon数量往往远远小于实际icon的资源数量,因此通常一张1024或者2048大小...
UI背景模糊的两种实现方法
原理实现模糊的原理比较简单:一个像素本身的颜色通过某种算法,最终计算为周围颜色的一个均值或权重值,这样看起来的图就是模糊的了。通过计算均值颜色的算法选择不同,就产生了不同效果、不同效率的模糊。具体实现算法可以看左神的这篇文章:高品质后处理:十种图像模糊算法的总结与实现,里面把所有的模糊算法的数学原理和效果都列举完了,言简意赅,字字珠玑。下面介绍我在实际工程中的实现UI背景模糊两种实现方法。 因为需要计算周围颜色的值,所以我们首先需要抓取到当前渲染的画面。目前我知道的抓取画面有两种方法:1、GrabPss。2、屏幕后处理。所以我这两把这两种方式分成了两种方法。 方法一:GrabPassGrabPass是Unity的内置渲染管线中一个特殊的pass,可以在shader中将当前帧抓取到一张纹理贴图中。我的实现中获取了两次,分别做了垂直和水平方向的模糊处理。效果如下:通过调整Image位置和大小实现局部模糊: shader代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454...
基于前缀树的红点系统设计与实现
前言红点系统作为游戏内的活动开启、提醒等有着重要的作用,并广泛用于几乎所有类型的游戏中。之前在项目中为了实现红点都是各个业务使用独立逻辑,当有红点之前有父子层级结构时,逻辑就变得非常复杂和难以维护。最近看了几篇前缀树实现红点系统的文章,自己也来尝试实现一个。 效果这里先上一个gif展示效果。左上角的数值分为左右两部分:左边为节点及其所有子节点的总数值;右边为此节点自身的数值。 前缀树介绍 前缀树或Trie树,是一种哈希树的变种树形数据结构。其核心特征是通过共享字符串的公共前缀减少存储空间和缩短查询时间,查询效率优于传统哈希树。每个节点存储单一字符,子节点字符互不相同,路径构成对应字符串。基本操作包括插入、查找和前缀匹配,节点通常包含标识字段用于标记单词结尾。该结构广泛应用于字符串检索、自动补全、拼写检查、IP路由表以及文本统计排序,被搜索引擎系统用于词频统计与字典序排序。通过构建路径实现快速检索和前缀匹配,节点压缩技术与动态内存管理可优化存储效率。持久化字典树采用数据压缩、并发锁机制和分布式计算框架提升性能,支持多线程构建与查询。 – 百度百科 红点数据节点的数据结构红点节点作...
Unity的TMP的纹理优化
上一篇文章介绍了Unity的TMP和Text,TMP在渲染时需要先一张距离纹理,这不禁让我想到一种优化策略:如果对这张纹理选择不同图像压缩方式,是不是就可以以牺牲显示效果为代价,换取内存开销。 Unity SDF Asset结构使用AlibabaPuHuiTi-3-65-Medium.ttf文件创建一个SDF Asset,选择渲染模式为static,并使用3500常用字生成贴图,可以看到Unity生成了一张4096*4096的纹理,并且大部分面积都存了字体。Unity还把纹理贴图当成了SDF Asset结构的一部分管理起来,不允许我们直接修改图像的压缩方式。 TMP纹理的提取不幸中的万幸,Unity把TMP的源码跟着package一起放在了本地,通过对于的编辑器界面逻辑,我找到了Unity保存纹理的过程代码。其中Save_Bitmap_FontAsset和Save_SDF_FontAsset都走向了执行的逻辑都相同,都是做了同样的三件事:1、把纹理赋值给atlasTextures字段。2、把纹理资源设置到FontAsset中。3、更新FontAsset资源的材质主帖图为当前纹理。...
Unity的TMP和Text原理和使用优化
Unity在2018版本加入了TextMeshPro,并且现在已经将Text标记为Legacy。下面是我有关于TMP和Text的理解和思考。 Text的渲染原理Unity的text组件是使用FreeType库来加载TrueType字体来实现的。通过加载TrueType字体并为每一个字形生成位图以及计算几个度量值(Metric),生成出一个具体的字形的位图纹理。text动态渲染一个字符过程为:判断一个字符是否已经存在字体纹理中,在则直接获取对应的纹理坐标渲染;若不在则读取ttf文件中字符数据生成字符的位图,并放入纹理中管理。若放入时图集已满,则会对图集进行一次扩大。因为每个size、粗体、斜体的度量值不同,所以当修改Text的这些值的时候,纹理中会出现同一个字符多个位图的情况。 TMP的渲染原理TMP是基于SDF(有向距离场)的技术渲染的,将字体中每个像素或顶点离其最近的形状边缘的距离值,存储到一张距离纹理中。所以TMP在显示前必须生成这个距离纹理,否则将无法显示(Unity中TMP会缺字出现口的原因)。因为显示的像素为动态计算该使用何种颜色和透明度,从而创建出精细、高品质的字体效...
Unity的协程的原理理解
协程最近在看面试题,经常会遇到一个经典的八股问题:协程是多线程吗?底层原理是什么。虽然接触Unity好几年了,但是一直没用探究过其中的底层原理,看到这个问题也大脑空白。今天就来探寻一下其中的原理。 测试代码废话不多说,直接上代码。代码非常简单一个TestCoroutine函数内yield return null和log输出。 IL代码C#的代码看不出什么,打开IL代码。可以清晰编译器为我们的协程生成了一个类名叫d__1,并且继承了IEnumerator,类中除了IEnumerator的Current、MoveNext()和Reset(),还额外多了一个state变量。我们自己写的TestCoroutine函数中就new了这个类的实例。其中类中最关键的就是这个state变量的使用。 state变量的使用从截图可以看到,MoveNext()中维护的switch充当了一个类似状态机的逻辑,每个yield return null就变成了一个case,state变量就变成了case块的记录,通过枚举去执行不同的逻辑,这就是协程可重入机制的根本原因。 协程的性能消耗既然协程是new的了一个类...
C#匿名函数与闭包
起因最近看到了一段C#程序,一开始就简单的以为只是一个for循环的0~9的打印,但是深入了解之后发现我错了,而且对其中的原理相知甚少。代码如下: 12345678910111213public static void Main(string[] args){ Action[] actions = new Action[10]; for (int i = 0; i < 10; i++) { actions[i] = () => {Console.WriteLine(i.ToString()); }; } foreach (var action in actions) { action(); }} 这段代码的运行结果输出是10个10,如果做对了,想必已经是了解其中的原理,下面的内容可以不用看了。 原因原因其实很简单,使用编辑器查看IL代码,可以很清晰的看到:编译器为匿名函数生成了一个名为<>c__DisplayC...
Unity的==与空并运算符(?.)
Unity.Object使用==与?.的区别众所周知,C#的判空有两种:==与?.(空并运算符),之前因为没有深入了解两种判空的在Uniyt.Object中的差别,开发的时候更是想到谁就用谁,造成了一些Crash,下面详细讲解两者的区别。 Unity.Object使用?.如上图代码和结果所示,在使用GameObject.DestroyImmediate函数当帧立即销毁之后,打印name还是执行了并且报空,说明?.无法正确的判断Unity.Object为空。 Unity.Object使用==如上图代码和结果所示,在使用==判断时,成功判断了对象为空。 差别的原因在解释之前,补充一段关于Unity.Object的知识。Unity运行时是C++的,所有的对象都在C++层管理,在C#层只是有一个引用对象指向C++对象。所以,就有一种情况,就是C++层的对象被销毁了,但是C#层的对象还存在,导致判空出错。了解了这个知识点,下面开始探究问题所在。以下是Unity重载==运算符的源码:上面的方法...
算法--判断点是否在多边形中(包含凹凸多边形)
判断点是否在一个凸多边形内部,可以根据面积、叉乘的方法判断。但是包括凹多边形的时候,就得使用射线法判断。下面介绍这种算法。 算法原理奇-偶规则(Odd-even Rule):奇数表示在多边形内,偶数表示在多边形外从任意位置p作任意方向的一条射线,若与该射线相交的多边形边的数目为奇数,则p是多边形内部点,否则是外部点。以上图为例:从红点向任意方向发射射线(上图是向左和向右),与图形的边的交点总和为奇数时,点在内部;为偶数时,点在外部。(上图向左有5个点,向右有3个点),所以红点在多边形的内部。所以,问题就从判断点在多边形的内部转化为了:判断点朝任意方向的射线与多边形的边的交点个数的奇、偶问题。 如何判断射线与多边形的交点?从上图可以非常直观地看出,取出多边形中两个相邻的点P1、P2,根据两点得到直线的方程为:Y=(y1-y2)/(X1-X2)*(X - X1)+Y1。将待监测点P0的X0代入直线方程(如果向上打射线),计算出X0在该射线上的Y位置,如果Y>Y0则说明:该P0点在线段P1、P2的下方,交点数+1;反之则为下方。 代码实现12345678910...
Unity节点编辑器(六) -- 引导节点与引导流程
引导流程需求目前已经有了节点和引导的遮罩,为了实现编辑步骤,还要把每一步需要高亮的位置或者需要高亮跟随的GameObject放入节点数据中,并根据节点的先后顺序逐一运行逻辑。所以就需要有3个事情:1.引导节点。目前节点编辑器只有开始节点,无法存储引导节点的数据,所以需要继承于NodeBase和数据,实现编辑器的可视化功能。2.引导节点逻辑处理脚本。为了之后的拓展性,所以针对每一种引导节点都有一个特定的处理脚本。通过维护一个状态机的生命周期,在脚本中拿到节点数据进行具体的逻辑处理。3.引导系统。我把这个写成了单例。负责加载引导数据并反序列化出来,同时也要提供根据引导名字开启引导的接口。 引导节点实现引导数据数据类继承于原来的,引导需要知道高亮指定的GameObject,数据就需要知道GameObject的ID(这里通过给对应的物体挂载脚本),引导数据传入ID。 12345678910[Serializable]public class GuideData:NodeSerializationData{ [SerializeField] public string...










