Unity UGUI 屏幕适配

Unity UGUI 屏幕适配

为了让项目能够在各种电子设备上都能正常运行,我们需要让UGUI在各种分辨率、各种比例下都能正常显示。为了达到这一目的我们需要做UGUI的屏幕适配。另外还有相机的适配在这篇文章中暂不讨论。

基础知识

首先我们需要了解手机屏幕分辨率的相关知识。
在屏幕适配的过程中我们主要关注:

  1. 屏幕的宽高比 (Aspect Ratio, 屏幕宽度/屏幕高度)。 可以在Unity的Game视图中进行调整。
  2. 游戏的设计分辨率。 既UI在设计界面的时候的分辨率。相对小的设计分辨率会减小安装包的大小,内存占用的大小等等。但是可能在大屏设备上变得模糊。

适配方案

由于UGUI是通过Canvas显示在界面上,所以Canvas的RenderMode属性需要调整为Screen Space - Camera.
当实际设备的屏幕分辨率与设计分辨路不一致的时候,通过CanvasScaler组件进行缩放。CanvasScaler组件在创建Canvas会自动创建。
CanvasScaler组件主要调整的参数就是UIScaleMode. 具体参数可以参考画布缩放器 (Canvas Scaler). 由于设计分辨率不可能和屏幕分辨率完美对应上我们需要选择Scale With Screen Size选项来保证UI会随着屏幕进行放大或缩小。

可选的适配方案:

  1. 按照短边等比缩放
  2. 按照短边进行等比缩放,长边进行自适应。自适应可以通过Anchors和Pivot来进行处理。

另外现在手机都有异性屏也需要进行处理。 处理方法为使用Screen.safeArea计算并设置Panel相关内容来避开安全区域。

实验处理

为了方便开发可以抽象一个Panel的概念。 Panel是一个界面的父节点,我们可以按照适配方案控制好panel的大小,这样这个节点下的所有UI都无需再考虑适配的逻辑。仅需要使用锚点等方法进行处理。
首先搭建一个测试场景如下:
testscene
Canvas是我们的画布,挂载了AdaptiveCanvas脚本
Panel是我们的一个界面,挂载了AdaptivePanel脚本
Bg是没有进行适配的对照,其中的两个方块固定再左上和右下

注意: 代码在Update中一直计算比例并进行适配,实际项目中需要通过发事件或者其他方式自行处理。

public enum AdaptivePlan
{
    
    
    Match, // 通过自适应适配,尽量填充满屏幕
    Expand, // 比例实在夸张无法适配,采用固定尺寸,其他区域留白的方式处理
}

public class AdaptiveCanvas : MonoBehaviour
{
    
    
    public static AdaptiveCanvas Instance;
    public Vector2 referenceResolutoin = new Vector2(750, 1334);

    public AdaptivePlan adaptivePlan;
    private float lastwidth = 0f;
    private float lastheight = 0f;

    CanvasScaler canvasScaler;

    private void Awake()
    {
    
    
        Instance = this;
        canvasScaler = GetComponent<CanvasScaler>();
        canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        canvasScaler.referenceResolution = referenceResolutoin;
    }
    void Update()
    {
    
    
        CheckResolution();
    }
    private void CheckResolution()
    {
    
    
        if (lastwidth != Screen.width || lastheight != Screen.height)
        {
    
    
            lastwidth = Screen.width;
            lastheight = Screen.height;
            Adaptive();
        }
    }
    private void Adaptive()
    {
    
    
        // 一般情况下竖屏游戏的短边是宽,横屏游戏的短边是高
        bool shortSideIsWidth = Screen.width < Screen.height ? true : false;

        float ratio = (float)Screen.width / Screen.height;
        // Debug.Log($"width: {Screen.width}  height: {Screen.height} ratio: {ratio}");

        if (shortSideIsWidth)
        {
    
    
            if (ratio < 0.4f || ratio > 0.6f) // 比例实在太离谱,为保证游戏可用采取拓展画布区域
            {
    
    
                canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
                adaptivePlan = AdaptivePlan.Expand;
                return;
            }
            canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
            canvasScaler.matchWidthOrHeight = 0;
            adaptivePlan = AdaptivePlan.Match;
        }
        else
        {
    
    
            if (ratio < 1.6f || ratio > 2.5f) // 类型长方形比例
            {
    
    
                canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
                adaptivePlan = AdaptivePlan.Expand;
                return;
            }
            // 比例在合适范围内使用短边适配的方案
            canvasScaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
            canvasScaler.matchWidthOrHeight = 1;
            adaptivePlan = AdaptivePlan.Match;
        }
    }
}
public class AdaptivePanel : MonoBehaviour
{
    
    
    RectTransform rectTrans;
    // Start is called before the first frame update
    void Start()
    {
    
    
        rectTrans = transform as RectTransform;
    }

    private void Update()
    {
    
    
        if (AdaptiveCanvas.Instance.adaptivePlan == AdaptivePlan.Match)
        {
    
    
            rectTrans.localScale = Vector3.one;
            UpdateAdaptive();
            rectTrans.anchorMin = Vector2.zero;
            rectTrans.anchorMax = Vector2.one;
            rectTrans.offsetMin = Vector2.zero;
            rectTrans.offsetMax = Vector2.zero;
        }
        else if (AdaptiveCanvas.Instance.adaptivePlan == AdaptivePlan.Expand)
        {
    
    
            rectTrans.localScale = Vector3.one;
            UpdateAdaptive();
            rectTrans.anchorMin = Vector2.one * 0.5f;
            rectTrans.anchorMax = Vector2.one * 0.5f;
            rectTrans.offsetMin = Vector2.zero;
            rectTrans.offsetMax = Vector2.zero;

            rectTrans.sizeDelta = AdaptiveCanvas.Instance.referenceResolutoin;
        }
    }
	// 使用safeArea适配异性屏
    void UpdateAdaptive()
    {
    
    
        var r = Screen.safeArea;
        Vector2 anchorMin = r.position;
        Vector2 anchorMax = r.position + r.size;
        anchorMin.x /= Screen.width;
        anchorMin.y /= Screen.height;
        anchorMax.x /= Screen.width;
        anchorMax.y /= Screen.height;
        rectTrans.anchorMin = anchorMin;
        rectTrans.anchorMax = anchorMax;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_36433883/article/details/125653050