【UGUI】使用Screen.safeArea适配全面屏/异形屏

本文介绍如何在Unity中利用Screen.safeArea进行异形屏适配的方法,通过计算屏幕顶部异形区域大小并调整UI界面的偏移量,确保UI元素不被遮挡。

Unity中使用Screen.safeArea适配全面屏/异形屏

 

屏幕高度(即屏幕最高点):Screen.height,安全区域最高点:Screen.safeArea.yMax,屏幕顶部异形区域大小:Screen.height - Screen.safeArea.yMax

(安全区域最低点:Screen.safeArea.yMin,可用于屏幕底部适配(苹果Touch Bar))

        //计算屏幕顶部异形大小
        int safeAreaOffset = (int)(Screen.height - Screen.safeArea.yMax);
        //UI偏移
        RectTransform rectTransform = transform.Find("SafeArea").GetComponent<RectTransform>();
        rectTransform.offsetMax = new Vector2(0,-safeAreaOffset);

TestPanel:UI界面根节点,添加背景图片,可忽略异形屏的影响将图片填充满屏幕

SafeArea:安全区域根节点,在UI被加载后执行以上代码,可以根据异形屏大小动态设置顶部偏移量,避免被异形屏遮挡

     

 未广泛测试机型,有待验证! 还可以根据设备类型(SystemInfo.deviceModel)、设备名称(SystemInfo.deviceName),手动设置每个机型的偏移量

