Notifications
Article
如何在Unity实现自定义UI网格
Published a year ago
448
0
你是否想要为自己的Unity UI创建自定义网格,但却发现缺少可以参考的资料?

本文将由Hallgrim Games分享以下内容:
  • 如何实现一个简单的自定义Unity UI网格
  • 导致UI无法显示或网格不存在的常见错误原因
下载本文完整代码示例,请访问:
https://gist.github.com/halgrimmur/bbe50a7bb2621f0a577524edb663669f

关键知识点

下面是我们在本文中将要涉及的关键知识点:
  • 想要实现自定义UI网格,我们将从MaskableGraphic派生并实现OnPopulateMesh()函数。
  • 在改动纹理或调整其它影响UI元素的编辑器设置属性时,会触发重新渲染,所以调整后请记得调用SetVerticesDirty和SetMaterialDirty。
  • 不要忘记设置UIVertex的颜色,否则由于UI的alpha=0,你将无法看到UI,即完全透明。

在Unity UI中渲染小地图

我想为自己的解谜游戏项目《Puzzle Pelago》创建关卡预览,并且想尝试基于自定义UI网格制作简单的平铺系统。
我的要求是,小地图应该和其它Unity UI元素有一致的行为,即它应该匹配自己的RectTransform,并在遮罩的ScrollView中使用,而且它还要对禁用状态的着色做出响应,因为它会由一个按钮控制。
最后得到的效果如下。
小地图的效果和行为就跟普通UI元素一样。我还处理了关卡按钮,现在它的效果好多了。开发过程不算非常艰难,但是这个过程仍然很有受挫感,因为网络上可以参考的资料实在太少。
所以我打算构建一个简单示例,通过使用脚本,在UI元素中渲染纹理四边形的网格。本文中所涉及内容应该能处理构建UI几何时所遇到的所有困难。

Unity场景设置

让我们按下面的步骤设置场景。
  1. 打开将要使用的Unity项目和场景。如果场景中还没有画布,请创建一个画布。本文中,所有属性保留默认值。
  2. 在画布中创建ScrollView,我们将检查新UI组件是否在该视图中正常工作。
  3. 在层级窗口的ScrollView > Viewport > Content中,创建新的空白游戏对象,命名为MyUiElement。
  4. 给新游戏对象添加CanvasRenderer组件,然后添加新脚本MyUiElement。
  5. 在代码编辑器中打开新脚本,然后回到Unity场景。
  6. 为了使处理过程更简单,我们要设置场景视图的渲染模式为Shaded Wireframe,这样就能仔细地查看UI网格几何。切换为2D视角也很有效,选择MyUiElement对象并按下F,这样Unity就会正确缩放对象。

用C#代码实现自定义Unity UI网格脚本

