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

这是一个手动的手游异形屏适配解决方案,即交给玩家一个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右上角的向量


相关推荐
Longyugxq1 天前
Untiy的Webgl端网页端视频播放,又不想直接mp4格式等格式的。
unity·音视频·webgl
avi91111 天前
Unity毛玻璃渲染模糊渲染Shader数学入门
unity·aigc·图形学·shader·hlsl
微光守望者1 天前
Unity小知识【1】:刚体(Rigidbody)与碰撞器(Collider)的区别,你真的清楚吗?
unity·游戏引擎
June bug1 天前
【配环境】unity项目开发环境
unity·游戏引擎
JQLvopkk1 天前
C#调用Unity实现设备仿真开发浅述
开发语言·unity·c#
秦奈1 天前
Unity复习学习笔记(九):UGUI
笔记·学习·unity
垂葛酒肝汤2 天前
unity的背包滑动组件中道具的提示框被裁剪的问题
unity·游戏引擎
yi碗汤园2 天前
【超详细】TCP编程与UDP编程
网络·网络协议·tcp/ip·unity·udp·visual studio
teunyu2 天前
在Unity中使用LineRenderer实现A点到B点的贝塞尔曲线。并且曲线为虚线。方向为A点流向B点。效果图如下
unity·游戏引擎