本帖最后由 pppboy 于 2023-4-22 11:11 编辑
本帖最后由 pppboy 于 2023-4-22 11:06 编辑
Unity UGUI 屏幕适配与黑边的处理
为了让项目能够在各种电子设备上都能正常运行,我们需要让UGUI在各种分辨率、各种比例下都能正常显示。为了达到这一目的我们需要做UGUI的屏幕适配。另外还有相机的适配在这篇文章中暂不讨论。
基础知识
首先我们需要了解手机屏幕分辨率的相关知识。
在屏幕适配的过程中我们主要关注:
屏幕的宽高比 (Aspect Ratio, 屏幕宽度/屏幕高度)。 可以在Unity的Game视图中进行调整。
游戏的设计分辨率。 既UI在设计界面的时候的分辨率。相对小的设计分辨率会减小安装包的大小,内存占用的大小等等。但是可能在大屏设备上变得模糊。
适配方案
由于UGUI是通过Canvas显示在界面上,所以Canvas的RenderMode属性需要调整为Screen Space - Camera.
当实际设备的屏幕分辨率与设计分辨路不一致的时候,通过CanvasScaler组件进行缩放。CanvasScaler组件在创建Canvas会自动创建。
CanvasScaler组件主要调整的参数就是UIScaleMode. 具体参数可以参考画布缩放器 (Canvas Scaler). 由于设计分辨率不可能和屏幕分辨率完美对应上我们需要选择Scale With Screen Size选项来保证UI会随着屏幕进行放大或缩小。
可选的适配方案:
按照短边等比缩放
按照短边进行等比缩放,长边进行自适应。自适应可以通过Anchors和Pivot来进行处理。
另外现在手机都有异性屏也需要进行处理。 处理方法为使用Screen.safeArea计算并设置Panel相关内容来避开安全区域。
实验处理
为了方便开发可以抽象一个Panel的概念。 Panel是一个界面的父节点,我们可以按照适配方案控制好panel的大小,这样这个节点下的所有UI都无需再考虑适配的逻辑。仅需要使用锚点等方法进行处理。
首先搭建一个测试场景如下:
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;
}
}
|