查看原文
其他

如何在Unity实现自定义UI网格

Unity Unity官方平台 2019-05-07

你是否想要为自己的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轻量级渲染管线LWRP

12月12日 20:00,新一期的直播课程将为大家带来LWRP轻量级渲染管线的详细介绍,帮助大家了解LWRP参数设置,Shader编写以及功能扩展等方面的知识。[了解详情...]

直播地址:

https://connect.unity.com/events/unity_lwrp_intro


Unity订阅惊喜|邀请你参加圣诞派对 与大咖零距离交流

Unity的新订阅用户在获得优惠礼包的同时,我们将邀请活动期间所有的订阅用户参与Unity的圣诞派对,共庆年终圣诞季![了解详情...


Unity全球学生开发挑战赛

Unity面向全球的学生推出-Unity全球学生开发挑战赛,寻找全世界最具创意,展现自我的学生开发者团队。[了解详情...

活动地址:

https://connect.unity.com/challenges/gsc2018


Unity 2D开发挑战赛

参加Unity 2D开发挑战赛,向全世界展示你在Unity 2D方面的创意实践。[了解详情...]

活动地址:

https://connect.unity.com/challenges/2dchallenge


点击“阅读原文”访问Unity官方中文论坛

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存