UGUI源码剖析(Image)

Runtime类图分析

UGUI源码剖析(Image)_第1张图片

Image继承了MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter,提供了四种ImageType:Simple(普通)、Sliced(切割)、Tiled(平铺)、Filled(填充)。继承了ILayoutElement是一个布局元素,可以被各种布局组(ILayoutGroup)所包含,将它和其他布局元素进行布局。

 

Editor类为:ImageEditor

 


 

关键方法分析

接口方法

ISerializationCallbackReceiver : 提供了序列化前和反序列化后的处理接口

OnBeforeSerialize:序列化之前调用的方法
OnAfterDeserialize:序列化之后调用的方法

 

ILayoutElement : 布局元素,可以被布局组件(ILayoutController)布局,主要提供跟布局相关的方法

+CalculateLayoutInputHorizontal();
+CalculateLayoutInputVertical();
+minWidth { get; } : float
+preferredWidth { get; } : float
+flexibleWidth { get; } : float
+ minHeight { get; } : float
+ preferredHeight { get; } : float
+flexibleHeight { get; } : float
+ layoutPriority { get; } : float

 

ICanvasRaycastFilter : 判断射线是否可以有效作用于该Image

IsRaycastLocationValid

 

生命周期

OnEnableOnDiable,分别调用了TrackSpriteUnTrackImage(this)方法,

protected override void OnEnable()
{
    base.OnEnable();
    TrackSprite();
}

protected override void OnDisable()
{
    base.OnDisable();

    if (m_Tracked)
    UnTrackImage(this);
}

在调用TrackImage时实际上是在关注Unity的图集部分,注册重建Image的方法:

private static void TrackImage(Image g)
{
    if (!s_Initialized)
    {
        //注册重建Image的方法
        SpriteAtlasManager.atlasRegistered += RebuildImage;
        s_Initialized = true;
    }

    m_TrackedTexturelessImages.Add(g);
}
private static void UnTrackImage(Image g)
{
    m_TrackedTexturelessImages.Remove(g);
}

static void RebuildImage(SpriteAtlas spriteAtlas)
{
    for (var i = m_TrackedTexturelessImages.Count - 1; i >= 0; i--)
    {
        var g = m_TrackedTexturelessImages[i];
        if (spriteAtlas.CanBindTo(g.activeSprite))
        {
            g.SetAllDirty();
            m_TrackedTexturelessImages.RemoveAt(i);
        }
    }
}

 

Mesh部分

作为Grphic簇的组件,关注它的Mesh部分。Image使用了Sprite取代Texture作为存储图像信息的类型,Sprite是Unity专门为2D游戏(通常也用于3D游戏的UI)提供的图像对象。它记录了有关图形的许多信息(texture,uv,triangles,vertices…),通过SpriteRenderer来显示图形。

protected override void OnPopulateMesh(VertexHelper toFill)
{
    if (activeSprite == null)
    {
        base.OnPopulateMesh(toFill);
        return;
    }
	//根据不同的类型进行相应的处理
    switch (type)
    {
        case Type.Simple:
            GenerateSimpleSprite(toFill, m_PreserveAspect);
            break;
        case Type.Sliced:
            GenerateSlicedSprite(toFill);
            break;
        case Type.Tiled:
            GenerateTiledSprite(toFill);
            break;
        case Type.Filled:
            GenerateFilledSprite(toFill, m_PreserveAspect);
            break;
    }
}

里面的几个函数都是在处理顶点和面片来生成网格的部分

 

OnPopulateMesh方法

重写Graphic的OnPopulateMesh方法,会在Graphic的Rebuild方法被调用,为CanvasRenderer的Mesh提供了顶点位置、顶点颜色、UV和三角形信息。根据图片设置的不同类型,生成不同的顶点、顶点颜色、UV和三角形信息。

Simple:

GenerateSimpleSprite方法

  • 根据activeSprite的padding和RectTransform大小,重新计算顶点。
  • 根据activeSprite的外侧UV信息GetOuterUV,设置UV。
  • 把顶点,颜色,UV和三角形信息存入VertexHelper中。

Sliced:

GenerateSlicedSprite方法

  • 生成36个uv点,九宫格里每一个格子对应4个uv点。
  • 生成36个顶点,九宫格里每一个格子对应4个顶点。
  • 中心区域被拉大,四角部分不变。当取消Image上的Fill Center的时候,中间区域变透明,这是因为中间区域顶点、UV等信息都没有。

