问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Unity UGUI屏幕适配详解:以悬浮窗为例

创作时间:
作者:
@小白创作中心

Unity UGUI屏幕适配详解:以悬浮窗为例

引用
CSDN
1.
https://blog.csdn.net/WPAPA/article/details/52209834

在Unity开发中,UGUI(Unity GUI)提供了强大的UI解决方案,但有时仍需手动处理屏幕适配问题。本文通过一个悬浮窗案例,详细介绍了如何在不同屏幕分辨率下实现UI元素的像素适配,包括技术原理和具体代码实现。

Unity的UGUI确实帮助开发者做了很多适配工作,但有时仍会遇到特殊需求,需要手动处理适配问题。例如,当需要实现一个类似iPhone的AssistiveTouch悬浮窗时,这个悬浮窗不能出屏,出屏后需要弹回到屏幕范围内,这时就需要考虑适配问题。

技术分析

处理这类问题时,需要考虑以下两点:

  1. UI资源的比例是什么?
  2. 要适配的屏幕比例又是什么?

假设UI资源是按照960x640(3:2)绘制的,而目标屏幕是1920x1080(16:9)。不能直接使用1920x1080的尺寸来限定运动范围,因为两种屏幕的比例不同。在960x640屏幕上(50,50)像素的点,在1920x1080屏幕上的位置是不同的。

但是,我们可以根据屏幕尺寸的差异来转换。具体思路是:如果用960 / 1920 = ? 宽的比乘以一个1920元素的尺寸或位置,那不就是等于960的尺寸和位置了。

但是,计算比例的方式必须与UGUI的适配方式保持一致,才能保证结果的准确性。如果UGUI是以高为适配的,你算得是宽的比,结果肯定是不对的。

这些重要的信息都来自于Canvas Scaler组件,它负责了UGUI中对各种尺寸屏幕的像素适配(这里我用的是Scale with Screen Size模式,自建单独的UI摄像机)。

代码实现

下面是一个悬浮球的完整实现代码:

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

public class UIBtnCloud : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    /// <summary>
    /// 按下后超过这个时间则认定为"长按"
    /// </summary>
    public float interval = 0.1f;
    /// <summary>
    /// 是否只调用一次
    /// </summary>
    public bool invokeOnce = false;
    // 按下标志
    private bool isPointerDown = false;
    // 记录时间
    private float recordTime;
    //是否已经调用过
    private bool hadInvoke = false;
    // 点击事件
    public UnityEvent onClick = new UnityEvent();
    // 按住事件
    public UnityEvent onPress = new UnityEvent();
    private RectTransform m_rectTransform;
    private float m_scale;
    private float m_minX, m_minY, m_maxX, m_maxY;

    void Start()
    {
        m_rectTransform = gameObject.GetComponent<RectTransform>();
        // 计算分辨率的比例
        CanvasScaler cs = XXX.GetInstance().CanvasScaler;
        m_scale = cs.matchWidthOrHeight == 1 ?
             (float)Screen.height / cs.referenceResolution.y :
             (float)Screen.width / cs.referenceResolution.x;
        // 范围
        m_minX = m_rectTransform.sizeDelta.x / 2 * m_scale;
        m_minY = m_rectTransform.sizeDelta.y / 2 * m_scale;
        m_maxX = Screen.width - m_minX;
        m_maxY = Screen.height - m_minY;
    }

    void Update()
    {
        // 一次机会已用完
        if (invokeOnce && hadInvoke) return;
        // 按下
        if (isPointerDown)
        {
            // 算按住
            if ((Time.time - recordTime) > interval)
            {
                hadInvoke = true;
                onPress.Invoke();
            }
        }
    }

    #region 处理点击
    // 按下
    void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    {
        isPointerDown = true;
        recordTime = Time.time;
    }
    // 抬起
    void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
    {
        isPointerDown = false;
        hadInvoke = false;
        // 算点击
        if ((Time.time - recordTime) < interval)
        {
            onClick.Invoke();
        }
    }
    // 离开
    void IPointerExitHandler.OnPointerExit(PointerEventData eventData)
    {
        isPointerDown = false;
        hadInvoke = false;
    }
    #endregion 处理点击 -------------------------------------

    #region 处理拖拽
    private void SetRectTransformPos(Vector2 position, Camera camera)
    {
        Vector3 globalMousePos;
        if (RectTransformUtility.ScreenPointToWorldPointInRectangle(m_rectTransform, position, camera, out globalMousePos))
        {
            m_rectTransform.position = globalMousePos;
        }
    }

    // 开始拖拽
    void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
    {
    }

    // 拖拽中
    void IDragHandler.OnDrag(PointerEventData eventData)
    {
        this.SetRectTransformPos(eventData.position, eventData.pressEventCamera);
    }

    // 结束拖拽
    void IEndDragHandler.OnEndDrag(PointerEventData eventData)
    {
        // 位置
        float x = m_rectTransform.position.x;
        float y = m_rectTransform.position.y;
        // 超出范围处理
        if (x < m_minX)
        {
            x = m_minX;
        }
        else if (x > m_maxX)
        {
            x = m_maxX;
        }
        if (y < m_minY)
        {
            y = m_minY;
        }
        else if (y > m_maxY)
        {
            y = m_maxY;
        }
        Vector2 currentPosition = new Vector2(x, y);
        // 设置坐标
        this.SetRectTransformPos(currentPosition, eventData.pressEventCamera);
    }
    #endregion 处理拖拽 -------------------------------------
}

将以上脚本挂在普通按钮上即可使用。需要注意的是,代码中的CanvasScaler cs = XXX.GetInstance().CanvasScaler;需要根据项目实际情况进行修改。

此外,这个实现没有添加差值效果,只是简单地将出屏的球拉回。如果需要更平滑的效果,可以自行添加差值动画。

总结

UGUI确实比NGUI好用,但同时也增加了使用的门槛,尤其是对于不了解差值动画概念的新手来说。不过,提高自己才是最重要的,不能总依赖别人。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号