unity 鼠标控制UI大小

文章介绍了如何在Unity中使用自定义组件实现UI大小的动态调整,支持单行或单列布局,通过鼠标拖拽调整UI元素的尺寸,同时提供锚点和最大/最小值设置。
摘要由CSDN通过智能技术生成

unity 鼠标控制UI大小(类似unity编辑器界面的界面大小拖拽效果)

效果展示

Unity-UI大小控制

组件介绍

组件属性面板

  1. 组件添加在UI组件上后,向rectTransforms中添加子物体页面
  2. 设置UI的模式SetType:横向排列、纵向排列
  3. 设置鼠标样式:不同的UI模式,需要不同的图片。Cursor.SetCursor无法设置鼠标样式图片的旋转
  4. 该组件所在游戏物体被隐藏,或者无需交互时,最好将该组件.enable=false,因为在该组件的Update中存在鼠标交互检测逻辑
  5. UI布局示例(子UI需要填满父物体UI,组件只负责修改子物体UI的锚点、轴心)
    在这里插入图片描述在这里插入图片描述

代码

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
/// <summary>
/// UI通过鼠标动态大小调整工具
/// 1.只能调整单行或者单列布局的UI
/// 2.鼠标放置在两个UI的交界处,按下鼠标左键,左右拖拽即可调整UI大小
/// 3.每个UI元素可以设置最大最小值。该值跟据设置的SetType类型自动应用成最小/大的高或宽。
/// </summary>
public class ChangeUISize : MonoBehaviour
{
    [Serializable]
    enum SetType
    {
        Horizontal,
        Vertical,
    }
    [Serializable]
    public enum TargetArea
    {
        none,
        top,
        left,
    }

    [Serializable]
    class UiInfo
    {
        public RectTransform rect;
        public float minSize;
        public float _curVal;
        public float CurVal
        {
            get
            {
                _curVal = setType == SetType.Horizontal ? rect.rect.width : rect.rect.height;
                return _curVal;
            }
            set
            {
                _curVal = value;
                rect.sizeDelta = setType == SetType.Horizontal ? new Vector2(_curVal, rect.sizeDelta.y) : new Vector2(rect.sizeDelta.x, _curVal);
            }
        }
        public float maxSize;
        public TargetArea curPressed;
        Dictionary<TargetArea, RectTransform> rects;
        public SetType setType;

        [NonSerialized] public UiInfo last = null;
        [NonSerialized] public UiInfo next = null;

        public void Init(SetType _setType, UiInfo _last, UiInfo _next, float maxWidth = 1920, float maxHeight = 1080)
        {
            curPressed = TargetArea.none;
            setType = _setType;
            last = _last;
            next = _next;
            _curVal = setType == SetType.Horizontal ? rect.rect.width : rect.rect.height;

            minSize = minSize <= 10 ? 10 : minSize;
            maxSize = maxSize == 0 ? (setType == SetType.Horizontal ? maxWidth : maxHeight) : maxSize;
            LoadAllPrefab();
            rects ??= new();
            rects.Clear();

            //添加鼠标响应区域的Rect(本质上是一个只有RectTransform的空物体)
            switch (_setType)
            {
                case SetType.Horizontal:
                    if (last != null)
                        rects.Add(TargetArea.left, GameObject.Instantiate(prefabs[TargetArea.left], rect).transform as RectTransform);
                    break;
                case SetType.Vertical:
                    if (last != null)
                        rects.Add(TargetArea.top, GameObject.Instantiate(prefabs[TargetArea.top], rect).transform as RectTransform);
                    break;
            }

            //修改Ui的锚点模式、轴点
            if (next != null)
            {
                rect.anchorMin = setType == SetType.Horizontal ? new Vector2(0, 0) : new Vector2(0, 1);
                rect.anchorMax = setType == SetType.Horizontal ? new Vector2(0, 1) : new Vector2(1, 1);
                rect.pivot = setType == SetType.Horizontal ? new Vector2(0, rect.pivot.y) : new Vector2(rect.pivot.x, 1);
            }
            else
            {
                rect.anchorMin = setType == SetType.Horizontal ? new Vector2(1, 0) : new Vector2(0, 0);
                rect.anchorMax = setType == SetType.Horizontal ? new Vector2(1, 1) : new Vector2(1, 0);
                rect.pivot = setType == SetType.Horizontal ? new Vector2(1, rect.pivot.y) : new Vector2(rect.pivot.x, 0);
            }
        }

