前言

上半年的大半年时间都在用FGUI,最近又用回了UGUI,发现FGUI的一些工具还是很高效的,比如对齐工具,想着说移植到UGUI里面去,上网搜了一圈还真有,原理并不复杂,就是进行一些数学计算出最终的位置。

不过也没有完全适合项目需求,针对原项目基础上添加了三个功能,下面介绍一下。

一、撤销功能

原项目的工具在修改后,习惯性就按下Ctrl+Z,发现并不能撤销原来的操作,查询了Unity的API后,发现有个Undo的类,可以实现撤销功能,有很多方法,主要用到了RecordObject,代码如下

private static void SetTranPos(Transform tran, Vector3 pos)
{
    Undo.RecordObject(tran, "modify posstion");
    tran.position = pos;
}

二、Selection.GameObjects不是按选择顺序的

API中的Selection.GameObjects用于获取当前选中的物件,数组的顺序和我选择的顺序无关,所以不能实现灵活的对齐,查询网上文章可以用Selection.Objects替代,里面的第一个元素就是Ctrl选中的第一个元素,以此类推,代码如下

//按选中顺序获取GameObjects
private static GameObject[] GetOrderedSelctionObjs()
{
    return Selection.objects.OfType<GameObject>().ToArray();
}

三、添加对父节点对齐

功能就是和标题一样,原项目是没有的,代码如下

//如果只选中一个,对父节点对齐
if (rects.Count == 1)
{
    if (rects[0].parent != null && rects[0].parent.GetComponent<RectTransform>() != null)
    {
        rects.Insert(0, rects[0].parent.GetComponent<RectTransform>());
    }
}

四、代码

UGUIAlign

using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;

public enum AlignType
{
    Top = 1,
    Left = 2,
    Right = 3,
    Bottom = 4,
    HorizontalCenter = 5,       //水平居中
    VerticalCenter = 6,         //垂直居中
    Horizontal = 7,             //横向分布
    Vertical = 8,               //纵向分布
}

public class UGUIAlign : Editor
{
    [MenuItem("GameObject/UI/Align/Left 【左对齐】")]
    static void AlignLeft()
    {
        Align(AlignType.Left);
    }
    [MenuItem("GameObject/UI/Align/HorizontalCenter 【水平居中】")]
    static void AlignHorizontalCenter()
    {
        Align(AlignType.HorizontalCenter);
    }
    [MenuItem("GameObject/UI/Align/Right 【右对齐】")]
    static void AlignRight()
    {
        Align(AlignType.Right);
    }
    [MenuItem("GameObject/UI/Align/Top 【顶端对齐】")]
    static void AlignTop()
    {
        Align(AlignType.Top);
    }
    [MenuItem("GameObject/UI/Align/VerticalCenter 【垂直居中】")]
    static void AlignVerticalCenter()
    {
        Align(AlignType.VerticalCenter);
    }
    [MenuItem("GameObject/UI/Align/Bottom 【底端对齐】")]
    static void AlignBottom()
    {
        Align(AlignType.Bottom);
    }
    [MenuItem("GameObject/UI/Align/Horizontal 【横向分布】")]
    static void AlignHorizontal()
    {
        Align(AlignType.Horizontal);
    }
    [MenuItem("GameObject/UI/Align/Vertical 【纵向分布】")]
    static void AlignVertical()
    {
        Align(AlignType.Vertical);
    }

    public static void Align(AlignType type)
    {
        List<RectTransform> rects = new List<RectTransform>();
        GameObject[] objects = GetOrderedSelctionObjs();
        if (objects != null && objects.Length > 0)
        {
            for (int i = 0; i < objects.Length; i++)
            {
                RectTransform rect = objects[i].GetComponent<RectTransform>();
                if (rect != null)
                    rects.Add(rect);
            }
        }

        //如果只选中一个,对父节点对齐
        if (rects.Count == 1)
        {
            if (rects[0].parent != null && rects[0].parent.GetComponent<RectTransform>() != null)
            {
                rects.Insert(0, rects[0].parent.GetComponent<RectTransform>());
            }
        }

        if (rects.Count > 1)
        {
            Align(type, rects);
        }
    }

    //按选中顺序获取GameObjects
    private static GameObject[] GetOrderedSelctionObjs()
    {
        return Selection.objects.OfType<GameObject>().ToArray();
    }

