news 2026/4/23 12:28:13

Unity插件SafeArea Helper适配异形屏详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity插件SafeArea Helper适配异形屏详解

一.导入插件SafeArea Helper

链接:

Safe Area Helper

打开SafeAreaDemo场景,打开Simulator视图,切到异形屏机型

二.SafeArea.cs脚本分析

先上代码

using UnityEngine; namespace Crystal { /// <summary> /// Safe area implementation for notched mobile devices. Usage: /// (1) Add this component to the top level of any GUI panel. /// (2) If the panel uses a full screen background image, then create an immediate child and put the component on that instead, with all other elements childed below it. /// This will allow the background image to stretch to the full extents of the screen behind the notch, which looks nicer. /// (3) For other cases that use a mixture of full horizontal and vertical background stripes, use the Conform X & Y controls on separate elements as needed. /// </summary> public class SafeArea : MonoBehaviour { #region Simulations /// <summary> /// Simulation device that uses safe area due to a physical notch or software home bar. For use in Editor only. /// </summary> public enum SimDevice { /// <summary> /// Don't use a simulated safe area - GUI will be full screen as normal. /// </summary> None, /// <summary> /// Simulate the iPhone X and Xs (identical safe areas). /// </summary> iPhoneX, /// <summary> /// Simulate the iPhone Xs Max and XR (identical safe areas). /// </summary> iPhoneXsMax, /// <summary> /// Simulate the Google Pixel 3 XL using landscape left. /// </summary> Pixel3XL_LSL, /// <summary> /// Simulate the Google Pixel 3 XL using landscape right. /// </summary> Pixel3XL_LSR } /// <summary> /// Simulation mode for use in editor only. This can be edited at runtime to toggle between different safe areas. /// </summary> public static SimDevice Sim = SimDevice.None; /// <summary> /// Normalised safe areas for iPhone X with Home indicator (ratios are identical to Xs, 11 Pro). Absolute values: /// PortraitU x=0, y=102, w=1125, h=2202 on full extents w=1125, h=2436; /// PortraitD x=0, y=102, w=1125, h=2202 on full extents w=1125, h=2436 (not supported, remains in Portrait Up); /// LandscapeL x=132, y=63, w=2172, h=1062 on full extents w=2436, h=1125; /// LandscapeR x=132, y=63, w=2172, h=1062 on full extents w=2436, h=1125. /// Aspect Ratio: ~19.5:9. /// </summary> Rect[] NSA_iPhoneX = new Rect[] { new Rect (0f, 102f / 2436f, 1f, 2202f / 2436f), // Portrait new Rect (132f / 2436f, 63f / 1125f, 2172f / 2436f, 1062f / 1125f) // Landscape }; /// <summary> /// Normalised safe areas for iPhone Xs Max with Home indicator (ratios are identical to XR, 11, 11 Pro Max). Absolute values: /// PortraitU x=0, y=102, w=1242, h=2454 on full extents w=1242, h=2688; /// PortraitD x=0, y=102, w=1242, h=2454 on full extents w=1242, h=2688 (not supported, remains in Portrait Up); /// LandscapeL x=132, y=63, w=2424, h=1179 on full extents w=2688, h=1242; /// LandscapeR x=132, y=63, w=2424, h=1179 on full extents w=2688, h=1242. /// Aspect Ratio: ~19.5:9. /// </summary> Rect[] NSA_iPhoneXsMax = new Rect[] { new Rect (0f, 102f / 2688f, 1f, 2454f / 2688f), // Portrait new Rect (132f / 2688f, 63f / 1242f, 2424f / 2688f, 1179f / 1242f) // Landscape }; /// <summary> /// Normalised safe areas for Pixel 3 XL using landscape left. Absolute values: /// PortraitU x=0, y=0, w=1440, h=2789 on full extents w=1440, h=2960; /// PortraitD x=0, y=0, w=1440, h=2789 on full extents w=1440, h=2960; /// LandscapeL x=171, y=0, w=2789, h=1440 on full extents w=2960, h=1440; /// LandscapeR x=0, y=0, w=2789, h=1440 on full extents w=2960, h=1440. /// Aspect Ratio: 18.5:9. /// </summary> Rect[] NSA_Pixel3XL_LSL = new Rect[] { new Rect (0f, 0f, 1f, 2789f / 2960f), // Portrait new Rect (0f, 0f, 2789f / 2960f, 1f) // Landscape }; /// <summary> /// Normalised safe areas for Pixel 3 XL using landscape right. Absolute values and aspect ratio same as above. /// </summary> Rect[] NSA_Pixel3XL_LSR = new Rect[] { new Rect (0f, 0f, 1f, 2789f / 2960f), // Portrait new Rect (171f / 2960f, 0f, 2789f / 2960f, 1f) // Landscape }; #endregion RectTransform Panel; Rect LastSafeArea = new Rect (0, 0, 0, 0); Vector2Int LastScreenSize = new Vector2Int (0, 0); ScreenOrientation LastOrientation = ScreenOrientation.AutoRotation; [SerializeField] bool ConformX = true; // Conform to screen safe area on X-axis (default true, disable to ignore) [SerializeField] bool ConformY = true; // Conform to screen safe area on Y-axis (default true, disable to ignore) [SerializeField] bool Logging = false; // Conform to screen safe area on Y-axis (default true, disable to ignore) void Awake () { Panel = GetComponent<RectTransform> (); if (Panel == null) { Debug.LogError ("Cannot apply safe area - no RectTransform found on " + name); Destroy (gameObject); } Refresh (); } void Update () { Refresh (); } void Refresh () { Rect safeArea = GetSafeArea (); if (safeArea != LastSafeArea || Screen.width != LastScreenSize.x || Screen.height != LastScreenSize.y || Screen.orientation != LastOrientation) { // Fix for having auto-rotate off and manually forcing a screen orientation. // See https://forum.unity.com/threads/569236/#post-4473253 and https://forum.unity.com/threads/569236/page-2#post-5166467 LastScreenSize.x = Screen.width; LastScreenSize.y = Screen.height; LastOrientation = Screen.orientation; ApplySafeArea (safeArea); } } Rect GetSafeArea () { Rect safeArea = Screen.safeArea; if (Application.isEditor && Sim != SimDevice.None) { Rect nsa = new Rect (0, 0, Screen.width, Screen.height); switch (Sim) { case SimDevice.iPhoneX: if (Screen.height > Screen.width) // Portrait nsa = NSA_iPhoneX[0]; else // Landscape nsa = NSA_iPhoneX[1]; break; case SimDevice.iPhoneXsMax: if (Screen.height > Screen.width) // Portrait nsa = NSA_iPhoneXsMax[0]; else // Landscape nsa = NSA_iPhoneXsMax[1]; break; case SimDevice.Pixel3XL_LSL: if (Screen.height > Screen.width) // Portrait nsa = NSA_Pixel3XL_LSL[0]; else // Landscape nsa = NSA_Pixel3XL_LSL[1]; break; case SimDevice.Pixel3XL_LSR: if (Screen.height > Screen.width) // Portrait nsa = NSA_Pixel3XL_LSR[0]; else // Landscape nsa = NSA_Pixel3XL_LSR[1]; break; default: break; } safeArea = new Rect (Screen.width * nsa.x, Screen.height * nsa.y, Screen.width * nsa.width, Screen.height * nsa.height); } return safeArea; } void ApplySafeArea (Rect r) { LastSafeArea = r; // Ignore x-axis? if (!ConformX) { r.x = 0; r.width = Screen.width; } // Ignore y-axis? if (!ConformY) { r.y = 0; r.height = Screen.height; } // Check for invalid screen startup state on some Samsung devices (see below) if (Screen.width > 0 && Screen.height > 0) { // Convert safe area rectangle from absolute pixels to normalised anchor coordinates 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; // Fix for some Samsung devices (e.g. Note 10+, A71, S20) where Refresh gets called twice and the first time returns NaN anchor coordinates // See https://forum.unity.com/threads/569236/page-2#post-6199352 if (anchorMin.x >= 0 && anchorMin.y >= 0 && anchorMax.x >= 0 && anchorMax.y >= 0) { Panel.anchorMin = anchorMin; Panel.anchorMax = anchorMax; } } if (Logging) { Debug.LogFormat ("New safe area applied to {0}: x={1}, y={2}, w={3}, h={4} on full extents w={5}, h={6}", name, r.x, r.y, r.width, r.height, Screen.width, Screen.height); } } } }

其核心逻辑是通过Screen.width, Screen.height获取屏幕宽高,通过Screen.safeArea获取安全区,再通过将safeArea的宽高除以Screen.width, Screen.height获取锚点的Min和Max的x,y值,从而最终将gameObject的位置和尺寸设置好。

SafeArea的核心逻辑如下,在实际项目中即使不适用SafeArea Helper插件,自己写SafeArea脚本,这段逻辑基本都会应用

if (Screen.width > 0 && Screen.height > 0) { // Convert safe area rectangle from absolute pixels to normalised anchor coordinates 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; // Fix for some Samsung devices (e.g. Note 10+, A71, S20) where Refresh gets called twice and the first time returns NaN anchor coordinates // See https://forum.unity.com/threads/569236/page-2#post-6199352 if (anchorMin.x >= 0 && anchorMin.y >= 0 && anchorMax.x >= 0 && anchorMax.y >= 0) { Panel.anchorMin = anchorMin; Panel.anchorMax = anchorMax; } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 17:37:46

一个UI内置组件el-scrollbar

el-scrollbar自动识别滚动&#xff1a;只要包裹的内容超出自身设定的高度 / 宽度&#xff0c;会自动显示垂直 / 水平滚动条&#xff0c;内容未超出则隐藏&#xff1b;隐藏水平滚动条

作者头像 李华
网站建设 2026/4/23 11:48:43

收藏备用|2026届校招AI人才需求报告解读,程序员小白必看!

对于正在备战2026届校招的程序员小白、AI方向学习者来说&#xff0c;摸清行业需求、找准发力点至关重要&#xff01;《2026届校招市场AI人才需求报告》明确指出&#xff0c;高科技企业已成为AI人才需求的核心力量&#xff0c;近60%企业将AI人才纳入核心招聘目标。当前AI人才需求…

作者头像 李华
网站建设 2026/4/23 11:53:11

服务器端请求伪造(SSRF)漏洞解析

免责申明 **本文仅是用于学习检测自己搭建的靶场环境有关SSRF的原理和攻击实验&#xff0c;请勿用在非法途径上&#xff0c;若将其用于非法目的&#xff0c;所造成的一切后果由您自行承担&#xff0c;产生的一切风险和后果与笔者无关&#xff1b;本文开始前请认真详细学习《‌中…

作者头像 李华
网站建设 2026/4/23 11:53:20

AIGC降重全指南:从工具选择到完美定稿

面对AIGC降重这一新事物&#xff0c;许多作者从好奇到尝试&#xff0c;再到遇到问题。本指南旨在提供一个从工具选择、实操步骤到最终校验的完整路线图&#xff0c;助您平稳高效地完成论文降重。 01 第一步&#xff1a;理解工具——选择真正的“智能”降重 并非所有标榜AIGC的…

作者头像 李华
网站建设 2026/4/23 11:53:29

从此告别拖延,AI论文平台千笔AI VS 锐智 AI

随着人工智能技术的迅猛迭代与普及&#xff0c;AI辅助写作工具已逐步渗透到高校学术写作场景中&#xff0c;成为专科生、本科生、研究生完成毕业论文不可或缺的辅助手段。越来越多面临毕业论文压力的学生&#xff0c;开始依赖各类AI工具简化写作流程、提升创作效率。但与此同时…

作者头像 李华