        static Dictionary<TargetArea, GameObject> prefabs = null;

        static void LoadAllPrefab()
        {
            if (prefabs != null)
            {
                return;
            }

            prefabs = new Dictionary<TargetArea, GameObject>
            {
                { TargetArea.left, ResourcesLoad.LoadResources<GameObject>("Prefabs/MapEditor/UISizeChange/left") },
                { TargetArea.top, ResourcesLoad.LoadResources<GameObject>("Prefabs/MapEditor/UISizeChange/top") },
            };
        }

        public RectTransform GetPreseedRect(Vector2 mousePos)
        {
            foreach (var item in rects)
            {
                if (RectTransformUtility.RectangleContainsScreenPoint(item.Value, mousePos))
                {
                    curPressed = item.Key;
                    return item.Value;
                }
            }

            curPressed = TargetArea.none;
            return null;
        }

        public bool ChangeSize(float aimSize, ref float curChangedVal)
        {
            //ui修改总值满足条件,完成递归
            if (curChangedVal + aimSize == 0)
            {
                return true;
            }
            //计算预计值
            float tmpVal = Mathf.Clamp(_curVal - Mathf.Abs(aimSize - curChangedVal), minSize, maxSize);

            //判断是不是首尾的UI(要根据UI扩展的方向来判断首、尾)
            if ((aimSize > 0 && next == null) || (aimSize < 0 && last == null))
            {
                //若是首尾UI,则必须返回true,不管此时curChangedVal与aimSize是否相等,都要返回true
                //判断首尾UI大小是否还有改变的余地,有则修改
                if (tmpVal != _curVal)
                {
                    float tmpChangedVal = tmpVal - _curVal;  //当前UI修改量
                    curChangedVal += tmpChangedVal;         //累加修改量

                    CurVal = tmpVal;
                }
                return true;
            }
            //当前UI无法再修改了,进行递归,让后续UI接着修改
            if (tmpVal == _curVal)
            {
                //return next.ChangeSize(aimSize, ref curChangedVal);
                if (aimSize > 0)
                {
                    return next.ChangeSize(aimSize, ref curChangedVal);
                }
                else
                {
                    return last.ChangeSize(aimSize, ref curChangedVal);
                }
            }
            //当前UI可以修改时
            else
            {
                float tmpChangedVal = tmpVal - _curVal;  //当前UI修改量
                curChangedVal += tmpChangedVal;         //累加修改量

                //修改UI大小
                CurVal = tmpVal;
                //若当前UI可修改大小+之前UI累计修改大小 = aimSize,则返回true,结束递归
                if (curChangedVal + aimSize == 0)
                {
                    return true;
                }
                //若为满足aimSize,则继续递归
                else
                {
                    //return next.ChangeSize(aimSize, ref curChangedVal);
                    if (aimSize > 0)
                    {
                        return next.ChangeSize(aimSize, ref curChangedVal);
                    }
                    else
                    {
                        return last.ChangeSize(aimSize, ref curChangedVal);
                    }
                }
            }
        }
    }
    [SerializeField, Tooltip("灵敏度")] float sensitivity = 500f;
    [SerializeField] float pageMaxWidth;
    [SerializeField] float pageMaxHeight;
    [SerializeField] SetType setType;
    [SerializeField] Texture2D mouseSprite;
    [SerializeField, Tooltip("ui必须按照从左到右、从上到下的顺序依次添加")] UiInfo[] rectTransforms;

    static List<ChangeUISize> parentChangeUISizes = new();
    static ChangeUISize firstActive = null;
    void Start()
    {
        parentChangeUISizes.Add(this);
        pageMaxWidth = (transform as RectTransform).rect.width;
        pageMaxHeight = (transform as RectTransform).rect.height;

        Assert.IsTrue(rectTransforms.Length >= 2, "该组件内,控制的UI数量必须 size>=2");
        rectTransforms[0].Init(setType, null, rectTransforms[1], pageMaxWidth, pageMaxHeight);
        for (int i = 1; i < rectTransforms.Length - 1; i++)
        {
            rectTransforms[i].Init(setType, rectTransforms[i - 1], rectTransforms[i + 1], pageMaxWidth, pageMaxHeight);
        }
        rectTransforms[rectTransforms.Length - 1].Init(setType, rectTransforms[rectTransforms.Length - 2], null, pageMaxWidth, pageMaxHeight);
    }

