Unity之ScrollRect简易实现

资料

ScrollRect源码

创建Scroll View,选择组件对象,查看源码

作用

实现滑动窗口,学习滑动窗口如何实现。

思路

  1. 继承IDragHandler, IBeginDragHandler, IEndDragHandler,实现拖拽功能
  2. 计算可视区域和移动区域的Bounds,限制移动区域

实现

主要方法:

  1. UpdateBounds方法用于计算可视区域和内容区域的包围盒
  2. AdjuseBounds方法调节内容区域的包围盒
  3. CalculateOffset方法计算偏移位置,防止超出边界
csharp 复制代码
using UnityEngine;
using UnityEngine.EventSystems;

public class SimpleScrollRect : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
{
    [Header("拖动区域")]
    [SerializeField] RectTransform viewRect;

    [Header("移动内容")]
    [SerializeField] RectTransform content;

    [Header("水平移动")]
    [SerializeField] bool horizontal;

    [Header("竖直移动")]
    [SerializeField] bool vertical;

    Vector2 startLocalCursor;
    Vector2 contentStartPosition;
    bool isDrag = false;

    Bounds viewBounds;
    Bounds contentBounds;

    public void OnBeginDrag(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect,
        eventData.position, eventData.pressEventCamera,
        out startLocalCursor);
        contentStartPosition = content.anchoredPosition;
        isDrag = true;
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (isDrag == false) return;
        if (eventData.button != PointerEventData.InputButton.Left)
            return;

