Recttransformutility, Или Как Сделать Компонент, Который Анимированно Перемещает Элементы Пользовательского Интерфейса За Пределы Экрана

В предыдущей статье - Типы координат, используемые в графическом интерфейсе Unity3d Я попытался кратко рассказать о типах координат в Unity UI/RectTransform. Теперь мне хотелось бы пролить свет на такую полезную для пользовательского интерфейса вещь, как RectTransformUtility. Это один из основных инструментов для расчета чего-либо в пользовательском интерфейсе относительно чего-то еще.



Простая и сложная задача

Есть задача — нужен компонент, который анимированно убирает UI-элемент с выбранного края экрана.

Компонент должен быть показан иерархически, где находятся привязки, какой размер экрана и где на экране он расположен.

Компонент должен иметь возможность удалять объект в 4 направлениях (вверх, вниз, влево, вправо) за заданное время.

Размышления В принципе, как это можно сделать? Узнайте размеры экрана в координатах объекта, переместите объект на координату за краем экрана и вроде бы готово.

Но есть пара вещей: Как узнать координаты экрана относительно пользовательского интерфейса? Если гуглить в лоб, то гугите какую-то ерунду или не полезные вещи, а то и вопросы без ответов.

Ближе всего подходят случаи, когда за курсором следует какой-то элемент пользовательского интерфейса, который точно существует в экранных координатах.

  
  
  
  
  
  
  
  
  
   

RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, new Vector2(Input.mousePosition), null, out topRightLocalCoord);

Это непосредственно RectTransformUtility и ScreenPointToLocalPointInRectangle. Здесь мы получаем локальные координаты внутри RectTransform, исходя из положения точки на экране.

В текущем примере мы находим локальные координаты курсора мыши, нам нужно заменить их на край экрана:

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);

И ура, объект уходит вправо.

Но… Второй нюанс Здесь мы узнаем, что на самом деле расположение прямоугольника зависит от его оси.



RectTransformUtility, или как сделать компонент, который анимированно перемещает элементы пользовательского интерфейса за пределы экрана

Поэтому объект может танцевать с позиционированием в зависимости от пивота, плюс объект может быть очень большим, и 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));

Вот так выглядит координата для смещения вправо с компенсацией ширины ректа и смещением за пределы экрана на ширину ректа, возможности задать смещение нет, планирую добавить чуть позже, но думаю кто-нибудь подскажет заинтересуйтесь попыткой написать это сами.



Выводы

  1. Для элементов пользовательского интерфейса лучше использовать локальные или якорные координаты, и вам следует постараться их понять.

    Глобальные координаты можно использовать для особых случаев, но они не позволяют удобно работать, например, с размерами прямоугольников и во многих других микроэпизодах.

  2. Вам нужно присмотреться к RectTransformUtility, в ней много полезного функционала для пользовательского интерфейса, все расчеты, связанные с положением чего-либо внутри и вокруг прямоугольника, производятся через него.

Ну и сам компонент, если кто захочет с ним поиграться, для этого понадобится DoTween: Компонент

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

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.