手游手动异形屏适配方案,类“明日方舟”

这是一个手动的手游异形屏适配解决方案,即交给玩家一个slider,玩家通过调整slider来改变UI的适配,类似"明日方舟"的效果

实现这种方案的关键在于将除了背景以外的UI元素放入一个"SafeRect"中,这个SafeRect是一个撑满的空节点

实现效果如图所示

代码如下

csharp 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class SafeRect : MonoBehaviour
{
    /// <summary>
    /// 需要适配的安全区
    /// </summary>
    public RectTransform safeRect;
    
    /// <summary>
    /// 滑动条
    /// </summary>
    public Slider safeRectSlider;
    
    /// <summary>
    /// 滑动量
    /// </summary>
    public TextMeshProUGUI safeRectText;
    
    /// <summary>
    /// 偏移系数
    /// </summary>
    private float safeRectOffsetValue;
    private List<Action<float>> callbacks = new List<Action<float>>();
    
    private float screenWidth;
    private float screenHeight;
    private Rect safeArea;

    private float SafeRectOffsetValue
    {
        get
        {
            return safeRectOffsetValue;
        }
        set
        {
            if (value != safeRectOffsetValue)
            {
                safeRectOffsetValue = value;

                foreach (var callback in callbacks)
                {
                    callback?.Invoke(value);
                }
            }
        }
    }

    private void Start()
    {
        safeRectSlider.onValueChanged.AddListener(SliderChange);
        screenWidth = Screen.width;
        screenHeight = Screen.height;
        
        
        safeArea = Screen.safeArea;
        
        callbacks.Add(Fix);
    }
    
    /// <summary>
    /// 调整SafeRect宽度
    /// </summary>
    /// <param name="width"></param>
    private void SafeRectChange(float width)
    {
        if (safeRect != null)
        {
            safeRect.offsetMin = new Vector2(width, 0);
            safeRect.offsetMax = new Vector2(-width, 0);
        }
    }
    
    /// <summary>
    /// 获得SafeRect左右两边的最大调整量
    /// </summary>
    /// <returns></returns>
    private float GetSafeRectMaxOffset()
    {
        float width = Screen.width;
        float height = Screen.height;
        
        float result = width - 16/9f * height;  //刚好是16:9的屏幕,这里会是0,但又因为目前市面上没有刚好16:9还异形屏的手机,所以能用
        if (result < 0)    //即当前屏幕小于16:9的情况下(目前好像没有小于16:9又是异形屏的手机,折叠屏除外,所以这里不适合折叠屏)
        {
            return 0;
        }

        result = result * 0.5f / (height / 1080f);
        return result;
    }
    
    /// <summary>
    /// 偏移值改变的回调
    /// </summary>
    /// <param name="value"></param>
    private void Fix(float value)
    {
        if (safeArea.width == screenWidth && safeArea.height == screenHeight)  //如果不是异形屏,那么一直不变
        {
            return;
        }
        
        var safeRectWidth = GetSafeRectMaxOffset() * value;

        var s = screenWidth - safeArea.width;
        float diff = float.MaxValue;
        if (s > 0)  //是异形屏
        {
            diff = safeArea.xMin;
        }

        if (safeRectWidth >= diff)
        {
            safeRectWidth = diff;
        }
        
        SafeRectChange(safeRectWidth);
    }
    
    /// <summary>
    /// 滑动条回调
    /// </summary>
    /// <param name="input"></param>
    private void SliderChange(float input)
    {
        SafeRectOffsetValue = input;   //设置偏移值
        safeRectText.text = ((int)(input * 100)).ToString();
    }
}

这里的核心设计点在于,通过滑动条获得一个值,这个值就是SafeRect可以变化的一个"偏移系数 "

然后将这个值给到SafeRectOffsetValue,SafeRectOffsetValue本身是一个全局的可以被监听的数据类型,事先将需要监听的事件注册进来


Fix就是核心方法,这里会先判断一下当前屏幕是否是异形屏,如果不是异形屏,那么就直接return,否则就继续获取一个变化宽度,即GetSafeRectMaxOffset() * value

这里第一次获取的result就是在16:9的参考宽高比之下,当前屏幕分辨率的宽和以当前高为基准的理想16:9宽的差值,如果这个值小于0,即代表当前屏幕的宽高比小于16:9,而目前市面上又没有小于16:9的异形屏(折叠屏除外),所以这里直接返回0,即不管怎么拉都不会变;最后,将result / 2再除以当前屏幕高和1080的比值(因为参考分辨率是1920 x 1080,即16:9)得到最终"SafeRect"的左右两边可以调整的最大值 (这里比例因子的设计可能存在问题,但是先这么放着)

比如有屏幕分辨率2340 x1080

result = 2340 - 16/9 * 1080 = 420

scaleFactor = 1080 / 1080 = 1

最终左右两边偏移量 = 420 / 2 / 1 = 210

即"SafeRect"的左右两边最多可以改变210

这个最大值还需要乘滑动条传过来的偏移值,从0 - 1,即偏移值越大,两边收缩的越多

然后获得当前屏幕的"SafeArea",获取安全区的宽度,判断是否是异形屏,如果是异形屏,那么得到在当前屏幕下最大的调整值,即"SafeArea"的起点

最终调用SafeRectChange,这个方法通过改变"SafeRect"的offsetMin和offsetMax来改变其对于左右两边的距离,因为offsetMin是UI左下锚点到UI左下角的向量,offsetMax是UI右上锚点到UI右上角的向量


相关推荐
心疼你的一切6 小时前
Unity异步编程神器:Unitask库深度解析(功能+实战案例+API全指南)
深度学习·unity·c#·游戏引擎·unitask
呆呆敲代码的小Y8 小时前
【Unity 实用工具篇】 | Book Page Curl 快速实现翻书效果
游戏·unity·游戏引擎·u3d·免费游戏·翻书插件
AC梦21 小时前
unity中如何将UI上的字高清显示
ui·unity
小贺儿开发1 天前
Unity3D 智慧城市管理平台
数据库·人工智能·unity·智慧城市·数据可视化
June bug2 天前
【领域知识】休闲游戏一次发版全流程:Google Play + Apple App Store
unity
星夜泊客2 天前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
dzj20212 天前
PointerEnter、PointerExit、PointerDown、PointerUp——鼠标点击物体,则开始旋转,鼠标离开或者松开物体,则停止旋转
unity·pointerdown·pointerup
心前阳光2 天前
Unity 模拟父子关系
android·unity·游戏引擎
在路上看风景2 天前
26. Mipmap
unity
咸鱼永不翻身2 天前
Unity视频资源压缩详解
unity·游戏引擎·音视频