unity 鼠标控制UI大小(类似unity编辑器界面的界面大小拖拽效果)
效果展示
Unity-UI大小控制
组件介绍
- 组件添加在UI组件上后,向rectTransforms中添加子物体页面
- 设置UI的模式SetType:横向排列、纵向排列
- 设置鼠标样式:不同的UI模式,需要不同的图片。Cursor.SetCursor无法设置鼠标样式图片的旋转
- 该组件所在游戏物体被隐藏,或者无需交互时,最好将该组件.enable=false,因为在该组件的Update中存在鼠标交互检测逻辑
- 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;
}
}