2026-10-19 18:41:43 +02:00
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
// Подгоняет RectTransform под безопасную зону экрана (Screen.safeArea): вырез камеры, скруглённые углы,
|
|
|
|
|
// полоска "домой" на iPhone. Следит за поворотом и сменой разрешения (и в Device Simulator).
|
2026-10-27 17:02:04 +01:00
|
|
|
// Вешать на растянутый объект; всё, что внутри, не залезет под вырез.
|
|
|
|
|
// - fitInsideParent = false: объект растянут на весь Canvas (HUD);
|
|
|
|
|
// - fitInsideParent = true: родитель не на весь экран (окно между верхней и нижней панелью) --
|
|
|
|
|
// зона пересчитывается в координаты родителя, отступ появляется только там, где родитель заходит под вырез.
|
2026-10-19 18:41:43 +02:00
|
|
|
[RequireComponent(typeof(RectTransform))]
|
|
|
|
|
public class SafeArea : MonoBehaviour
|
|
|
|
|
{
|
2026-10-27 17:02:04 +01:00
|
|
|
public bool fitInsideParent;
|
|
|
|
|
|
2026-10-19 18:41:43 +02:00
|
|
|
private RectTransform rect;
|
|
|
|
|
private Rect appliedArea;
|
|
|
|
|
private Vector2Int appliedScreen;
|
2026-10-27 17:02:04 +01:00
|
|
|
private Rect appliedParent;
|
2026-10-19 18:41:43 +02:00
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
{
|
|
|
|
|
rect = GetComponent<RectTransform>();
|
|
|
|
|
Apply();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Update()
|
|
|
|
|
{
|
2026-10-27 17:02:04 +01:00
|
|
|
if (Screen.safeArea != appliedArea || Screen.width != appliedScreen.x || Screen.height != appliedScreen.y
|
|
|
|
|
|| (fitInsideParent && ParentScreenRect() != appliedParent))
|
2026-10-19 18:41:43 +02:00
|
|
|
Apply();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Apply()
|
|
|
|
|
{
|
|
|
|
|
Rect area = Screen.safeArea;
|
|
|
|
|
appliedArea = area;
|
|
|
|
|
appliedScreen = new Vector2Int(Screen.width, Screen.height);
|
|
|
|
|
if (Screen.width <= 0 || Screen.height <= 0) return;
|
|
|
|
|
|
2026-10-27 17:02:04 +01:00
|
|
|
if (!fitInsideParent)
|
|
|
|
|
{
|
|
|
|
|
// Якоря в долях экрана: Canvas в режиме Screen Space Overlay покрывает экран целиком
|
|
|
|
|
rect.anchorMin = new Vector2(area.xMin / Screen.width, area.yMin / Screen.height);
|
|
|
|
|
rect.anchorMax = new Vector2(area.xMax / Screen.width, area.yMax / Screen.height);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Rect parent = ParentScreenRect();
|
|
|
|
|
appliedParent = parent;
|
|
|
|
|
if (parent.width <= 0 || parent.height <= 0) return;
|
|
|
|
|
|
|
|
|
|
// Пересечение родителя с безопасной зоной -- в долях родителя
|
|
|
|
|
float xMin = Mathf.Max(parent.xMin, area.xMin), xMax = Mathf.Min(parent.xMax, area.xMax);
|
|
|
|
|
float yMin = Mathf.Max(parent.yMin, area.yMin), yMax = Mathf.Min(parent.yMax, area.yMax);
|
|
|
|
|
if (xMax <= xMin || yMax <= yMin) return;
|
|
|
|
|
rect.anchorMin = new Vector2((xMin - parent.xMin) / parent.width, (yMin - parent.yMin) / parent.height);
|
|
|
|
|
rect.anchorMax = new Vector2((xMax - parent.xMin) / parent.width, (yMax - parent.yMin) / parent.height);
|
|
|
|
|
}
|
2026-10-19 18:41:43 +02:00
|
|
|
rect.offsetMin = Vector2.zero;
|
|
|
|
|
rect.offsetMax = Vector2.zero;
|
|
|
|
|
}
|
2026-10-27 17:02:04 +01:00
|
|
|
|
|
|
|
|
// Прямоугольник родителя в пикселях экрана
|
|
|
|
|
Rect ParentScreenRect()
|
|
|
|
|
{
|
|
|
|
|
var parent = transform.parent as RectTransform;
|
|
|
|
|
if (parent == null) return new Rect(0, 0, Screen.width, Screen.height);
|
|
|
|
|
|
|
|
|
|
var corners = new Vector3[4];
|
|
|
|
|
parent.GetWorldCorners(corners);
|
|
|
|
|
Canvas canvas = parent.GetComponentInParent<Canvas>();
|
|
|
|
|
Camera cam = canvas != null && canvas.renderMode != RenderMode.ScreenSpaceOverlay ? canvas.worldCamera : null;
|
|
|
|
|
Vector2 min = RectTransformUtility.WorldToScreenPoint(cam, corners[0]);
|
|
|
|
|
Vector2 max = RectTransformUtility.WorldToScreenPoint(cam, corners[2]);
|
|
|
|
|
return Rect.MinMaxRect(min.x, min.y, max.x, max.y);
|
|
|
|
|
}
|
2026-10-19 18:41:43 +02:00
|
|
|
}
|