赞
踩
Graphic 在 UGUI 中主要负责显示部分,从Unity uGUI原理解析中我们可以知道如下关系:
可以看到我们熟悉的RawImag、Image和Text正是继承自Graphic。
那么 Graphic 是如何渲染图像到界面上的呢? 我们可以在类头部的特性看到CanvasRenderer,Graphic 收集完数据后调用 CanvasRenderer 设置顶点、材质等信息由 CanvasRenderer 来进行渲染。
[RequireComponent(typeof(CanvasRenderer))]
为了将图像显示出来我们需要给CanvasRenderer设置 材质(Material)、贴图(Texture)、网格(Mesh)。
一个基本流程如下面的代码:
/// <summary> /// Base class for all UI components that should be derived from when creating new Graphic types. /// </summary> [DisallowMultipleComponent] [RequireComponent(typeof(CanvasRenderer))] [RequireComponent(typeof(RectTransform))] [ExecuteAlways] public abstract class Graphic : UIBehaviour, ICanvasElement { //... /// <summary> /// Set all properties of the Graphic dirty and needing rebuilt. /// Dirties Layout, Vertices, and Materials. /// </summary> public virtual void SetAllDirty() { SetLayoutDirty(); // 忽略掉各种判断代码 SetMaterialDirty(); // 忽略掉各种判断代码 SetVerticesDirty(); } /// <summary> /// Mark the layout as dirty and needing rebuilt. /// </summary> /// <remarks> /// Send a OnDirtyLayoutCallback notification if any elements are registered. See RegisterDirtyLayoutCallback /// </remarks> public virtual void SetLayoutDirty() { if (!IsActive()) return; LayoutRebuilder.MarkLayoutForRebuild(rectTransform); if (m_OnDirtyLayoutCallback != null) m_OnDirtyLayoutCallback(); } /// <summary> /// Mark the vertices as dirty and needing rebuilt. /// </summary> /// <remarks> /// Send a OnDirtyVertsCallback notification if any elements are registered. See RegisterDirtyVerticesCallback /// </remarks> public virtual void SetVerticesDirty() { if (!IsActive()) return; m_VertsDirty = true; CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); if (m_OnDirtyVertsCallback != null) m_OnDirtyVertsCallback(); } /// <summary> /// Mark the material as dirty and needing rebuilt. /// </summary> /// <remarks> /// Send a OnDirtyMaterialCallback notification if any elements are registered. See RegisterDirtyMaterialCallback /// </remarks> public virtual void SetMaterialDirty() { if (!IsActive()) return; m_MaterialDirty = true; CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild(this); if (m_OnDirtyMaterialCallback != null) m_OnDirtyMaterialCallback(); } /// <summary> /// Rebuilds the graphic geometry and its material on the PreRender cycle. /// </summary> /// <param name="update">The current step of the rendering CanvasUpdate cycle.</param> /// <remarks> /// See CanvasUpdateRegistry for more details on the canvas update cycle. /// </remarks> public virtual void Rebuild(CanvasUpdate update) { if (canvasRenderer == null || canvasRenderer.cull) return; switch (update) { case CanvasUpdate.PreRender: if (m_VertsDirty) { UpdateGeometry(); m_VertsDirty = false; } if (m_MaterialDirty) { UpdateMaterial(); m_MaterialDirty = false; } break; } } /// <summary> /// Call to update the Material of the graphic onto the CanvasRenderer. /// </summary> protected virtual void UpdateMaterial() { if (!IsActive()) return; canvasRenderer.materialCount = 1; canvasRenderer.SetMaterial(materialForRendering, 0); canvasRenderer.SetTexture(mainTexture); } /// <summary> /// Call to update the geometry of the Graphic onto the CanvasRenderer. /// </summary> protected virtual void UpdateGeometry() { //... DoMeshGeneration(); } private void DoMeshGeneration() { //... OnPopulateMesh(s_VertexHelper); var components = ListPool<Component>.Get(); GetComponents(typeof(IMeshModifier), components); for (var i = 0; i < components.Count; i++) ((IMeshModifier)components[i]).ModifyMesh(s_VertexHelper); canvasRenderer.SetMesh(workerMesh); } /// <summary> /// Callback function when a UI element needs to generate vertices. Fills the vertex buffer data. /// </summary> /// <param name="vh">VertexHelper utility.</param> /// <remarks> /// Used by Text, UI.Image, and RawImage for example to generate vertices specific to their use case. /// </remarks> protected virtual void OnPopulateMesh(VertexHelper vh) { var r = GetPixelAdjustedRect(); var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height); Color32 color32 = color; vh.Clear(); vh.AddVert(new Vector3(v.x, v.y), color32, new Vector2(0f, 0f)); vh.AddVert(new Vector3(v.x, v.w), color32, new Vector2(0f, 1f)); vh.AddVert(new Vector3(v.z, v.w), color32, new Vector2(1f, 1f)); vh.AddVert(new Vector3(v.z, v.y), color32, new Vector2(1f, 0f)); vh.AddTriangle(0, 1, 2); vh.AddTriangle(2, 3, 0); } }
IMeshModifier
接口的组件并调用 ModifyMesh
方法来修改Mesh数据Graphic 组件负责在界面上的展示内容,需要知道自己是否被操作了。
在 OnEnable 或其他时候在 GraphicRegistry
中注册自身。
在 OnDisable 或其他时候在 GraphicRegistry
中取消注册。
查阅 GraphicRegistry 源码我们可以知道这是一个单例, 里面实现的功能也很简单。 就是注册、取消注册以及获取。
public class GraphicRegistry { private static GraphicRegistry s_Instance; //... public static GraphicRegistry instance { get { if (s_Instance == null) s_Instance = new GraphicRegistry(); return s_Instance; } } // 注册Graphic public static void RegisterGraphicForCanvas(Canvas c, Graphic graphic) { //... graphics.Add(graphic); instance.m_Graphics.Add(c, graphics); } public static void UnregisterGraphicForCanvas(Canvas c, Graphic graphic) { //... graphics.Remove(graphic); if (graphics.Count == 0) instance.m_Graphics.Remove(c); } public static IList<Graphic> GetGraphicsForCanvas(Canvas canvas) { //... IndexedSet<Graphic> graphics; if (instance.m_Graphics.TryGetValue(canvas, out graphics)) return graphics; } }
通过 Graphic 给 GraphicRegistry 注册完成后,在 GraphicRaycaster 中的 Raycast函数中会进行获取的操作。
public class GraphicRaycaster : BaseRaycaster { private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results) { // Necessary for the event system int totalCount = foundGraphics.Count; // 这个就是从 GraphicRegistry 中获取的Graphics for (int i = 0; i < totalCount; ++i) { Graphic graphic = foundGraphics[i]; // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1) continue; if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera)) continue; if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane) continue; if (graphic.Raycast(pointerPosition, eventCamera)) { s_SortedGraphics.Add(graphic); } } s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth)); totalCount = s_SortedGraphics.Count; for (int i = 0; i < totalCount; ++i) results.Add(s_SortedGraphics[i]); s_SortedGraphics.Clear(); } }
在UI上我们可能希望有个空白图片能让我们进行上色缩放等操作。 但是我们又不想要添加一个图片以及Image组件打断合批。那么我们可以编写一下脚本来实现。因为要响应事件所以还需要实现
IsRaycastLocationValid
方法。代码如下
public class EmptyImg : MaskableGraphic, // 继承它用来显示图像 ICanvasRaycastFilter // 实现这个接口来接收事件 { protected override void Awake() { base.Awake(); color = new Color(1.0f, 1.0f, 1.0f, 0.0f); } protected override void OnPopulateMesh(VertexHelper toFill) { toFill.Clear(); } public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) { return true; } }
翻转图像可以通过设置 Scale 的方式来实现翻转,但是我们偏偏就是不用。 通过对代码的阅读我们可以知道Mesh信息在两个地方可以进行修改。 一个是在 OnPopulateMesh 的时候直接生成对应的数据, 另外一个是实现IMeshModifier接口(可以继承 BaseMeshEffect 方便开发)来修改生成的Mesh信息。 注意:下面代码只是测试用,有很多情况不进行考虑。
修改 OnPopulateMesh 方法我们可以通过继承对应的类并重写虚方法来实现。 参考代码如下:
public class FlipImage : Image { public bool FlipHorizontal { get { return flipHor;} set { flipHor = value; UpdateGeometry(); } } public bool FlipVertical { get { return flipVer; } set { flipVer = value; UpdateGeometry(); } } [SerializeField] protected bool flipHor; [SerializeField] protected bool flipVer; protected override void OnPopulateMesh(VertexHelper toFill) { base.OnPopulateMesh(toFill); if (flipHor || flipVer) { Vector2 rectCenter = rectTransform.rect.center; int vertCount = toFill.currentVertCount; for (int i = 0; i < vertCount; i++) { UIVertex uiVertex = new UIVertex(); toFill.PopulateUIVertex(ref uiVertex, i); Vector3 pos = uiVertex.position; uiVertex.position = new Vector3( flipHor ? (pos.x + (rectCenter.x - pos.x) * 2) : pos.x, flipVer ? (pos.y + (rectCenter.y - pos.y) * 2) : pos.y, pos.z); toFill.SetUIVertex(uiVertex, i); } } } }
通过 BaseMeshEffect 方法实现我们需要在挂载了 Graphic 类(也就是Image、RawImage、Text)的GameObject上挂载继承BaseMeshEffect这个类的组件。 代码如下:
public class FlipImgMeshEffect : BaseMeshEffect { public bool FilpHorizontal { get { return flipHor; } set { flipHor = value; graphic?.SetAllDirty(); } } public bool FlipVertical { get { return flipVer; } set { flipVer = value; graphic?.SetAllDirty(); } } [SerializeField] protected bool flipHor; [SerializeField] protected bool flipVer; readonly List<UIVertex> stream = new List<UIVertex>(); public override void ModifyMesh(VertexHelper vh) { vh.GetUIVertexStream(stream); if (flipHor || flipVer) { Vector2 rectCenter = graphic.rectTransform.rect.center; for (int i = 0; i < vh.currentVertCount; i++) { UIVertex uiVertex = new UIVertex(); vh.PopulateUIVertex(ref uiVertex, i); Vector3 pos = uiVertex.position; uiVertex.position = new Vector3( flipHor ? (pos.x + (rectCenter.x - pos.x) * 2) : pos.x, flipVer ? (pos.y + (rectCenter.y - pos.y) * 2) : pos.y, pos.z); vh.SetUIVertex(uiVertex, i); } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。