简易UGUI插件的开发教程

2019-06-12  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读