Unity uGUI-Graphic の原理解析

Unity uGUI-Graphic の原理解析

UGUI では主に表示部分を担当するのが Graphic ですが、Unity uGUI の原理を分析すると、
UGUI グラフィック関係
おなじみの RawImag、Image、Text が Graphic から継承されていることがわかります

1. 描画原理

では、グラフィックはどのようにしてインターフェイスに画像をレンダリングするのでしょうか? 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);
    }
}
  1. SetAllDirty。OnEnable、Reset、OnDidApplyAnimationProperties、OnValidate、OnTransformParentChanged およびその他のイベントで、パフォーマンスを更新する必要がある場合、SetAllDirty が呼び出され、対応するイベントが送信されます。CanvasUpdateRegistry を再構築するようにマークする
  2. リビルドをトリガーし、リビルド機能を実行します。このとき、Dirtyのロゴに合わせてUpdateGeometryとUpdatematerialが呼び出されます(SetAllDirtyでロゴをtrueにし、ここではfalseにします)。
  3. マテリアルを更新 Updatematerial. (CanvasRenderer に値を直接設定)
  4. 頂点データを更新します。UpdateGeometry。
    1. コードによれば、メッシュ データを生成するために OnPopulateMesh が最初に呼び出されることがわかります。Image、RawImage、Text はすべてこの関数をオーバーライドして、特定の頂点データを生成します。
    2. インターフェースを実装する現在のゲームオブジェクト上のすべてのコンポーネントを取得しIMeshModifierModifyMeshメソッドを呼び出してメッシュデータを変更します
    3. CanvasRenderer の更新データを設定する

2. イベント原理

Graphic コンポーネントはインターフェイス上にコンテンツを表示する役割を担っており、コンテンツが操作されたかどうかを知る必要があります。それ自体を
OnEnable またはその他の方法で登録します。OnDisable などで登録を解除します。GraphicRegistry
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関数で実行されます。

  1. GraphicRaycasterは、GraphicRegistryに登録されているGraphicを取得し、Graphic内のRaycastメソッドを呼び出します。
  2. Graphic によってマウントされたトランスフォーム上のすべてのコンポーネントを取得して、ICanvasRaycastFilter が実装されているかどうかを検出し、ICanvasRaycastFilter を実装するすべてのインターフェイスに対して IsRaycastLocationValid を呼び出します。
  3. IsRaycastLocationValid は、対応する実装に従って検出されます。
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. 実用化

1. 表示内容をカスタマイズする

UI には、色付けやズームなどの操作を実行できる空白の画像が必要な場合があります。ただし、バッチを中断するために画像と画像コンポーネントを追加したくありません。次に、それを実現するためのスクリプトを作成します。イベントに応答する必要があるため、
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;
    }
}

2. 画像を反転します

Flip 画像は Scale を設定することで反転できますが、私たちはそれを使用しません。コードを読むと、メッシュ情報が 2 か所で変更できることがわかります。1 つは OnPopulateMesh 中に対応するデータを直接生成する方法で、もう 1 つは IMeshModifier インターフェイス (開発を容易にするために BaseMeshEffect を継承できる) を実装して、生成されたメッシュ情報を変更する方法です。注: 次のコードはテスト専用であり、考慮されていない状況が多数あります。

OnPopulateMesh の実装

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 の実装

BaseMeshEffect メソッドを使用して、BaseMeshEffect クラスを継承するコンポーネントを、Graphic クラス (つまり、Image、RawImage、Text) をマウントする GameObject にマウントする必要があります。コードは以下のように表示されます。

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

おすすめ

転載: blog.csdn.net/qq_36433883/article/details/125451451