        Vector2 localCursor;
        if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewRect,
        eventData.position, eventData.pressEventCamera, out localCursor))
            return;

        UpdateBounds();

        //从拖拽开始到当前帧,鼠标在 viewRect 局部坐标系中的总累计位移
        var pointerDelta = localCursor - startLocalCursor;
        //目标位置
        var position = contentStartPosition + pointerDelta;
        //从当前实际位置到目标位置所需的位移
        var delta = position - content.anchoredPosition;
        //边界限制
        var offset = CalculateOffset(ref viewBounds, ref contentBounds, horizontal, vertical, delta);
        position += offset;
        //水平或竖直移动限制
        if (!horizontal)
            position.x = content.anchoredPosition.x;
        if (!vertical)
            position.y = content.anchoredPosition.y;
        //更新位置
        if (position != content.anchoredPosition)
            content.anchoredPosition = position;
    }

    private readonly Vector3[] contentCorners = new Vector3[4];
    protected void UpdateBounds()
    {
        //生成可视区域bounds
        viewBounds = new Bounds(viewRect.rect.center, viewRect.rect.size);

        //生成内容区域Bounds,坐标系统一在viewRect的局部坐标系中
        var viewWorldToLocalMatrix = viewRect.worldToLocalMatrix;
        content.GetWorldCorners(contentCorners);
        contentBounds = GetBounds(contentCorners, ref viewWorldToLocalMatrix);

        //调整内容区域Bounds,确保内容区域永远不会小于可视区域
        Vector3 contentSize = contentBounds.size;
        Vector3 contentPos = contentBounds.center;
        var contentPivot = content.pivot;
        AdjustBounds(ref viewBounds, ref contentPivot, ref contentSize, ref contentPos);
        contentBounds.size = contentSize;
        contentBounds.center = contentPos;
    }
    private Bounds GetBounds(Vector3[] corners, ref Matrix4x4 viewWorldToLocalMatrix)
    {
        var vMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
        var vMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);

        for (int j = 0; j < 4; j++)
        {
            Vector3 v = viewWorldToLocalMatrix.MultiplyPoint3x4(corners[j]);
            vMin = Vector3.Min(v, vMin);
            vMax = Vector3.Max(v, vMax);
        }

        var bounds = new Bounds(vMin, Vector3.zero);
        bounds.Encapsulate(vMax);
        return bounds;
    }
    private void AdjustBounds(ref Bounds viewBounds, ref Vector2 contentPivot, ref Vector3 contentSize, ref Vector3 contentPos)
    {
        Vector3 excess = viewBounds.size - contentSize;//可视区域超出内容区域的尺寸

        if (excess.x > 0)//当内容区域宽度小于可视区域宽度,扩展内容区域宽度
        {
            //contentPivot.x=0.5 原有矩形中心位置不变,宽度向两侧增加,中心点不改变
            //contentPivto.x=0 原有矩形左边界位置不变,宽度向右侧增加,中心点向右移动
            //contentPivot.x=1 原有矩形右边界位置不变,宽度向左测增加,中心点向左移动
            //宽度改变时,改变中心点,保证pivot的视觉位置不变
            contentPos.x -= excess.x * (contentPivot.x - 0.5f);
            contentSize.x = viewBounds.size.x;
        }
        if (excess.y > 0)//当内容区域高度小于可视区域高度,扩展内容区域高度
        {
            contentPos.y -= excess.y * (contentPivot.y - 0.5f);
            contentSize.y = viewBounds.size.y;
        }
    }
    private Vector2 CalculateOffset(ref Bounds viewBounds, ref Bounds contentBounds, bool horizontal, bool vertical, Vector2 delta)
    {
        Vector2 offset = Vector2.zero;
        Vector2 min = contentBounds.min;
        Vector2 max = contentBounds.max;
        if (horizontal)
        {
            min.x += delta.x;//模拟内容偏移后的左边界
            max.x += delta.x;//模拟内容偏移后的右边界

            float minOffset = viewBounds.min.x - min.x;//可视区域的左边界减去内容左边界
                                                       //>0 内容左边界 在  可视区域左边界 左侧
                                                       //<0 内容左边界 在  可视区域左边界 右侧

            float maxOffset = viewBounds.max.x - max.x;//可视区域的右边界减去内容右边界
                                                       //>0 内容右边界 在 可视区域右边界 左侧
                                                       //<0 内容右边界 在 可视区域右边界 右侧
            if (minOffset < -0.001f) //左边界对齐
                offset.x = minOffset;
            else if (maxOffset > 0.001f)//右边界对齐
                offset.x = maxOffset;
        }

        if (vertical)
        {
            min.y += delta.y;//模拟内容偏移后的下边界
            max.y += delta.y;//模拟内容偏移后的上边界

            float maxOffset = viewBounds.max.y - max.y;//可视区域的上边界减去内容上边界
                                                       //>0 内容上边界 在  可视区域上边界 下侧
                                                       //<0 内容上边界 在  可视区域上边界 上侧

            float minOffset = viewBounds.min.y - min.y;//可视区域的下边界减去内容下边界
                                                       //>0 内容下边界 在  可视区域下边界 下侧
                                                       //<0 内容下边界 在  可视区域下边界 上侧
            if (maxOffset > 0.001f)//上边界对齐
                offset.y = maxOffset;
            else if (minOffset < -0.001f)//下边界对齐
                offset.y = minOffset;
        }

        return offset;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if (eventData.button != PointerEventData.InputButton.Left)
            return;
        isDrag = false;
    }
}
相关推荐
RReality6 小时前
【Unity Shader URP】Matcap 材质捕捉实战教程
java·ui·unity·游戏引擎·图形渲染·材质
魔士于安6 小时前
unity urp材质球大全
游戏·unity·游戏引擎·材质·贴图·模型
南無忘码至尊9 小时前
Unity学习90天 - 第 6 天 -学习物理 Material + 重力与阻力并实现弹跳球和冰面滑动效果
学习·unity·游戏引擎
mxwin11 小时前
Unity 单通道立体渲染(Single Pass Instanced)对 Shader 顶点布局的特殊要求
unity·游戏引擎·shader
魔士于安13 小时前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
RReality13 小时前
【Unity Shader URP】简易卡通着色(Simple Toon)实战教程
ui·unity·游戏引擎·图形渲染·材质
魔士于安14 小时前
unity 骷髅人 连招 武器 刀光 扭曲空气
游戏·unity·游戏引擎·贴图·模型
洛阳吕工16 小时前
从 micro-ROS 到 px4_ros2:ROS2 无人机集成开发实战指南
游戏引擎·无人机·cocos2d
风酥糖16 小时前
Godot游戏练习01-第29节-游戏导出
游戏·游戏引擎·godot
瑞瑞小安16 小时前
Unity功能篇:文本框随文字内容动态调整
ui·unity