Tiled:

GenerateTiledSprite方法

如果sprite有边界,那么便会生成跟GenerateSlicedSprite一样的结果,如果没有边界,那么就会在该区域内填充多个sprite,GenerateTiledSprite会计算该区域里可以放下多少个精灵单元(横纵分别向上取整),假设为格子数N,便会有4N个顶点,如果一个小格子可以完整的放下一个精灵单元,uv值便是x从0到1,y从0到1的完整纹理坐标。而如果只能放下一部分,那边根据百分比计算uv值。

Filled:

GenerateFilledSprite方法

区分了不同的填充方法(Horizontal,Vertical,Radial 90,Radial 180,Radial 360)

Horizontal和Vertical填充方法

根据m_FillOrigin(填充的起点)和m_FillAmount(填充的值),设置顶点和UV。

 

Radial系列方法

static void RadialCut(Vector3[] xy, float cos, float sin, bool invert, int corner)

 static bool RadialCut(Vector3[] xy, Vector3[] uv, float fill, bool invert, int corner)

调用RadialCut方法,调整指定的四边形,使其径向填充。然后调用AddQuad方法,设置顶点,颜色和UV。

 

材质

Material

在材质纹理的处理上,仅仅是多了对带有Alpha通道的图片的渲染操作(这个取决于原图片是否采取含有透明通道的压缩方式,若不含有透明通道,则为null)

protected override void UpdateMaterial()
{
    base.UpdateMaterial();

    // check if this sprite has an associated alpha texture (generated when splitting RGBA = RGB + A as two textures without alpha)

    if (activeSprite == null)
    {
        canvasRenderer.SetAlphaTexture(null);
        return;
    }

    Texture2D alphaTex = activeSprite.associatedAlphaSplitTexture;

    if (alphaTex != null)
    {
        canvasRenderer.SetAlphaTexture(alphaTex);
    }
}

 

射线检测

Raycast

Image的射线相关接口,主要通过判断坐标点的像素透明度是否满足接收射线的透明度值来判断是否可以有效响应射线。

alphaHitTestMinimumThreshold: 是可以被动态设置的值,默认值为0,通过该值控制射线响应条件。

将屏幕上的点转换为Image的RectTransform上的局部空间中的位置local,转换为以左下角为参考点的坐标系MapCoordinate,归一化local,然后转换为纹理空间坐标,通过调用Texture2D .GetPixelBilinear取得以标准化坐标,返回的已过滤像素颜色,判断像素颜色的alpha值是否大于等于eventMinimumAlphaThreshold,我们可以外部修改这个值,例如改成0.5f,那么点击到alpha小于0.5f的像素点就无法接收事件。

public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
    if (alphaHitTestMinimumThreshold <= 0)
        return true;
    if (alphaHitTestMinimumThreshold > 1)
        return false;
    if (activeSprite == null)
        return true;
    //计算作用位置坐标
    Vector2 local;
    if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local))
        return false;
    Rect rect = GetPixelAdjustedRect();
    // Convert to have lower left corner as reference point.
    local.x += rectTransform.pivot.x * rect.width;
    local.y += rectTransform.pivot.y * rect.height;
    local = MapCoordinate(local, rect);
    // Normalize local coordinates.
    Rect spriteRect = activeSprite.textureRect;
    Vector2 normalized = new Vector2(local.x / spriteRect.width, local.y / spriteRect.height);
    // Convert to texture space.
    float x = Mathf.Lerp(spriteRect.x, spriteRect.xMax, normalized.x) / activeSprite.texture.width;
    float y = Mathf.Lerp(spriteRect.y, spriteRect.yMax, normalized.y) / activeSprite.texture.height;
    try
    {
        //判断坐标位置的像素透明度是否满足射线接收的最小透明度
        return activeSprite.texture.GetPixelBilinear(x, y).a >= alphaHitTestMinimumThreshold;
    }
    catch (UnityException e)
    {
        Debug.LogError("Using alphaHitTestMinimumThreshold greater than 0 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this);
        return true;
    }
}

 

重写的MaskableGraphic的OnCanvasHierarchyChanged

protected override void OnCanvasHierarchyChanged()
{
    base.OnCanvasHierarchyChanged();
    if (canvas == null)
    {
        m_CachedReferencePixelsPerUnit = 100;
    }
    else if (canvas.referencePixelsPerUnit != m_CachedReferencePixelsPerUnit)
    {
        m_CachedReferencePixelsPerUnit = canvas.referencePixelsPerUnit;
        if (type == Type.Sliced || type == Type.Tiled)
        {
            SetVerticesDirty();
            SetLayoutDirty();
        }
    }
}

