RectTransformUtility, ou como criar um componente que anima elementos da interface do usuário atrás da tela

No último artigo - Variedades de coordenadas usadas na GUI do Unity3d, tentei falar brevemente sobre as variedades de coordenadas no Unity UI / RectTransform. Agora, quero destacar algo tão útil para a interface do usuário como RectTransformUtility. Qual é uma das principais ferramentas para calcular algo na interface do usuário em relação a outra coisa.

Desafio simples


Existe uma tarefa - você precisa de um componente que remova animadamente o elemento da interface do usuário além da borda selecionada da tela. O componente deve estar roxo onde está localizado hierarquicamente, em que locais as âncoras estão, qual o tamanho da tela e em que local da tela está. O componente deve poder limpar o objeto em 4 lados (cima, baixo, esquerda, direita) por um determinado tempo.

Reflexões

Em princípio, como isso pode ser feito? Descubra o tamanho da tela nas coordenadas do objeto, mova o objeto para a coordenada além da borda da tela e parece que o problema está no chapéu. Mas há alguns, mas:

Como descobrir as coordenadas da tela em relação à interface do usuário?

Se você pesquisar na testa, pesquise no Google algumas coisas sem sentido ou inúteis, ou até mesmo perguntas sem resposta. A coisa mais próxima que combina com você é quando algum elemento da interface do usuário segue o cursor, que existe apenas nas coordenadas da tela.

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

Estes são RectTransformUtility e ScreenPointToLocalPointInRectangle diretamente. Aqui, obtemos as coordenadas locais dentro do rect (RectTransform), com base na posição do ponto na tela.
No exemplo atual, encontramos as coordenadas locais do cursor do mouse, precisamos substituí-las pela borda da tela:

 RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, new Vector2(Screen.width, Screen.height), null, out topRightLocalCoord); 

E assim obtivemos a coordenada do ponto superior direito da tela para que o objeto deixe a tela à direita, nosso objeto deve estar mais além desse ponto + digamos a largura do retângulo ou o recuo especificado.

Então, a primeira ressalva

Temos coordenadas locais adequadas para objetos diretamente dentro da tela, se o rio a ser deslocado estiver em outro reto, suas coordenadas locais serão consideradas relativas ao pai, não à tela. Ou seja, essas próprias coordenadas não nos convêm.

Existem duas maneiras , a primeira é usar coordenadas globais, pois elas são globais. Ou calcule as coordenadas da tela nas coordenadas locais de cada retângulo separadamente.

Considere o primeiro caso - como converter coordenadas locais em globais.

A maioria dos métodos pesquisados ​​usa - TransformPoint.

 transform.position = myCanvas.transform.TransformPoint(pos); 

Assim, convertemos coordenadas locais em globais.

Na minha opinião, isso geralmente é uma etapa extra, pois RectTransformUtility tem um método ScreenPointToWorldPointInRectangle que retorna imediatamente a posição global.

Precisamos deslocar o retângulo além da borda direita da tela, para isso pegamos a coordenada X da posição encontrada e Y deixamos o retângulo que movemos para que ele simplesmente se mova para a direita.

 new Vector3(topRightCoord.x+offset, rectTransform.position.y, 0); 

A coordenada resultante é alimentada por DoTween.

 rectTransform.DOMove(new Vector3(correctedTargetRight.x, rectTransform.position.y, 0), timeForHiding); 

E viva, o objeto sai para a direita. Mas ...

Segunda nuance

Aqui descobrimos que, de fato, o posicionamento do reto depende do pivô do reto.



Portanto, o objeto pode dançar com o posicionamento, dependendo do pivô, e o objeto pode ser muito grande e o deslocamento não o empurrará completamente para trás da tela, sempre haverá uma chance de que a peça fique destacada.

Ou seja, precisamos aparafusar a compensação para compensar, o que levará em consideração o tamanho do rect + pivô.

A segunda nuance é mover o objeto pelo tamanho do retângulo, você precisa conhecer as coordenadas locais ou âncoras e obteremos coordenadas globais. Devo dizer imediatamente que as coordenadas globais não podem ser obtidas e convertidas em coordenadas locais da interface do usuário ou em ancoradas.
Eu vim com a muleta a seguir, lembramos a posição inicial do reto, movemos para a posição global final, mudamos a posição da âncora pelo tamanho do reto para a direita, lembramos a posição global que leva em consideração o deslocamento, levando em conta o tamanho do objeto e o alimentamos com uma cavidade, sem esquecer de retornar ao original posição.

Exemplo de código
  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); 


Parece uma muleta gigante, mas essa muleta permite sincronizar coordenadas globais e outras. Isso ajuda quando há objetos na interface que se movem em relação um ao outro e eles estão em hierarquias diferentes. Bem, mais até agora, essa é a única maneira que encontrei para obter coordenadas retas do global.

Nesse ponto, dizeremos não às muletas e voltaremos à ideia de obter o tamanho da tela nas coordenadas locais.

Segunda via


A segunda maneira é obter os tamanhos de tela para cada retângulo individualmente, para que possamos conhecer as coordenadas locais das bordas da tela, independentemente da tela e da hierarquia.

Terceira nuance

 RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, new Vector2(Screen.width, Screen.height), null, out topRightCoord); RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, new Vector2(0, 0), null, out bottomScreenCoord); 

Os objetos podem estar localizados em qualquer lugar da tela, diferentemente da tela que cobre a tela inteira. Portanto, as distâncias para as bordas esquerda e direita da tela podem diferir significativamente. No caso da tela, teríamos apenas a borda superior direita e, menos a parte superior direita, seria a parte superior esquerda. Nesse caso, você precisa obter os pontos inferior esquerdo e superior direito separadamente, conforme mostrado no exemplo de código.

Quarta nuance

A coordenada local é o deslocamento em relação ao centro do pai, quando o ret é incorporado em outro ret, que ocupa uma pequena parte da tela, precisamos de uma coordenada que leve em consideração os dois deslocamentos, bem, tudo é simples.

 ((Vector3)bottomLeftCoord + rectTransform.localPosition) 

adicione os vetores e obtenha a coordenada de que precisamos. Parece mais confuso do que com as coordenadas globais, mas agora podemos realizar qualquer cálculo relacionado ao tamanho do retângulo. E calmamente, finalmente, adicione compensação sem muletas.

  (Vector3)topRightCoord + rectTransform.localPosition + (new Vector3((rectTransform.sizeDelta.x * rectTransform.pivot.x) + rectTransform.sizeDelta.x, 0, 0)); 

Aqui está a coordenada para deslocar para a direita com compensação pela largura do reto e deslocar além da tela para a largura do reto, não há como definir o deslocamento, pretendo adicioná-lo um pouco mais tarde, mas acho que alguém estará interessado em tentar escrever isso sozinho.

Conclusões


  1. Para elementos da interface do usuário, é melhor usar coordenadas locais ou âncoras e tente entendê-las. As coordenadas globais podem ser usadas para casos especiais, mas não permitem trabalhar convenientemente, por exemplo, com os tamanhos de rects e em muitos outros micr episódios.
  2. Você precisa observar o RectTransformUtility, ele possui muitas funcionalidades úteis para a interface do usuário; todos os cálculos relacionados à posição de algo dentro e ao redor do retângulo são feitos através dele.

Bem, o próprio componente, se alguém quiser brincar com ele, o DoTween será necessário para isso:

Componente
 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; } } 

Source: https://habr.com/ru/post/pt457142/


All Articles