    public static void Align(AlignType type, List<RectTransform> rects)
    {
        RectTransform tenplate = rects[0];
        float w = tenplate.sizeDelta.x * tenplate.lossyScale.x;
        float h = tenplate.sizeDelta.y * tenplate.lossyScale.y;

        float x = tenplate.position.x - tenplate.pivot.x * w;
        float y = tenplate.position.y - tenplate.pivot.y * h;

        switch (type)
        {
            case AlignType.Top:
                for (int i = 1; i < rects.Count; i++)
                {
                    RectTransform trans = rects[i];
                    float th = trans.sizeDelta.y * trans.lossyScale.y;
                    Vector3 pos = trans.position;
                    pos.y = y + h - th + trans.pivot.y * th;
                    SetTranPos(trans, pos);
                }
                break;
            case AlignType.Left:
                for (int i = 1; i < rects.Count; i++)
                {
                    RectTransform trans = rects[i];
                    float tw = trans.sizeDelta.x * trans.lossyScale.x;
                    Vector3 pos = trans.position;
                    pos.x = x + tw * trans.pivot.x;
                    SetTranPos(trans, pos);
                }
                break;
            case AlignType.Right:
                for (int i = 1; i < rects.Count; i++)
                {
                    RectTransform trans = rects[i];
                    float tw = trans.sizeDelta.x * trans.lossyScale.x;
                    Vector3 pos = trans.position;
                    pos.x = x + w - tw + tw * trans.pivot.x;
                    SetTranPos(trans, pos);
                }
                break;
            case AlignType.Bottom:
                for (int i = 1; i < rects.Count; i++)
                {
                    RectTransform trans = rects[i];
                    float th = trans.sizeDelta.y * trans.lossyScale.y;
                    Vector3 pos = trans.position;
                    pos.y = y + th * trans.pivot.y;
                    SetTranPos(trans, pos);
                }
                break;
            case AlignType.HorizontalCenter:
                for (int i = 1; i < rects.Count; i++)
                {
                    RectTransform trans = rects[i];
                    float tw = trans.sizeDelta.x * trans.lossyScale.x;
                    Vector3 pos = trans.position;
                    pos.x = x + 0.5f * w - 0.5f * tw + tw * trans.pivot.x;
                    SetTranPos(trans, pos);
                }
                break;
            case AlignType.VerticalCenter:
                for (int i = 1; i < rects.Count; i++)
                {
                    RectTransform trans = rects[i];
                    float th = trans.sizeDelta.y * trans.lossyScale.y;
                    Vector3 pos = trans.position;
                    pos.y = y + 0.5f * h - 0.5f * th + th * trans.pivot.y;
                    SetTranPos(trans, pos);
                }
                break;
            case AlignType.Horizontal:
                float minX = GetMinX(rects);
                float maxX = GetMaxX(rects);
                rects.Sort(SortListRectTransformByX);
                float distance = (maxX - minX) / (rects.Count - 1);
                for (int i = 1; i < rects.Count - 1; i++)
                {
                    RectTransform trans = rects[i];
                    Vector3 pos = trans.position;
                    pos.x = minX + i * distance;
                    SetTranPos(trans, pos);
                }
                break;
            case AlignType.Vertical:
                float minY = GetMinY(rects);
                float maxY = GetMaxY(rects);
                rects.Sort(SortListRectTransformByY);
                float distanceY = (maxY - minY) / (rects.Count - 1);
                for (int i = 1; i < rects.Count - 1; i++)
                {
                    RectTransform trans = rects[i];
                    Vector3 pos = trans.position;
                    pos.y = minY + i * distanceY;
                    SetTranPos(trans, pos);
                }
                break;
        }
    }

    private static void SetTranPos(Transform tran, Vector3 pos)
    {
        Undo.RecordObject(tran, "modify posstion");
        tran.position = pos;
    }

    private static int SortListRectTransformByX(RectTransform r1, RectTransform r2)
    {
        float w = r1.sizeDelta.x * r1.lossyScale.x;
        float x1 = r1.position.x - r1.pivot.x * w;
        w = r2.sizeDelta.x * r2.lossyScale.x;
        float x2 = r2.position.x - r2.pivot.x * w;
        if (x1 >= x2)
            return 1;
        else
            return -1;
    }

    private static int SortListRectTransformByY(RectTransform r1, RectTransform r2)
    {
        float w = r1.sizeDelta.y * r1.lossyScale.y;
        float y1 = r1.position.y - r1.pivot.y * w;
        w = r2.sizeDelta.y * r2.lossyScale.y;
        float y2 = r2.position.y - r2.pivot.y * w;
        if (y1 >= y2)
            return 1;
        else
            return -1;
    }

    private static float GetMinX(List<RectTransform> rects)
    {
        if (null == rects || rects.Count == 0)
            return 0;
        RectTransform tenplate = rects[0];
        float minx = tenplate.position.x;
        float tempX = 0;
        for (int i = 1; i < rects.Count; i++)
        {
            tempX = rects[i].position.x;
            if (tempX < minx)
                minx = tempX;
        }
        return minx;
    }

    private static float GetMaxX(List<RectTransform> rects)
    {
        if (null == rects || rects.Count == 0)
            return 0;
        RectTransform tenplate = rects[0];
        float maxX = tenplate.position.x;
        float tempX = 0;
        for (int i = 1; i < rects.Count; i++)
        {
            tempX = rects[i].position.x;
            if (tempX > maxX)
                maxX = tempX;
        }
        return maxX;
    }

    private static float GetMinY(List<RectTransform> rects)
    {
        if (null == rects || rects.Count == 0)
            return 0;
        RectTransform tenplate = rects[0];
        float minY = tenplate.position.y;
        float tempX = 0;
        for (int i = 1; i < rects.Count; i++)
        {
            tempX = rects[i].position.y;
            if (tempX < minY)
                minY = tempX;
        }
        return minY;
    }

    private static float GetMaxY(List<RectTransform> rects)
    {
        if (null == rects || rects.Count == 0)
            return 0;
        RectTransform tenplate = rects[0];
        float maxY = tenplate.position.y;
        float tempX = 0;
        for (int i = 1; i < rects.Count; i++)
        {
            tempX = rects[i].position.y;
            if (tempX > maxY)
                maxY = tempX;
        }
        return maxY;
    }
}

五、代码下载

https://github.com/dandkong/UGUIAlign

六、参考

Unity编辑器拓展之十:UI对齐工具