处理Hierarchy下的Canvas改变时的刷新,在type为Sliced或者Tiled的时候还需要刷新顶点数据和布局

 

帮助方法

GetAdjustedBorders

获取调整之后的包围盒子,主要多用在跟Graphic的GetPixelAdjustedRect();之后调用

 

AddQuad

添加顶点和面片,主要用在生成网格部分的代码中

 

GetDrawingDimensions

获取用于绘制的图像尺寸. X = left, Y = bottom, Z = right, W = top.

shouldPreserveAspect  是否保持横纵比

 

PreserveSpriteAspectRatio

保持Sprite的纵横比

 

DisableSpriteOptimizations

用在ImageEditor的SpriteGUI方法中,当sprite发生更改时停止对布局和材质的更新

if (m_SkipLayoutUpdate)
{
    m_SkipLayoutUpdate = false;
}
else
{
    SetLayoutDirty();
}

if (m_SkipMaterialUpdate)
{
    m_SkipMaterialUpdate = false;
}
else
{
    SetMaterialDirty();
}

 

SetNativeSize:使图片保持原有大小

public override void SetNativeSize()
{
    if (activeSprite != null)
    {
        float w = activeSprite.rect.width / pixelsPerUnit;
        float h = activeSprite.rect.height / pixelsPerUnit;
        rectTransform.anchorMax = rectTransform.anchorMin;
        rectTransform.sizeDelta = new Vector2(w, h);
        SetAllDirty();
    }
}

 


 

重要属性和字段分析

Sprite

共有三种Sprite:

m_Sprite,m_OverrideSprite,activeSprite,分别为当前的Sprite,为过渡效果准备的覆盖Sprite,  当前激活Sprite,优先返回覆盖Sprite

 

Type

图像显示的类型
        /// Unity可以根据预期目的以不同的方式解释图像。这可用于显示:
        /// - 拉伸整个图像以适应图像的RectTransform变换
        /// - 适用于各种修饰的 UI 框和其他矩形元素的9宫格切片图像
        /// - 重复子画面部分的平铺图像重复子画面部分的平铺图像
        /// - 作为部分图像,可用于擦除、淡入淡出、计时器、状态栏等

 

PreserveAspect

是否保持纵横比

 

FillCenter

是否中心填充

 

FilleMethod

填充模式

Horizontal,
Vertical,
Radial90,
Radial180,
Radial360,

 

SliceFillMethod

九宫裁剪方向

 Horizontal,
 Vertical,

 

FillAmount

填充的具体数值大小

 

FillClockWise

图像填充方式是顺时针(true)还是逆时针(false)

 

FillOrigin

控制填充过程的原点。值表示每个填充方法的不同内容

 

UseSpriteMesh

控制是否使用sprite导入器生成的网格 
        /// 允许您指定是使用纹理导入器生成的网格还是由简单的四边形网格显示UI图像
        /// 当此属性设置为 false 时,UI 图像使用简单的四边形。设置为 true 时,UI 图像使用 [纹理导入者] 生成的Sprite网格。如果要使用基于图像中的 Alpha 值的紧密拟合的Sprite网格,则应将此设置为 true
        /// 注意:如果纹理导入器的 SpriteMeshType 属性设置为 SpriteMeshType.FullRect,它将仅生成四边形,而不是紧密拟合的Sprite网格,这意味着无论此属性的值如何,都将使用四元图像绘制此 UI 图像
        /// 因此,当启用此属性使用紧密拟合的Sprite网格时,您还必须确保纹理导入器的 SpriteMeshType 属性设置为"Tight"

 

defaultETC1GraphicMaterial
/// 缓存默认的ETC1和alpha材质
/// 储存从GetETC1SupportedCanvasMaterial方法返回的画布生成的ETC1材质.
/// 注意:总是在始终包含的shader列表中指定 UI/Default1 Hader,以便使用 ETC1 和 alpha 材质。

 

mainTexture

图像纹理

 

hasBorder

图像的Sprite是否有要处理的边框

 

pixelsPerUnit

每一个单位的像素

 

material

材质


 

參考博客:

https://blog.csdn.net/qq826364410/article/details/88140535

你可能感兴趣的:(UGUI源码剖析,unity)