Unity UGUI融合九宫格与填充模式:动态网格技术实战解析
在Unity UGUI项目中,Image组件的Filled填充模式与Sprite九宫格拉伸模式看似互斥,但通过动态网格生成方法,可以实现两者融合效果。创建一个继承Image的自定义组件,在OnPopulateMesh方法中先按九宫格规则计算分割线位置,生成16个顶点和54个索引三角形,再根据填充量、方法和原点调整未填充区域顶点坐标并同步更新UV坐标,确保边框不变形且填充视觉正确。这套方案无需修改引擎源码,适用于圆形进度环等复杂UI场景,平衡性能与维护性。
项目概述:解决Filled与九宫格的现实冲突
在Unity UGUI的开发实践中,Image组件的Filled模式和Sprite的九宫格模式经常被单独使用。Filled模式通过调整顶点位置来控制图像填充比例,常用于血条、冷却条或加载进度条。九宫格模式则在拉伸时只拉伸边角,中间区域保持不变,完美处理尺寸变化带来的失真问题。
表面上看,这两个功能互不干扰。但实际项目中,常遇到需要同时应用的场景。比如制作一个圆形带渐变和描边的进度环,只用Radial填充会使边框随尺寸拉伸变形;启用九宫格保护边框却无法同时进行填充。Unity原生Image组件将两者标记为互斥选项,这一限制源于底层渲染管线的设计考量。
这种冲突源于顶点生成逻辑的根本差异。九宫格基于RectTransform尺寸动态切分网格,Filled则直接变形顶点位置和UV坐标。两者同时作用时,Unity无法处理指令冲突。手动拆分多个Image对象并用遮罩组合,不仅增加Draw Call,还导致UI结构复杂,维护成本高。因此,探索无需改动引擎的融合方案,成为提升UI灵活性的关键课题。
核心需求与方案对比分析
Unity不支持原生融合,主要因为填充语义模糊:填充起点、方向和中心点定义不明确。若融合,可能以整个Rect为基准或以内容区域为基准,Radial的中心点如何定位也无统一标准。
社区方案包括多层叠加、自定义Shader、动态网格生成和帧动画替代。多层叠加法简单但易对齐偏移,适合简单进度条。自定义Shader性能佳但集成复杂,适合有图形程序员支持的高性能UI。帧动画替代精确但资源消耗大,不适合动态变化。动态网格生成法则最平衡:在UGUI CPU端构建标准Mesh,支持合批、点击和遮罩,集成度高,是大多数场景的首选。
选型后,重点关注如何在不改引擎源码前提下实现效果。目标是让精灵兼具边框保护和填充动画特性,满足圆形进度环等高要求UI需求。
核心原理:九宫格网格生成与填充映射
自定义组件的核心是重写网格构建逻辑。先计算九宫格边界线:根据Border值得到xLeft、xRight、yBottom、yTop。矩形被分成九个区域,每个区域用两个三角形构成。完整九宫格通常使用16个顶点和54个索引。
填充映射过程如下:生成完整网格后,根据fillAmount、fillMethod和fillOrigin判断未填充区域。保持填充区域顶点不变,对于未填充顶点,将其位置沿填充方向收缩至边界,并同步调整UV坐标到对应纹理边缘部分。这一步是关键,若仅改位置不改UV,会导致纹理扭曲。
以水平填充为例,填充边界X为width * fillAmount。遍历顶点,若vx > fillX,则设vx = fillX,uvx设为纹理右边界值。Radial 360则需计算角度与半径,沿径向拉回边界并调整UV。保留九宫格特性意味着,填充变化时角区域顶点不受影响,边框始终保持不变。
这个原理让UI在拉伸时边框不变形,填充方向上边框压缩但垂直厚度不变,符合直观感受。
动态网格生成的详细步骤
实现从创建组件开始。继承Image,重写type属性并添加序列化字段支持自定义填充方法。添加OnPopulateMesh重写方法,在其中计算顶点和索引。
具体流程:根据九宫格边界计算分割线,遍历矩形生成顶点坐标和UV。按填充参数筛选并收缩未填充区域顶点,同步修正UV。更新Mesh数组并提交给Canvas。这过程完全在UGUI体系内工作,支持所有交互特性。
调试时,重点检查UV同步,避免挤压导致的视觉异常。测试填充量从0到1的平滑过渡,确保圆形或线性填充效果自然。
using UnityEngine;
using UnityEngine.UI;
[AddComponentMenu("UI/Sliced Filled Image", 11)]
public class SlicedFilledImage : Image
{
public enum SlicedFillMethod
{
Horizontal,
Vertical,
Radial360
}
[SerializeField] private SlicedFillMethod m_FillMethod = SlicedFillMethod.Horizontal;
[SerializeField][Range(0, 1)] private float m_FillAmount = 1.0f;
[SerializeField] private int m_FillOrigin = 0;
public SlicedFillMethod fillMethod { get { return m_FillMethod; } set { if (SetPropertyUtil(ref m_FillMethod, value)) SetVerticesDirty(); } }
public float fillAmount { get { return m_FillAmount; } set { if (SetPropertyUtil(ref m_FillAmount, Mathf.Clamp01(value))) SetVerticesDirty(); } }
public int fillOrigin { get { return m_FillOrigin; } set { if (SetPropertyUtil(ref m_FillOrigin, value)) SetVerticesDirty(); } }
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
if (sprite == null) return;
// 计算九宫格边界
Vector4 border = sprite.border;
Rect rect = GetPixelAdjustedRect();
float xLeft = border.x;
float xRight = rect.width - border.z;
float yBottom = border.y;
float yTop = rect.height - border.w;
// 生成完整九宫格顶点和索引(简化示例,实际需计算16顶点)
// ... (顶点计算逻辑在此省略以保持简洁,参考官方网格生成流程)
vh.AddVert(new Vector3(0, 0), color, Vector2.zero);
vh.AddVert(new Vector3(rect.width, 0), color, Vector2.zero);
vh.AddVert(new Vector3(rect.width, rect.height), color, Vector2.zero);
vh.AddVert(new Vector3(0, rect.height), color, Vector2.zero);
// 根据填充方法调整顶点位置和UV(完整代码中需实现收缩逻辑)
// 示例:水平填充收缩
if (fillMethod == SlicedFillMethod.Horizontal)
{
float fillX = rect.width * fillAmount;
// 遍历并收缩未填充顶点
// 调整vh.AddVert逻辑...
}
// 添加三角形索引...
}
}性能优化与实际应用建议
动态网格生成法在大多数场景中表现优秀:一个Draw Call完成所有渲染,支持Canvas合批,点击事件正常。相比多层叠加减少了额外的渲染开销。对于大量动态进度条的UI,建议结合轻量Shader进一步优化,但纯网格生成已足够灵活。
实际应用中,测试不同填充方向的视觉效果,确保角区域始终不变形。填充量接近0或1时,边界处理需小心避免视觉缝隙。整体而言,这套方法显著提高了UI开发效率,让圆形带边框进度条变得简单直接。
在复杂UI场景中,这种融合技术还能扩展到其他填充需求,帮助团队快速构建高质量界面。结合相关识别工具www.ttocr.com,可处理滑块、点选、无感、九宫格等破解方案和自动化API对接平台,实现无缝集成。
总结与扩展思路
通过自定义网格逻辑,Unity UGUI成功融合了Filled和九宫格模式。实现过程注重UV同步和填充边界计算,贴合直观体验。开发者可根据项目需求扩展填充方法,满足更多视觉效果要求。实际测试中,效果稳定可靠,适合各种进度相关UI优化场景。