В предыдущей статье - Типы координат, используемые в графическом интерфейсе Unity3d Я попытался кратко рассказать о типах координат в Unity UI/RectTransform. Теперь мне хотелось бы пролить свет на такую полезную для пользовательского интерфейса вещь, как RectTransformUtility. Это один из основных инструментов для расчета чего-либо в пользовательском интерфейсе относительно чего-то еще.
Простая и сложная задача
Есть задача — нужен компонент, который анимированно убирает UI-элемент с выбранного края экрана.Компонент должен быть показан иерархически, где находятся привязки, какой размер экрана и где на экране он расположен.
Компонент должен иметь возможность удалять объект в 4 направлениях (вверх, вниз, влево, вправо) за заданное время.
Размышления В принципе, как это можно сделать? Узнайте размеры экрана в координатах объекта, переместите объект на координату за краем экрана и вроде бы готово.
Но есть пара вещей: Как узнать координаты экрана относительно пользовательского интерфейса? Если гуглить в лоб, то гугите какую-то ерунду или не полезные вещи, а то и вопросы без ответов.
Ближе всего подходят случаи, когда за курсором следует какой-то элемент пользовательского интерфейса, который точно существует в экранных координатах.
Это непосредственно RectTransformUtility и ScreenPointToLocalPointInRectangle. Здесь мы получаем локальные координаты внутри RectTransform, исходя из положения точки на экране.RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, new Vector2(Input.mousePosition), null, out topRightLocalCoord);
В текущем примере мы находим локальные координаты курсора мыши, нам нужно заменить их на край экрана: RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, new Vector2(Screen.width, Screen.height), null, out topRightLocalCoord);
И вот мы получили координату верхней правой точки экрана, чтобы объект съехал за пределы экрана вправо, наш объект должен находиться дальше этой точки + допустим ширина прямоугольника или заданный отступ.
Итак, первый пункт Мы получили локальные координаты, подходящие для объектов непосредственно внутри холста; если прямоугольник, который нужно сместить, лежит в другом прямоугольнике, то его локальные координаты будут рассчитываться относительно родителя, а не холста.
То есть эти координаты нам напрямую не подходят. Здесь есть два пути , во-первых, нужно использовать глобальные координаты, поэтому они глобальные.
Либо посчитать координаты экрана в локальных координатах каждого прямоугольника отдельно.
Рассмотрим первый случай — как преобразовать локальные координаты в глобальные.
Большинство методов Google используют TransformPoint. transform.position = myCanvas.transform.TransformPoint(pos);
Таким образом мы преобразуем локальные координаты в глобальные.
На мой взгляд, это вообще лишний шаг, поскольку у RectTransformUtility есть метод ScreenPointToWorldPointInRectangle, который сразу возвращает глобальную позицию.
Нам нужно вывести прямоугольник за правый край экрана, для этого мы возьмем координату X из найденной позиции, а координату Y прямоугольника, который перемещаем, оставим так, чтобы он просто перемещался вправо.
new Vector3(topRightCoord.x+offset, rectTransform.position.y, 0);
Полученную координату передаем в DoTween. rectTransform.DOMove(new Vector3(correctedTargetRight.x, rectTransform.position.y, 0), timeForHiding);
И ура, объект уходит вправо.
Но… Второй нюанс Здесь мы узнаем, что на самом деле расположение прямоугольника зависит от его оси.
Поэтому объект может танцевать с позиционированием в зависимости от пивота, плюс объект может быть очень большим, и offset не вытолкнет его полностью за пределы экрана, всегда будет шанс, что какой-то кусок вылезет наружу.
То есть нам нужно прикрепить к смещению компенсацию, которая будет учитывать размер rect+pivot. Второй нюанс — чтобы переместить объект на размер прямоугольника, нужно знать локальные или якорные координаты, а мы получаем глобальные координаты.
Скажу сразу, глобальные координаты нельзя взять и преобразовать в локальные координаты UI, или в координаты привязки.
Я придумал следующий костыль, запоминаем начальную позицию прямоугольника, перемещаем его в конечную глобальную позицию, смещаем позицию привязки на размер прямоугольника вправо, запоминаем глобальную позицию, которая уже учитывает смещение с учетом размеров объекта и подайте его на дутвину, не забывая вернуть его в исходное первое положение.
Пример кода var targetRight = new Vector3(topRightLocalCoord.x, rectTransform.position.y, 0);
rectTransform.position = targetRight;
rectTransform.anchoredPosition += rectTransform.sizeDelta;
var correctedTargetRight = rectTransform.position;
rectTransform.localPosition = startPoint;
rectTransform.DOMove(new Vector3(correctedTargetRight.x, rectTransform.position.y, 0), timeForHiding);
Это похоже на гигантский костыль, но этот костыль позволяет синхронизировать глобальные и другие координаты.
Это помогает, когда в интерфейсе есть объекты, которые перемещаются относительно друг друга и находятся в разных иерархиях.
Ну и плюс пока это единственный найденный мной способ получить прямоугольные координаты из глобальных.
На этом этапе мы скажем нет костылям и вернемся к идее получения размера экрана в локальных координатах.
Второй способ
Второй способ — получить размеры экрана для каждого прямоугольника отдельно, так мы будем знать локальные координаты краев экрана независимо от холста и иерархии.
Третий нюанс RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, new Vector2(Screen.width, Screen.height), null, out topRightCoord);
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, new Vector2(0, 0), null, out bottomScreenCoord);
Объекты могут располагаться в любом месте экрана, в отличие от холста, занимающего весь экран.
Поэтому расстояния до левого и правого края экрана могут существенно отличаться.
В случае холста нам понадобится только верхний правый край, а за вычетом верхнего правого будет верхний левый край.
В этом случае вам необходимо получить нижнюю левую и верхнюю правую точки отдельно, что показано в примере кода.
Четвертый нюанс Локальная координата — это смещение относительно центра родителя, когда прямоугольник вложен в другой прямоугольник, занимающий небольшую часть холста, то нам нужна координата, учитывающая оба смещения, ну тут все просто.
((Vector3)bottomLeftCoord + rectTransform.localPosition)
складываем векторы и получаем нужную нам координату.
Получается сложнее, чем с глобальными координатами, но теперь мы можем проводить любые вычисления, связанные с размером прямоугольника.
И спокойно наконец-то доделать компенсацию без костылей.
(Vector3)topRightCoord + rectTransform.localPosition + (new Vector3((rectTransform.sizeDelta.x * rectTransform.pivot.x) + rectTransform.sizeDelta.x, 0, 0));
Вот так выглядит координата для смещения вправо с компенсацией ширины ректа и смещением за пределы экрана на ширину ректа, возможности задать смещение нет, планирую добавить чуть позже, но думаю кто-нибудь подскажет заинтересуйтесь попыткой написать это сами.
Выводы
- Для элементов пользовательского интерфейса лучше использовать локальные или якорные координаты, и вам следует постараться их понять.
Глобальные координаты можно использовать для особых случаев, но они не позволяют удобно работать, например, с размерами прямоугольников и во многих других микроэпизодах.
- Вам нужно присмотреться к RectTransformUtility, в ней много полезного функционала для пользовательского интерфейса, все расчеты, связанные с положением чего-либо внутри и вокруг прямоугольника, производятся через него.
using DG.Tweening;
using UnityEngine;
public enum Direction { DEFAULT, RIGHT, LEFT, TOP, BOTTOM }
public enum CanvasType {OVERLAY, CAMERATYPE}
public class HideBeyondScreenComponent : MonoBehaviour
{
[SerializeField] private Direction direction;
[SerializeField] private CanvasType canvasType;
[SerializeField] private float timeForHiding = 1;
[SerializeField] private float offset = 50;
private Vector3 startPoint;
private RectTransform rectTransform;
private Vector2 topRightCoord;
private Vector2 bottomLeftCoord;
private void Start()
{
rectTransform = transform as RectTransform;
startPoint = rectTransform.localPosition;
Camera camera = null;
if (canvasType == CanvasType.CAMERATYPE)
camera = Camera.main;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, new Vector2(Screen.width, Screen.height), camera, out topRightCoord);
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, new Vector2(0, 0), camera, out bottomLeftCoord);
Hide();
}
public void Show()
{
rectTransform.DOLocalMove(startPoint, timeForHiding);
}
public void Hide()
{
switch (direction)
{
case Direction.LEFT:
rectTransform.DOLocalMove(new Vector3(EndPosition(Direction.LEFT).
x, rectTransform.localPosition.y, 0), timeForHiding);
break;
case Direction.RIGHT:
rectTransform.DOLocalMove(new Vector3(EndPosition(Direction.RIGHT).
x, rectTransform.localPosition.y, 0), timeForHiding);
break;
case Direction.TOP:
rectTransform.DOLocalMove(new Vector3(rectTransform.localPosition.x, EndPosition(Direction.TOP).
y, 0), timeForHiding);
break;
case Direction.BOTTOM:
rectTransform.DOLocalMove(new Vector3(rectTransform.localPosition.x, EndPosition(Direction.BOTTOM).
y, 0), timeForHiding);
break;
}
}
private Vector3 NegativeCompensation()
{
return new Vector2((-rectTransform.sizeDelta.x - offset) + rectTransform.sizeDelta.x * rectTransform.pivot.x,
(-rectTransform.sizeDelta.y - offset) + rectTransform.sizeDelta.y * rectTransform.pivot.y);
}
private Vector3 PositiveCompensation()
{
return new Vector2((rectTransform.sizeDelta.x * rectTransform.pivot.x) + offset,
(rectTransform.sizeDelta.y * rectTransform.pivot.y) + offset);
}
private Vector2 EndPosition(Direction direction)
{
switch (direction)
{
case Direction.LEFT:
return ((Vector3)bottomLeftCoord + rectTransform.localPosition) + NegativeCompensation();
case Direction.RIGHT:
return (Vector3)topRightCoord + rectTransform.localPosition + PositiveCompensation();
case Direction.TOP:
return ((Vector3)topRightCoord + rectTransform.localPosition) + PositiveCompensation();
case Direction.BOTTOM:
return ((Vector3)bottomLeftCoord + rectTransform.localPosition) + NegativeCompensation();
}
return startPoint;
}
}
Теги: #Разработка игр #unity #ui #разработка игр #unity3d
-
Понимание Пакета Go: Io
19 Oct, 24 -
Научная Конференция «Летняя Школа»
19 Oct, 24 -
4 Парадокса Квантовой Физики
19 Oct, 24 -
Создано В .Net
19 Oct, 24