NullReferenceException: Object reference not set to an instance of an object HistoryPanelController.SetHistoryData (System.Collections.Generic.List`1[T] history) (at Assets/money_scrip/HistoryPanelController.cs:25) GachaMenuController.OnHistoryButtonClick () (at Assets/money_scrip/GachaMenuController.cs:41) UnityEngine.Events.InvokableCall.Invoke () (at /Volumes/jenkins1/sharedspace/ra_2022.3/Runtime/Export/UnityEvent/UnityEvent.cs:178) UnityEngine.Events.UnityEvent.Invoke () (at /Volumes/jenkins1/sharedspace/ra_2022.3/artifacts/generated/UnityEvent/UnityEvent_0.cs:57) UnityEngine.UI.Button.Press () (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70) UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114) UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57) UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272) UnityEngine.EventSystems.EventSystem:Update() (at ./Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:530)
最新发布
08-28
<think>我们遇到了一个NullReferenceException,发生在HistoryPanelController.SetHistoryData的第25行。根据堆栈信息,这个错误是在调用GachaMenuController.OnHistoryButtonClick时发生的。 分析: 错误信息:Object reference not set to an instance of an object 这意味着我们在使用一个未初始化的对象引用(null)时发生了错误。 查看代码: HistoryPanelController.SetHistoryData的第25行是: for (int i = history.Count - 1; i >= 0; i--) 可能的原因: 1. history参数为null:当调用SetHistoryData时传入的history是null,那么history.Count就会抛出异常。 2. 循环内部的某个对象为null:但是在第25行,我们只使用了history.Count,所以问题应该出在history本身。 再看调用者: GachaMenuController.OnHistoryButtonClick() 中调用了SetHistoryData。 因此,我们需要检查GachaMenuController中调用SetHistoryData时传入的history是否为null。 解决方案: 1. 在SetHistoryData方法开始时检查history是否为null: if (history == null) { Debug.LogError("History data is null!"); return; } 2. 在GachaMenuController中确保传入的history列表不为null。 另外,在GachaMenuController的第41行(调用SetHistoryData的地方)也需要检查: public void OnHistoryButtonClick() { // 确保gachaSystem.history不为null if (gachaSystem.history == null) { gachaSystem.history = new List<GachaSystem.GachaRecord>(); } historyPanel.SetHistoryData(gachaSystem.history); } 但是,我们还需要注意,historyPanel是否被正确赋值?如果historyPanel为null,那么在调用SetHistoryData之前就会抛出异常(在调用方法时,因为historyPanel是null)。但是堆栈显示异常是在SetHistoryData内部发生的,所以暂时排除historyPanel为null的情况。 因此,重点在于:确保传入的history列表不为null。 修改后的HistoryPanelController.SetHistoryData方法: ```csharp public void SetHistoryData(List<GachaSystem.GachaRecord> history) { // 检查history是否为null if (history == null) { Debug.LogError("Received null history data!"); return; } // 清除现有内容 foreach (Transform child in contentParent) { Destroy(child.gameObject); } // 从后向前遍历(这样最新的记录会显示在最上面?) for (int i = history.Count - 1; i >= 0; i--) { var record = history[i]; // 实例化记录项 GameObject item = Instantiate(recordItemPrefab, contentParent); // 设置记录项内容 Text rarityText = item.transform.Find("RarityText").GetComponent<Text>(); Text nameText = item.transform.Find("NameText").GetComponent<Text>(); Text timeText = item.transform.Find("TimeText").GetComponent<Text>(); // 检查这些Text组件是否找到,如果某个子对象不存在,transform.Find会返回null,然后GetComponent就会抛出NullReferenceException if (rarityText == null || nameText == null || timeText == null) { Debug.LogError("One of the text components is missing in the record item prefab."); continue; } rarityText.text = GetRarityDisplayName(record.rarity); nameText.text = record.spriteName; timeText.text = record.drawTime.ToString("MM/dd HH:mm"); rarityText.color = GetRarityColor(record.rarity); } } ``` 另外,我们还需要确保在GachaMenuController中,historyPanel被正确赋值。如果historyPanel是null,那么在调用SetHistoryData之前就会报错(在GachaMenuController的第41行:historyPanel.SetHistoryData(...))。但是堆栈信息显示异常发生在SetHistoryData内部,所以这里我们主要处理history为null的情况。 但是,为了安全,我们可以在GachaMenuController中检查historyPanel: public GachaSystem gachaSystem; public HistoryPanelController historyPanel; // 是否在Inspector中赋值? void OnHistoryButtonClick() { if (historyPanel == null) { Debug.LogError("historyPanel is not assigned!"); return; } // ... 同上 } 总结可能的原因和修复: 1. 传入的history为null -> 在调用SetHistoryData之前初始化它。 2. 记录项预制体中的子对象名称不匹配(例如,找不到"RarityText"等)-> 使用检查确保,并在代码中添加安全判断。 根据错误信息,我们首先修复history为null的情况,然后确保预制体的结构正确。 如果问题仍然存在,我们需要进一步检查: - 在SetHistoryData中,是否contentParent为null?但如果是这样,在Instantiate时就会报错,而错误发生在第25行(循环条件),所以排除。 - 循环条件中的history.Count:如果history为null,就会抛出NullReferenceException,所以我们已经添加了null检查。 因此,请按照上述修改代码,并确保在GachaMenuController中传入有效的history列表。 注意:在GachaMenuController中,我们如何获取historyPanel?如果它是通过Instantiate实例化的,那么需要确保将实例的引用赋给GachaMenuController中的historyPanel变量。 例如,在GachaMenuController中: public GameObject historyPanelPrefab; private HistoryPanelController historyPanel; public void OnHistoryButtonClick() { // 如果还没有打开记录面板,则实例化 if (historyPanel == null) { GameObject panelObj = Instantiate(historyPanelPrefab, transform.parent); // 假设在同一个Canvas下 historyPanel = panelObj.GetComponent<HistoryPanelController>(); } // 设置数据 historyPanel.SetHistoryData(gachaSystem.history ?? new List<GachaSystem.GachaRecord>()); } 或者,如果historyPanel是已经存在的对象(比如通过SetActive(false/true)来显示隐藏),那么确保在Inspector中将其拖拽赋值。 综上所述,我们主要进行两处修改: 1. 在SetHistoryData方法开头添加对history的null检查。 2. 在调用SetHistoryData的地方(GachaMenuController)确保传入的history不为null。 如果问题仍然存在,请检查history是否在循环过程中被其他线程修改?但Unity是单线程的,所以通常不会。另外,确保history列表在传入后没有被清空或置为null。 最后,注意错误堆栈中的行号可能因为代码修改而改变,请根据实际代码行定位问题。 修改后的代码示例见上。 如果按照上述修改后问题解决,则完成;否则,需要进一步调试。 由于错误发生在for循环的条件判断,所以基本确定是history为null导致。因此,重点处理null的情况。 另外,在GachaSystem中,确保history列表在初始化时被创建: public class GachaSystem : MonoBehaviour { public List<GachaRecord> history = new List<GachaRecord>(); // ... } 这样,在GachaMenuController中就可以直接使用gachaSystem.history(即使没有抽卡记录,也是一个空列表而不是null)。 因此,在GachaSystem中初始化history列表也是重要的。 步骤总结: 1. 在GachaSystem中初始化history列表(使用new List<>())。 2. 在GachaMenuController.OnHistoryButtonClick中,可以不检查null,因为已经初始化,但为了安全可以加上。 3. 在HistoryPanelController.SetHistoryData中,加上null检查(双重保险)。 这样应该能解决NullReferenceException的问题。</think>### 解决 NullReferenceException 错误详解 #### 错误分析 错误发生在 `HistoryPanelController.SetHistoryData()` 第25行: ```csharp for (int i = history.Count - 1; i >= 0; i--) ``` 根本原因是 `history` 参数为 **null**。当调用 `SetHistoryData()` 时传入的抽卡记录列表为空引用。 #### 完整解决方案 ##### 1. 修复 GachaMenuController 调用逻辑 ```csharp // 在 GachaMenuController.cs 中 public void OnHistoryButtonClick() { // 确保历史记录不为空 if (gachaSystem.history == null || gachaSystem.history.Count == 0) { Debug.LogWarning("抽卡记录为空"); return; } // 确保历史记录面板已初始化 if (historyPanel == null) { historyPanel = Instantiate(historyPanelPrefab, transform.parent).GetComponent<HistoryPanelController>(); } // 传入有效数据 historyPanel.SetHistoryData(new List<GachaSystem.GachaRecord>(gachaSystem.history)); } ``` ##### 2. 增强 HistoryPanelController 的健壮性 ```csharp public void SetHistoryData(List<GachaSystem.GachaRecord> history) { // 添加空值检查 if (history == null) { Debug.LogError("传入的历史记录为null"); return; } // 添加内容父物体检查 if (contentParent == null) { Debug.LogError("ContentParent未分配"); return; } // 添加预制体检查 if (recordItemPrefab == null) { Debug.LogError("记录项预制体未分配"); return; } // 安全清除内容 foreach (Transform child in contentParent) { if (child != null) Destroy(child.gameObject); } // 处理空记录 if (history.Count == 0) { CreateEmptyMessage(); return; } // 安全创建记录项 for (int i = history.Count - 1; i >= 0; i--) { var record = history[i]; if (record == null) continue; CreateRecordItem(record); } } // 创建空记录提示 private void CreateEmptyMessage() { GameObject emptyMsg = new GameObject("EmptyMessage"); emptyMsg.transform.SetParent(contentParent); Text text = emptyMsg.AddComponent<Text>(); text.text = "暂无抽卡记录"; text.font = Resources.GetBuiltinResource<Font>("Arial.ttf"); text.color = Color.gray; text.alignment = TextAnchor.MiddleCenter; RectTransform rt = emptyMsg.GetComponent<RectTransform>(); rt.anchorMin = new Vector2(0.5f, 0.5f); rt.anchorMax = new Vector2(0.5f, 0.5f); rt.pivot = new Vector2(0.5f, 0.5f); rt.sizeDelta = new Vector2(300, 100); } ``` ##### 3. 确保数据初始化 在抽卡系统核心代码中: ```csharp public class GachaSystem : MonoBehaviour { // 确保历史记录列表初始化 public List<GachaRecord> history = new List<GachaRecord>(); void Start() { // 加载保存的记录 LoadHistory(); } private void LoadHistory() { if (PlayerPrefs.HasKey("GachaHistory")) { string json = PlayerPrefs.GetString("GachaHistory"); history = JsonUtility.FromJson<List<GachaRecord>>(json); } } } ``` ##### 4. Unity 编辑器配置检查 1. 在 Hierarchy 中选中 HistoryPanel 预制体 2. 检查 Inspector 中的组件引用: - 确保 `Content Parent` 指向 ScrollView/Viewport/Content - 确保 `Record Item Prefab` 指向正确的预制体 3. 在 GachaMenuController 上: - 确保 `History Panel Prefab` 已分配 - 确保 `Gacha System` 引用正确 #### 调试技巧 1. **添加调试日志**: ```csharp Debug.Log($"历史记录数量: {history?.Count ?? 0}"); ``` 2. **使用条件断点**: 在 `SetHistoryData()` 第一行设置断点: - 条件:`history == null` - 操作:记录调用堆栈 3. **安全访问运算符**: ```csharp // 替代 history.Count int count = history?.Count ?? 0; ``` #### 预防措施 1. **添加 [RequireComponent] 属性**: ```csharp [RequireComponent(typeof(ScrollRect))] public class HistoryPanelController : MonoBehaviour ``` 2. **使用 SerializeField 代替 public**: ```csharp [SerializeField] private Transform contentParent; [SerializeField] private GameObject recordItemPrefab; ``` 3. **实现编辑器验证**: ```csharp #if UNITY_EDITOR void OnValidate() { if (contentParent == null) { contentParent = transform.Find("ScrollView/Viewport/Content"); } } #endif ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萧然CS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值