    //不能序列化,序列化之后无法等于null(序列化时unity会给一个new()的空白对象)
    [NonSerialized] UiInfo selectedRect = null; 
    void Update()
    {
        Vector3 mousePosition = Input.mousePosition;
        
        if (Input.GetMouseButtonDown(0))
        {
            selectedRect = null;
            for (int i = 0; i < rectTransforms.Length; i++)
            {
                if (rectTransforms[i].GetPreseedRect(mousePosition))
                {
                    selectedRect = rectTransforms[i];
                    break;
                }
            }
        }
        else if (Input.GetMouseButtonUp(0))
        {
            selectedRect = null;
        }

        if (!Input.GetMouseButton(0))
        {
            SetMouseSprite(mousePosition);
        }
        else if (selectedRect != null && Input.GetMouseButton(0)) 
        {
            float offset = (setType == SetType.Horizontal ? Input.GetAxis("Mouse X") : -Input.GetAxis("Mouse Y")) * sensitivity * Time.deltaTime;
            if (offset != 0)
            {
                float curMovedVal = 0;
                //修改UI
                SetUISize(offset, ref curMovedVal);
            }
        }
    }
 
    /// <summary>
    /// 修改Ui的size
    /// </summary>
    /// <param name="aimMovedVal">需要修改的UI大小</param>
    /// <param name="curMovedVal">当前已修改的大小</param>
    /// <returns></returns>
    bool SetUISize(float aimMovedVal, ref float curMovedVal)
    {
        if (selectedRect == null) 
        {
            return false;
        }
        //Debug.Log($"SetUISize({index}, {aimMovedVal})");

        if (aimMovedVal < 0)
        {
            selectedRect.last.ChangeSize(aimMovedVal, ref curMovedVal);
        }
        else
        {
            selectedRect.ChangeSize(aimMovedVal, ref curMovedVal);
        }
        if (curMovedVal != 0) 
        {
            if (aimMovedVal > 0) 
            {
                selectedRect.last.CurVal += (-curMovedVal);
            }
            else
            {
                selectedRect.CurVal += (-curMovedVal);
            }
            
            //UI的大小有过修改,但是ChangeSize只负责调整大小,UI的位置不作调整,在此处重新调整UI位置(最后一个UI锚点设置的不同,不需调整位置,只需要调整大小)
            float pos = 0;
            for (int i = 0; i < rectTransforms.Length - 1; i++)
            {
                rectTransforms[i].rect.anchoredPosition = setType == SetType.Horizontal ? 
                    new Vector2(pos, rectTransforms[i].rect.anchoredPosition.y)  : new Vector2(rectTransforms[i].rect.anchoredPosition.x, pos);
                pos += setType == SetType.Horizontal ? rectTransforms[i].rect.rect.width : rectTransforms[i].rect.rect.height;
            }
        }

        return true;
    }

    bool SetMouseSprite(Vector3 mousePos)
    {
        //确保只有一个组件在检测鼠标是否需要改变样式(一个大的页面中,可能存在多个组件同时存在,会导致鼠标一直闪烁)
        if (firstActive != null && firstActive != this && !firstActive.isActiveAndEnabled)
        {
            return false;
        }
        //选出一个当前用于遍历鼠标位置的活跃组件
        for (int i = 0;i < parentChangeUISizes.Count; i++)
        {
            if (parentChangeUISizes[i].isActiveAndEnabled)
            {
                firstActive = parentChangeUISizes[i];
            }
        }
        //遍历鼠标位置
        for (int i = 0; i < parentChangeUISizes.Count; i++)
        {
            if (!parentChangeUISizes[i].isActiveAndEnabled)
            {
                continue;
            }
            for (int j = 0; j < parentChangeUISizes[i].rectTransforms.Length; j++)
            {
                var rect = parentChangeUISizes[i].rectTransforms[j].GetPreseedRect(mousePos);
                if (rect != null)
                {
                    Cursor.SetCursor(parentChangeUISizes[i].mouseSprite, new Vector2(16, 16), CursorMode.Auto);
                    return true;
                }
            }
        }
        Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
        return false;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值