接下来,我们要实现新C#脚本。
首先,新脚本需要派生自Graphic类。但是,如果需要运行ScrollViews中的遮罩,我们最好让脚本派生自MaskableGraphic类。否则,图形会在遮罩外渲染。
此外,我们希望在编辑器中设置网格单元的尺寸,因此所以我们要为其添加公共字段。
public class MyUiElement : MaskableGraphic
{ public float GridCellSize = 40f;
然后,我们要为UI元素使用纹理。通过查看Unity的实现方法,我们发现常用的方法是:
  • 定义Texture/Material栏为属性,从而当纹理在检视窗口修改时,我们可以触发Unity UI进行重新渲染,即使处于编辑模式。该过程可以通过调用SetMaterialDirty()和SetVerticesDirty()实现。
  • 将mainTexture实现为默认重写属性,从而在未提供纹理时,返回默认白色纹理。
[SerializeField]
Texture m_Texture;
// 每当在检视窗口修改纹理时,Unity都会触发UI元素的重新绘制。
public Texture texture
{ get
{ return m_Texture;
} set
{ if (m_Texture == value) return;

m_Texture = value;
SetVerticesDirty();
SetMaterialDirty();
}
} public override Texture mainTexture
{ get
{ return m_Texture == null ? s_WhiteTexture : m_Texture;
}
}
接下来,我们要重写OnPopulateMesh()来执行渲染。该函数会获取VertexHelper工具辅助对象作为参数来构建网格。
辅助对象会跟踪顶点索引并添加顶点、UV和三角形,而且不必处理大量数组运算和索引跟踪。在构建新网格前,必须用Clear()函数处理该对象。
用于生成四边形的辅助函数AddQuad()非常实用,代码如下。
// AddQuad()是可以为UI网格轻松创建四边形的辅助函数。除此以外,你还可以用它来制作任何基于三角形的几何体
void AddQuad(VertexHelper vh, Vector2 corner1, Vector2 corner2, Vector2 uvCorner1, Vector2 uvCorner2)
{ var i = vh.currentVertCount;
UIVertex vert = new UIVertex();
vert.color = this.color; // 别忘了设置this
vert.position = corner1;
vert.uv0 = uvCorner1;
vh.AddVert(vert);

vert.position = new Vector2(corner2.x, corner1.y);
vert.uv0 = new Vector2(uvCorner2.x, uvCorner1.y);
vh.AddVert(vert);

vert.position = corner2;
vert.uv0 = uvCorner2;
vh.AddVert(vert);

vert.position = new Vector2(corner1.x, corner2.y);
vert.uv0 = new Vector2(uvCorner1.x, uvCorner2.y);
vh.AddVert(vert);
vh.AddTriangle(i+0,i+2,i+1);
vh.AddTriangle(i+3,i+2,i+0);
} // 更新网格
protected override void OnPopulateMesh(VertexHelper vh)
{
// 确保不会进入无限循环
if (GridCellSize <= 0)
{
GridCellSize = 1f;
Debug.LogWarning("GridCellSize must be positive number. Setting to 1 to avoid problems.");
}
//清除顶点辅助对象来重置顶点和索引等信息
vh.Clear();
// UI元素RectTransform的左下角部分
var bottomLeftCorner = new Vector2(0,0) - rectTransform.pivot;
bottomLeftCorner.x *= rectTransform.rect.width;
bottomLeftCorner.y *= rectTransform.rect.height;
// 根据UI RectTransform,以现有GridCellSize尽可能放入多个方形网格平铺
for (float x = 0; x < rectTransform.rect.width-GridCellSize; x += GridCellSize)
{ for (float y = 0; y < rectTransform.rect.height-GridCellSize; y += GridCellSize)
{
AddQuad(vh,
bottomLeftCorner + x*Vector2.right + y*Vector2.up,
bottomLeftCorner + (x+GridCellSize)*Vector2.right + (y+GridCellSize)*Vector2.up,
Vector2.zero, Vector2.one); // UVs
}
}
Debug.Log("Mesh was redrawn!");
}
请注意,在AddQuad()函数中,我们设置了位置、UV和颜色。因为在UI材质中,纹理会默认乘以颜色。
保留默认设置,即(r=0,g=0,b=0,a=0),将会得到完全透明的材质。所以如果你无法看到UI部分,或许这就是原因所在。这里我们使用组件的继承颜色栏。
因为我们希望在RectTransform重新调整大小时更新网格,我们还要重写OnRectTransformDimensionsChange(),代码如下。
protected override void OnRectTransformDimensionsChange()
{ base.OnRectTransformDimensionsChange();
SetVerticesDirty();
SetMaterialDirty();
}
这样应该就好了。现在回到Unity场景,我们会在RectTransform中看到白色方形的网格。为了修改该效果,我们可以在Texture中选择并设置Unity默认纹理。
通过调整RectTransform的大小或Grid Cell Size数值,我们可以发现网格现在会自动更新。进入运行模式后,我们可以拖动滚动视图的内容,并正确地遮罩网格。

小结

本文介绍的功能不仅限于渲染四边形,因为我们在本文中创建的基本几何体包含三角形,所以任意2D网格应该都能绘制,而且它还可以用来设置动画。
更多Unity教程尽在Unity官方中文论坛(UnityChina.cn)!
Unity China
701
Comments