Análisis principal de Unity uGUI-Graphic

Análisis principal de Unity uGUI-Graphic

Graphic es el principal responsable de la parte de visualización en UGUI.A partir del análisis del principio de Unity uGUI, podemos conocer la siguiente relación:
UGUI Relación gráfica
podemos ver que RawImag, Image y Text se heredan de Graphic.

1. Principio de dibujo

Entonces, ¿cómo representa Graphic las imágenes en la interfaz? Podemos ver el CanvasRenderer en las características del encabezado de la clase.Después de que el gráfico recopila los datos, llama al CanvasRenderer para configurar los vértices, los materiales y otra información que el CanvasRenderer representará.

[RequireComponent(typeof(CanvasRenderer))]

Para mostrar la imagen, debemos configurar el material (Material), la textura (Texture) y la cuadrícula (Mesh) para CanvasRenderer.
Un proceso básico es el siguiente:



/// <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);
    }
}
  1. EstablecerTodoSucio. En OnEnable, Reset, OnDidApplyAnimationProperties, OnValidate, OnTransformParentChanged y otros eventos, cuando sea necesario actualizar el rendimiento, se llamará a SetAllDirty y se enviará el evento correspondiente. Mark CanvasUpdateRegistro para ser reconstruido
  2. Active la reconstrucción y ejecute la función de reconstrucción. En este momento, se llamará a UpdateGeometry y UpdateMaterial de acuerdo con el logotipo de Dirty (establezca el logotipo en verdadero en SetAllDirty y establézcalo en falso aquí).
  3. Actualice el material UpdateMaterial (establezca el valor directamente en CanvasRenderer)
  4. Actualizar datos de vértice UpdateGeometry.
    1. De acuerdo con el código, podemos ver que OnPopulateMesh se llama primero para generar datos de malla. Image, RawImage y Text anulan esta función para generar datos de vértice específicos.
    2. Obtenga todos los componentes del GameObject actual que implementan IMeshModifierla interfaz y llame ModifyMeshal método para modificar los datos de la malla.
    3. Establecer datos actualizados para CanvasRenderer

2. Principio de evento

El componente Gráfico es responsable de mostrar contenido en la interfaz y necesita saber si ha sido manipulado. Regístrese
en OnEnable o de otra manera . Darse de baja en OnDisable o de lo contrario .GraphicRegistry
GraphicRegistry

Mirando el código fuente de GraphicRegistry, podemos saber que este es un singleton, y las funciones implementadas en él también son muy simples. Eso es registrarse, darse de baja y obtener.

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;
    }
}

Después de registrar GraphicRegistry a través de Graphic, la operación de adquisición se realizará en la función Raycast en GraphicRaycaster.

  1. GraphicRaycaster obtiene el Gráfico registrado en GraphicRegistry y llama al método Raycast en Gráfico.
  2. Obtenga todos los componentes en la transformación montada por Graphic para detectar si ICanvasRaycastFilter está implementado y llame a IsRaycastLocationValid para todas las interfaces que implementan ICanvasRaycastFilter
  3. IsRaycastLocationValid se detecta según la implementación correspondiente.
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();
	}
}

3. Aplicación práctica

1. Personaliza el contenido de la pantalla

En la interfaz de usuario, es posible que deseemos tener una imagen en blanco que nos permita realizar operaciones como colorear y hacer zoom. Pero no queremos agregar una imagen y un componente Imagen para interrumpir el lote. Entonces podemos escribir un guión para lograrlo. Debido a que desea responder a los eventos, también debe implementar
IsRaycastLocationValidel método. el código se muestra a continuación

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;
    }
}

2. Voltear la imagen

La imagen de volteo se puede voltear configurando Escala, pero simplemente no la usamos. Al leer el código, podemos saber que la información de Mesh se puede modificar en dos lugares. Una es generar directamente los datos correspondientes durante OnPopulateMesh y la otra es implementar la interfaz IMeshModifier (que puede heredar BaseMeshEffect para un desarrollo conveniente) para modificar la información de malla generada. Nota: El siguiente código es solo para pruebas y hay muchas situaciones que no se consideran.

Implementación de OnPopulateMesh

Para modificar el método OnPopulateMesh, podemos implementarlo heredando la clase correspondiente y reescribiendo el método virtual. El código de referencia es el siguiente:

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);
            }
        }
    }
}

Implementación de BaseMeshEffect

A través del método BaseMeshEffect, necesitamos montar los componentes que heredan la clase BaseMeshEffect en el GameObject que monta la clase Graphic (es decir, Image, RawImage, Text). el código se muestra a continuación:

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);
            }
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_36433883/article/details/125451451
Recomendado
Clasificación