Unity Scroll View 键盘和手柄切换子项适配

 先看效果:

        当选中到显示范围外的UI时,Scroll View的content会自动往下翻或者往上翻 。

        在进行游戏UI的手柄和键盘操作适配时,可以使用UGUI的Navigation功能,我们可以通过方向键来切换UI选中。

UI的Navigation功能可以切换selected的UI

         但是在使用Scroll View时,如果切换到超出ScrollView显示范围之外的UI就看不到了。

如图:

        需要自己进行一下适配,话不多说,上代码:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class ScrollViewSelectControl : MonoBehaviour
{
    public ScrollRect scrollRect;
    public RectTransform content;

    void Start()
    {
        // 为每个子UI元素添加EventTrigger组件
        foreach (Transform child in content)
        {
            EventTrigger trigger = child.gameObject.AddComponent<EventTrigger>();
            EventTrigger.Entry entry = new EventTrigger.Entry();
            entry.eventID = EventTriggerType.Select;
            entry.callback.AddListener((eventData) => { EnsureVisible((RectTransform)child); });
            trigger.triggers.Add(entry);
        }
    }

    void EnsureVisible(RectTransform target)
    {
        // 获取Scroll View的显示区域
        RectTransform viewport = scrollRect.viewport;

        // 将目标UI元素的坐标转换到viewport的局部坐标系中
        Vector3[] targetCorners = new Vector3[4];
        target.GetWorldCorners(targetCorners);
        Vector3[] viewportCorners = new Vector3[4];
        viewport.GetWorldCorners(viewportCorners);

        // 计算目标UI元素相对于viewport的偏移量
        float offsetMin = targetCorners[0].y - viewportCorners[0].y;
        float offsetMax = targetCorners[2].y - viewportCorners[2].y;

        // 调整Scroll View的content位置
        if (offsetMin < 0)
        {
            scrollRect.content.anchoredPosition += new Vector2(0, -offsetMin);
        }
        else if (offsetMax > 0)
        {
            scrollRect.content.anchoredPosition += new Vector2(0, -offsetMax);
        }
    }
}

         这里注意GetWorldCorners这个函数,它的作用是获取一个RectTransform 的四个角的世界坐标,需要传入的是一个至少长度为四的数组,因为数组是引用类型,在函数内执行的时候会为数组赋值,所以我们创建两个数组来获取RectTransform四个角的坐标。关于GetWorldCorners的详情可以看这里。在获取我们选中的UI的四个角的世界坐标和ScrollView的显示区域的四个角坐标后,通过将对应的坐标相减就可以得到偏移量了,因为我这个ScrollView是只能上下滑动的,所以就比对数组中的02(RectTransform的左下角和右上角)元素的y值,如果要左右滑动就换成比对x值就好了。

--------------------------------------------------------------分割线--------------------------------------------------------

发现原来的功能有的不全,那么现在来做一个进阶版本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ScrollViewController : MonoBehaviour
{
    public ScrollRect scrollRect;
    public RectTransform content;
    public bool horizontal = true;
    public bool vertical = true;
    public Canvas canvas;
    private void Awake() {
        canvas = GetComponentInParent<Canvas>();
    }
    private void Start() {
        UpdateItem();
    }
    public void UpdateItem()
    {
        // 为每个子UI元素添加EventTrigger组件
        foreach (Transform child in content)
        {
            EventTrigger trigger = child.gameObject.AddComponent<EventTrigger>();
            EventTrigger.Entry entry = new EventTrigger.Entry();
            entry.eventID = EventTriggerType.Select;
            entry.callback.AddListener((eventData) => { EnsureVisible((RectTransform)child); });
            trigger.triggers.Add(entry);
        }
    }
    void EnsureVisible(RectTransform target)
    {
        // 获取Scroll View的显示区域
        RectTransform viewport = scrollRect.viewport;

        // 将目标UI元素的坐标转换到viewport的局部坐标系中
        Vector3[] targetCorners = new Vector3[4];
        target.GetWorldCorners(targetCorners);
        Vector3[] viewportCorners = new Vector3[4];
        viewport.GetWorldCorners(viewportCorners);

         // 将屏幕坐标转换为Canvas内的局部坐标
        Vector2 localTargetMin, localTargetMax, localViewportMin, localViewportMax;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, targetCorners[0]), canvas.worldCamera, out localTargetMin);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, targetCorners[2]), canvas.worldCamera, out localTargetMax);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, viewportCorners[0]), canvas.worldCamera, out localViewportMin);
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, viewportCorners[2]), canvas.worldCamera, out localViewportMax);
        
        // 计算目标UI元素相对于viewport的偏移量
        Vector2 offsetMin = localTargetMin - localViewportMin;
        Vector2 offsetMax = localTargetMax - localViewportMax;

        // 调整Scroll View的content位置
        if (vertical)
        {
            if (offsetMin.y < 0)
                scrollRect.content.anchoredPosition += new Vector2(0, -offsetMin.y);
            else if (offsetMax.y > 0)
                scrollRect.content.anchoredPosition += new Vector2(0, -offsetMax.y);
        }

        if (horizontal)
        {
            if (offsetMin.x < 0)
                scrollRect.content.anchoredPosition += new Vector2(-offsetMin.x, 0);
            else if (offsetMax.x > 0)
                scrollRect.content.anchoredPosition += new Vector2(-offsetMax.x, 0);
        }
        
    }

}

        现在我们通过设置 ScrollViewSelectedControl 类中的horizontal和vertical两个布尔值就可以控制水平和垂直方向的滑动了。此外,还添加了将获取到的世界坐标都转换为canvas的局部坐标,防止因为canvas缩放带来的错位。

最后让我们看看效果吧。

  • 13
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值