美文网首页
简易UGUI插件的开发教程

简易UGUI插件的开发教程

作者: 羿升君 | 来源:发表于2019-06-12 15:43 被阅读0次

    目前ugui的功能已经十分强大了,大多数的ui需求都可以通过预定义控件的组合,配合脚本来实现,相信大部分开发者在处理这类需求时,都会按以上方式来制作prefab,这当然是没问题的,不过还有没有更高效简洁的方式呢?
    目前ugui预定义的ui控件种类较少,你应该会想过编写自定义的控件来进行拓展,这篇文章会实现一个简单的coverflow效果控件,将其添加到UI的可选控件列表中,并且可通过asset文件配置其参数。项目源码已上传github,链接在文章最后附上。

    扩展完成效果.png coverflow效果.gif

    在UI列表加入自定义控件选项

    要自定义控件添加到unity编辑器,控件生成类需要using UnityEditor,来使用编辑器状态下的类型。
    在编写的自定义控件生成类前添加AddComponentMenu("GameObject/UI/CoverFlow")特性,使该控件可以在顶部工具栏中显示

    工具栏扩展效果.png

    在控件生成方法前添加[MenuItem("GameObject/UI/CoverFlow")]特性,使其出现在Hierachy下的右键列表里

    扩展完成效果.png

    实现控件生成方法,点击菜单选项创建控件

    控件生成方法必须是静态方法public static void CreateCoverFlow(MenuCommand menuCommand)MenuCommand对象可以捕获菜单点击时的上下文信息,用来确定自定义控件的父对象等,通过AddComponent方法为对象添加组件,最终组合为你希望呈现的效果。
    如下coverflow效果,首先以当前右键点击对象为父对象一个coverflow根对象,为其添加CoverFlowBehavior行为类(该类也需要自行编写,具体实现在下方给出)作为控制器,并设置相关参数。
    在for循环中添加coverflow的子对象,数量尺寸间距等参数由CoverFlowBehavior指定,为其添加RawImageCoverFlowImageBehavior等组件。
    这样一个简单的coverflow效果就完成了,并可以通过右键点击Canvas=>UI=>CoverFlow进行添加了。

        //CoverFlow根对象
        GameObject coverflow = new GameObject("CoverFlow");
        RectTransform coverflowRTrs = coverflow.AddComponent<RectTransform>();
    
        Transform parent = (menuCommand.context as GameObject).transform;
        coverflow.transform.SetParent(parent, false)
    
        CoverFlowBehavior coverFlowBehavior = coverflow.AddComponent<CoverFlowBehavior>();
        coverFlowBehavior.imageHeight = coverFlowParameters.imageSizeDelta.y;
    
        //CoverFlow图片子对象
        for (int i = 0; i < coverFlowParameters.imageCount; i++)
        {
            GameObject image = new GameObject("Image" + i);
            RectTransform imageRTrs = image.AddComponent<RectTransform>();
            image.transform.SetParent(coverflowRTrs, false);
            imageRTrs.sizeDelta = coverFlowParameters.imageSizeDelta;
    
            image.AddComponent<RawImage>();
            AspectRatioFitter aspectRatioFitter = image.AddComponent<AspectRatioFitter>();
            aspectRatioFitter.aspectMode = AspectRatioFitter.AspectMode.HeightControlsWidth;
    
            CoverFlowImageBehavior coverFlowImageBehavior = image.AddComponent<CoverFlowImageBehavior>();
            coverFlowImageBehavior.coverFlowIndex = i - coverFlowParameters.imageCount / 2;
        }
    

    CoverFlowBehavior和CoverFlowImageBehavior的实现

    具体实现就不在本篇文章展开讲了,下面把源码附上,逻辑并不复杂

    public class CoverFlowBehavior : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
    {
    
    [Tooltip("CoverFlow的图片列表")]
    public List<Texture2D> t2ds;
    [Tooltip("图片间距的基础值")]
    public float imageGap = 100;
    [Tooltip("图片基础尺寸")]
    public float imageHeight = 300;
    [Tooltip("起始图片的索引")]
    
    public int initIndex = 0;
    
    public event SetImageDisPlay setImageDisPlay;  //发布刷新图片显示效果事件
    public event SetImageIndex setImageIndex;  //发布刷新图片索引事件
    
    [HideInInspector]
    public int childCount;
    float dragBeginPosX;
    
    private void Awake()
    {
        childCount = transform.childCount;
    
    }
    
    
    void Start()
    {
        InitIndex(initIndex);
    }
    
    public void OnBeginDrag(PointerEventData data)
    {
        dragBeginPosX = data.position.x;
    }
    public void OnDrag(PointerEventData data)
    {
    
    }
    public void OnEndDrag(PointerEventData data)
    {
        if (data.position.x - dragBeginPosX > 10)
            RefreshImageIndex(1);
        else if (data.position.x - dragBeginPosX < -10)
            RefreshImageIndex(-1);
    
        setImageDisPlay();
    }
    
    void RefreshImageIndex(int direction)
    {
        setImageIndex(direction);
    }
    
    /// <summary>
    /// 初始化coverflow的显示位置
    /// </summary>
    /// <param name="index">起始索引</param>
    public void InitIndex(int index)
    {
        if (t2ds.Count == 0)
            throw new System.Exception("Coverflow need at least one image, please assign image first!");
    
        for (int i = 0; i < childCount; i++)
        {
            transform.GetChild(i).GetComponent<CoverFlowImageBehavior>().t2dIndex = index;
            if (++index > t2ds.Count - 1)
                index = 0;
        }
    }
    
    
    public delegate void SetImageIndex(int direction);
    public delegate void SetImageDisPlay();
    }
    
    
    public class CoverFlowImageBehavior : MonoBehaviour, IPointerClickHandler
    {
    public int coverFlowIndex;
    public int t2dIndex;
    
    RectTransform rTrs;
    RawImage rawImage;
    AspectRatioFitter aspectRatioFitter;
    
    CoverFlowBehavior coverFlowBehavior;
    
    public UnityEvent unityEvent;
    
    private void Awake()
    {
        rTrs = GetComponent<RectTransform>();
        rawImage = GetComponent<RawImage>();
        aspectRatioFitter = GetComponent<AspectRatioFitter>();
    
        coverFlowBehavior = transform.parent.GetComponent<CoverFlowBehavior>();
        coverFlowBehavior.setImageIndex += SetIndex;   //订阅刷新图片索引事件
        coverFlowBehavior.setImageDisPlay += SetImageDisplay;  //订阅刷新图片显示效果事件
    }
    
    
    void Start()
    {
        SetImageDisplay();
    }
    
    
    public void OnPointerClick(PointerEventData data)
    {
        if (coverFlowIndex == 0)
        {
            unityEvent.Invoke();
        }
    }
    
    /// <summary>
    /// 设置CoverFlow中的位置索引和图片的索引
    /// </summary>
    /// <param name="direction"></param>
    public void SetIndex(int direction)
    {
        coverFlowIndex += direction;
        if (coverFlowIndex > coverFlowBehavior.childCount / 2)
        {
            coverFlowIndex = -coverFlowBehavior.childCount / 2;
            t2dIndex -= coverFlowBehavior.childCount;
            while(t2dIndex < 0)
                t2dIndex += coverFlowBehavior.t2ds.Count;
        }
        if (coverFlowIndex < -coverFlowBehavior.childCount / 2)
        {
            coverFlowIndex = coverFlowBehavior.childCount / 2;
            t2dIndex += coverFlowBehavior.childCount;
            while (t2dIndex > coverFlowBehavior.t2ds.Count - 1)
                t2dIndex -= coverFlowBehavior.t2ds.Count;
        }
    }
    
    /// <summary>
    /// 根据索引确定图片显示效果
    /// </summary>
    public void SetImageDisplay()
    {
        float targetPosX = coverFlowBehavior.imageGap * coverFlowIndex;  //目标位置
        float sizeRatio = (coverFlowBehavior.childCount - Mathf.Abs(coverFlowIndex)) / (float)coverFlowBehavior.childCount;  //目标尺寸
        int targetSiblingIndex = coverFlowBehavior.childCount - 1 - (coverFlowIndex >= 0 ? Mathf.Abs(coverFlowIndex) * 2 : Mathf.Abs(coverFlowIndex) * 2 - 1);  //目标层级
        float alpha = 1 - (1 - sizeRatio) * 0.8f;
    
        rTrs.SetSiblingIndex(targetSiblingIndex);
    
        rTrs.DOSizeDelta(coverFlowBehavior.imageHeight * Vector2.one * sizeRatio, 0.3f);
        rTrs.DOAnchorPosX(targetPosX, 0.3f).onComplete = () =>
        {
            rawImage.texture = coverFlowBehavior.t2ds[t2dIndex];
            aspectRatioFitter.aspectRatio = rawImage.texture.width / (float)rawImage.texture.height;
            rawImage.color = new Color(alpha, alpha, alpha, 1);
        };
    }
    
    }
    

    为生成类添加.asset文件的创建方法

    到这里一个自定义插件的编写就已经完成了,但是每次修改生成参数,都需要修改Builder类,我们可以将需要修改的参数放入一个Parameter类中,这里我将其定义为了可以设置值的单例类

    public class CoverFlowParameters : ScriptableObject
    {
        [SerializeField]
        public int imageCount = 3;
        [SerializeField]
        public Vector2 imageSizeDelta = new Vector2();
    
        static CoverFlowParameters instance;
        public static CoverFlowParameters Instance
        {
            get
            {
                if (instance == null)
                    instance = CreateInstance<CoverFlowParameters>();
                return instance;
            }
            set
            {
                instance = value;
            }
        }
    
        private CoverFlowParameters() { }
    }
    

    参数类中有两个序列化的字段,imageCountimageSizeDelta是可以通过asset来修改的,也正是这两个字段作为了CoverFlowBuilder类的参数
    现在为Parameter类成一个.asset文件用来方便参数的设置,下面是一个生成.asset对象的泛型方法,类型T需要继承自ScriptableObject

    public static void CreateAsset<T>() where T : ScriptableObject
    {
        string path = Application.dataPath + "/Settings";
        if (!Directory.Exists(path))
            Directory.CreateDirectory(path);
        AssetDatabase.CreateAsset(CreateInstance(typeof(T)), string.Format("Assets/Settings/{0}.asset", typeof(T).ToString()));
    }
    

    用此方法创建CoverFlowBuilder的.asset文件,并将选项扩展到工具栏

    [MenuItem("ObjectAsset/CreateCoverFlowAsset")]
    static void CreateCoverFlowAsset()
    {
        AssetBuilder.CreateAsset<CoverFlowBuilder>();
    }
    
    asset创建选项.png

    若使用.asset文件设置和保存参数,在控件创建方法CreateCoverFlow中,需要先读取该asset文件中的参数

            //从asset中获取coverflow参数
            CoverFlowParameters.Instance = AssetDatabase.LoadAssetAtPath<CoverFlowParameters>("Assets/Settings/CoverFlowParameters.asset");
            CoverFlowParameters coverFlowParameters = CoverFlowParameters.Instance;
    

    这样就可以在工具栏由ObjectAsset=>CreateCoverFlowAsset来创建Builder类的asset,并通过修改asset文件来指定Builder类的参数了。


    生成的asset文件.png 通过asset文件指定参数.png

    项目源码github链接:https://github.com/Luke-Wang/UGUIExtend

    相关文章

      网友评论

          本文标题:简易UGUI插件的开发教程

          本文链接:https://www.haomeiwen.com/subject/kcrkfctx.html