Sprite是以Texture2d为基础的,所以我们应该首先获取Sprite的texture2d对象,合并,然后生成新的Sprite对象。

可以用画画的方式一层一层的混合,Unity3d新建的Texture2d对象并不是一张RGB为0的图,而是0.8的灰色缓冲区,所以我们需要清空这个颜色,只需要用最底层的texture去填充覆盖,上面的texture在绘制即可。

分别为两个指令,Fill和Draw。

public static void Fill(Texture2D src, Texture2D brush)
{
    src.SetPixels(brush.GetPixels());
}

Draw绘制函数

public static void Draw(Texture2D src, Texture2D brush, Vector2Int pos)
{
    int width = brush.width;
    int height = brush.height;

    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            var destColor = brush.GetPixel(i, j);
            var srcColor = src.GetPixel(pos.x + i, pos.y + j);
            if (srcColor.a != 0)
            {
                float alpha = srcColor.a + destColor.a;
                destColor = Color.Lerp(srcColor, destColor, destColor.a);
                if (alpha >= 1)
                {
                    destColor.a = 1;
                }
            }
            src.SetPixel(pos.x + i, pos.y + j, destColor);
        }
    }
}

该函数对旧颜色进行判断,如果旧颜色的的透明度不为0,则使用新颜色的透明度对两个颜色插值,这是为了处理两层交界边缘的半透明区域。但是Lerp同时也会插值alpha,会在旧不透明与新半透明时混合错误,所以这里做了一个判断,如果两个颜色透明度相加大于等于1,那就将Lerp插值错误的alpha补偿回来。

现在已经可以合并多个Texture了,例:

Texture2D t1 = sprite1.texture as Texture2D;
Texture2D t2 = sprite2.texutre as Texture2D;
Texture2D tex = new Texture2D(t1.width, t1.height, t1.format, false);
Fill(tex, t1);
Draw(tex, t2, Vector2Int.zero);
tex.Apply();

到这里我们已经可以手动设置多张texture合成了。

public static Texture2D ComposeNew(Texture2D[] objs, Vector2Int[] offset)
{
    Texture2D t1 = objs[0];
    Texture2D tex = new Texture2D(t1.width, t1.height, t1.format, false);
    Fill(tex, t1);
    for (int i = 1; i < objs.Length; i++)
    {
        Draw(tex, objs[i], offset[i]);
    }
    tex.Apply();
    return tex;
}

但是对于sprite来说还需要计算它们的偏移。

在ugui中的sprite以Image显示,而在ugui的Canvas中一个单位即一个像素。在绘制的过程中我们要注意一点:Unity的颜色缓冲信息是从图像的左下角开始的,所以我们要算出Image在Canvas中的相对位置以及和填充层的相对位置。

一般情况下ugui的控件中心点pivot为(0.5,0.5),使用fillRect.sizeDelta * fillRect.pivot就可以求出填充层的中心位置,但是从填充层中心开始画就错了,因为是从左下角开始画的,所以这里还需要减掉需要绘制图像的一半:

fillRect.sizeDelta * fillRect.pivot – transRect.sizeDelta / 2

然后我们加上两个对象在Canvas上的相对距离,首先编写控件获取在Canvas上的位置:

private static Vector2 TransformToCanvasLocalPosition(Transform current, Canvas canvas)
{
    var screenPos = canvas.worldCamera.WorldToScreenPoint(current.transform.position);
    Vector2 localPos;
    RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.GetComponent<RectTransform>(), screenPos,
        canvas.worldCamera, out localPos);
    return localPos;
}

最后将上述表达式整合,编写Sprite合成函数:

public static Sprite ComposeNewSpriteFromUImage(Transform[] transforms, Canvas canvas)
{
    Texture2D[] texArr = new Texture2D[transforms.Length];
    Vector2Int[] offsetArr = new Vector2Int[transforms.Length];
    var first = transforms[0];
    for (int i = 0; i < transforms.Length; i++)
    {
        var transform = transforms[i];
        texArr[i] = transform.GetComponent<Image>().mainTexture as Texture2D;
        if (i == 0)
        {
            offsetArr[i] = new Vector2Int(0, 0);
        }
        else
        {
            var fillRect = first.GetComponent<RectTransform>();
            var transRect = transform.GetComponent<RectTransform>();

            var offset = fillRect.sizeDelta * fillRect.pivot - transRect.sizeDelta / 2;
            offset += TransformToCanvasLocalPosition(transform, canvas) - TransformToCanvasLocalPosition(first, canvas);

            offsetArr[i] = new Vector2Int((int)offset.x, (int)offset.y);
        }
    }
    var tex = ComposeNew(texArr, offsetArr);
    return Texture2DUtility.Texture2dToSprite(tex);
}