【Unity】一个鼠标控制UI移动和缩放的小组件

简介

能实现用鼠标拖动UI位置,滚轮缩放UI尺寸的功能。支持在缩放的时候让UI向光标位置靠拢,不过没有做平滑处理。

可以满足简单的需求,至少能避免一些界面遮挡视野带来的不悦。

使用要求

目标对象必须具有Rect Transform组件。我的环境是unity2021+vs2019

解释文档

CanMove:允许UI被光标拖拽移动。若您不希望UI被移动,请务必同时关闭“IsFocusWhenScale”聚焦功能,避免UI位置的移动。

CanScale:允许使用滚轮缩放UI的大小。此功能通过增减UI物体的localScale实现。

ScaleSpeed:缩放速度,即滚轮每个段落将改变UI的localScale大小。如在ScaleSpeed为0.1f时,正向滚动滚轮一个段落将使UI的localScale增加0.1f。

LimitSize.x:缩放后最小尺寸

LimitSize.y:缩放后最大尺寸

IsFucusWhenScale:是否启用聚焦(在缩放时,使UI向光标靠近)

FocusSpeed:聚焦速度,绝对值越大,聚焦越快。默认为正值。为负值时,缩放会远离光标。

AllowOverScreen:当光标在画面外时,是否允许操作生效。特别地,当您不希望用户将UI拖拽至画面外时,禁用此选项。

ResetOnDisable:当UI物件被禁用或处于非活动状态时,是否将其位置和尺寸复原。当您使用Destroy销毁该UI物件或使用SetActive(false)隐藏它(无论在这之前activeSelf是否已经为false),亦或是运行时在编辑器中手动隐藏该Ui,都会触发此功能。

代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(RectTransform))]
public class UIMove : MonoBehaviour, IDragHandler, IBeginDragHandler, IScrollHandler
{
    [Header("移动")]
    [SerializeField]
    private bool canMove = true;

    [Header("缩放")]
    [SerializeField]
    private bool canScale = false;

    /// <summary>
    /// 滚轮滚动一个段落缩放的本地大小倍数
    /// </summary>
    [SerializeField]
    [Tooltip("缩放的速度")]
    private float scaleSpeed = 0.1f;

    [SerializeField]
    [Tooltip("x:最小尺寸 y:最大尺寸")]
    private Vector2 limitSize = new Vector2(0.5f,2);

    [Header("聚焦")]
    [Tooltip("在缩放时,使UI中心向光标靠近")]
    [SerializeField]
    private bool isFocusWhenScale = true;

    /// <summary>
    /// 缩放时,靠近的速度
    /// </summary>
    [SerializeField]
    private float focusSpeed = 0.3f;

    [Header("其他")]
    [SerializeField]
    [Tooltip("允许光标超出屏幕后生效")]
    private bool AllowOverScreen = false;

    [SerializeField]
    [Tooltip("非活动(Disable)时复原位置和尺寸")]
    private bool ResetOnDisable = true;

    RectTransform rt;

    //拖拽前UI的位置
    Vector3 startObjectPos;

    //每次拖拽开始前光标的位置
    Vector3 startMousePos;
    
    //每次拖拽过程中光标的位置
    Vector3 nowMousePos;
    
    //UI的原始尺寸的位置,用于复原
    Vector3 _originScale;
    Vector3 _originPoint;

    void Start()
    {
        rt = gameObject.GetComponent<RectTransform>();
        _originPoint = rt.localPosition;
        _originScale = rt.localScale;
    }
    public void OnScroll(PointerEventData eventData)
    {
        if(canScale) SetSize(eventData);
    }
    public void SetSize(PointerEventData eventData)
    {
        float speed = focusSpeed / rt.localScale.x;

        //注意eventData.enterEventCamera与eventData.pressEventCamera的不同,在这里踩了坑,导致第一次缩放出问题
        RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, eventData.position, eventData.enterEventCamera, out nowMousePos);

        float OffsetX = nowMousePos.x - transform.position.x;
        float OffsetY = nowMousePos.y - transform.position.y;
        float PivotX = OffsetX / rt.rect.width / transform.localScale.x;
        float PivotY = OffsetY / rt.rect.height / transform.localScale.y;

        if (isFocusWhenScale)
        {
            rt.pivot += new Vector2(PivotX, PivotY);
            transform.position += new Vector3(OffsetX, OffsetY, 0) * speed;
        }
        if (Input.GetAxis("Mouse ScrollWheel") > 0)
        {
            transform.localScale += (transform.localScale.x >= limitSize.y ? Vector3.zero : Vector3.one * scaleSpeed);
        }
        else if (Input.GetAxis("Mouse ScrollWheel") < 0)
        {
            transform.localScale += (transform.localScale.x <= limitSize.x ? Vector3.zero : Vector3.one * -scaleSpeed);
        }
    }
    public void OnBeginDrag(PointerEventData eventData)
    {
        //记录拖拽前UI位置,鼠标位置
        startObjectPos = rt.position;
        RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, eventData.position, eventData.pressEventCamera, out startMousePos);
    }
    public void OnDrag(PointerEventData eventData)
    {
        if (canMove) SetDraggedPosition(eventData);
    }
    private void SetDraggedPosition(PointerEventData eventData)
    {
        //检测鼠标位置是否在UI内
        if (RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, eventData.position, eventData.pressEventCamera, out nowMousePos))
        {
            //记录光标移动的路程,加到UI上,实现位置跟随
            Vector3 targetPos = startObjectPos + nowMousePos - startMousePos;

            //若光标移动到了屏幕外,根据设AllowOverScreen决定是否跟随
            if (AllowedMove())
            {
                rt.position = targetPos;
            }
        }
    }
    /// <summary>
    /// 检测鼠标是否在屏幕内
    /// </summary>
    /// <param name="targetPos"></param>
    /// <param name="targetlocalScale"></param>
    /// <returns></returns>
    private bool IsOverScreen()
    {
        bool isOver;
        isOver = Input.mousePosition.x > Screen.width || Input.mousePosition.x < 0 
            || Input.mousePosition.y > Screen.height || Input.mousePosition.y < 0;
        return isOver;
    }
    private bool AllowedMove()
    {
        return !IsOverScreen() || AllowOverScreen;
    }
    /// <summary>
    /// 将其位置和尺寸重置到初始状态
    /// </summary>
    public void ResetPosAndScale()
    {
        this.transform.localPosition = _originPoint;
        this.transform.localScale = _originScale;
    }
    /// <summary>
    /// 隐藏时复原位置和尺寸
    /// </summary>
    public void OnDisable()
    {
        if(ResetOnDisable) ResetPosAndScale();
    }
}
© 版权声明
THE END
喜欢就点个赞吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容