아래와 같은 내용을 다룬다.
트랜지션 애니메이션은 어떤 속성의 상태나 값에 변화가 생겼을때 발생한다.

현재 트랜지션 애니메이션의 duration을 가진 상태에서 지금과는 다른 상태나 값을 가진 클래스가 추가되면 트랜지션 애니메이션이 발생한다. 이러한 작동 방식을 이용해서 남자 우주인을 애니메이션을 해보자. 아래와 같이 서로 다른 상태를 가진 스타일을 만들고 시작할 때 스타일을 바꿔주면 된다.

Transition Animation의 Duration에 0이 아닌 값을 주고 스타일 클래스에 추가해준다.

이제 Transform의 translate를 사용해 공중에 떠있는 상태를 만들고 클래스를 추가해준다.

private void Start() {
//생략
// Boy Animation
AnimateBoy();
}
private void AnimateBoy() {
_boy.RemoveFromClassList("image--boy--inair");
}
Play 해도 트랜지션 애니메이션이 실행이 안되는데 Debug 해보고 싶으면 Update 안에 아래와 같은 함수를 이용하면 된다. ( bool 반환 )
UI요소.ClassListContains("있는지 확인할 클래스")
확인해보면 처음부터 image--boy--inair 클래스가 없다고 뜬다. 왜 이럴까?

씬의 첫 번째 프레임에는 그 이전 상태가 존재하지 않기 때문에 첫 번째 프레임 이후에 트랜지션 애니메이션을 실행하라고 공식 문서에 적혀있다. 따라서 Invoke로 0.1초 정도 기다렸다가 실행하도록 한다.
// Boy Animation
Invoke("AnimateBoy", .1f);
이제 트랜지션 애니메이션이 잘 실행된다.

여성이 출력되는 TV 이미지가 위 아래로 계속 움직일 수 있게 해보자. 위와 동일하게 Duration만 1초로 바꾸고 클래스 추가, Translate를 바꾸고 클래스 추가 과정을 진행하자.

여기서도 기존과 동일하게 클래스를 삭제했다가 추가했다가를 반복할 것이다. 그러면 위 아래로 움직이는 루핑 애니메이션을 실행할 수 있다.
1. 언제 추가 또는 삭제할 것인가?
Transition에는 아래와 같은 시점을 지니고 있다.
유니티에서는 버튼 클릭 이벤트를 제공하는 것처럼 트랜지션 시점에 대한 이벤트도 제공한다.

우리는 트랜지션이 끝났을때 발생하는 TransitionEndEvent를 사용한다.
2. 클래스를 추가, 삭제는 어떻게 구별할 것인가?
우리가 지금껏 AddToClassList와 RemoveFromClassList만 사용했다면 ToggleInClassList로 그 기능을 쉽게 꾸현할 수 있다.
UI요소.ToggleInClassList("끄거나 켤 스타일")
이 ToggleInClassList는 스타일의 유무를 알아서 파악해서 추가, 삭제를 진행해준다. 그래서 이 함수를 사용하면 스타일의 존재 유무를 확인할 필요 없이 반대로 바꿀 수 있다.
private void OnOpenButtonClicked(ClickEvent evt) {
// Open Bottom-Sheet
_bottomContainer.style.display = DisplayStyle.Flex;
_bottomSheet.AddToClassList("bottomsheet--up");
_scrim.AddToClassList("Scrim--fadein");
AnimateGirl();
}
private void AnimateGirl() {
_girl.ToggleInClassList("image--girl--up");
_girl.RegisterCallback<TransitionEndEvent>(AnimateBackGirl);
}
private void AnimateBackGirl(TransitionEndEvent evt) {
_girl.ToggleInClassList("image--girl--up");
}
이미지가 계속 위 아래로 움직인다.

람다식을 이용해서 스크립트를 정리해보자.
private void AnimateGirl() {
_girl.ToggleInClassList("image--girl--up");
_girl.RegisterCallback<TransitionEndEvent>(
evt => _girl.ToggleInClassList("image--girl--up")
);
}
여성 우주인 이미지 밑에 Label을 한 글자씩 입력되는 듯한 효과를 줘보자. 닷트윈을 import하고 DG.Tweening namespace를 추가해준다.
using DG.Tweening
맨 처음에는 text에 아무것도 없어야 되므로 empty를 넣어준 후에 DOTween.TO를 사용해서 message의 text에 애니메이션을 넣어준다.
private void AnimateGirl() {
_girl.ToggleInClassList("image--girl--up");
_girl.RegisterCallback<TransitionEndEvent>(
evt => _girl.ToggleInClassList("image--girl--up")
);
// Typing Animation
_message.text = string.Empty;
string m = "\"Sed in rebus apertissimis nimium longi sumus.\"";
DOTween.To(() => _message.text, x => _message.text = x, m, 3f).SetEase(Ease.Linear);
}
DOTween.To : 특정 값을 시작값에서 목표값까지 보간하여 변화시키는 Tween 작업을 생성 (우리는 text를 넣어서 문자가 하나씩 입력되면서 최종 문구 m까지 만들어진다. )

DOTween.To(() => 시작값, x => 값변경작업, 목표값, 시간);
UI Tool Kit에서 DOTween을 사용하려면 스크립트에서 제네릭 Tweener를 만들어서 사용해야한다.
지난 번에 언급한 bottomsheet가 아래로 내려가는 애니메이션을 구현해보자.
내려갈 때 내려가기도 전에 Display가 None이 되면서 그룹 전체가 꺼져버리는 문제가 있는데, 위에서 사용한 TransitionEndEvent를 사용하면 애니메이션이 끝날 때 그룹이 꺼지도록 할 수 있다.
private void OnOpenButtonClicked(ClickEvent evt) {
// Open Bottom-Sheet
_bottomContainer.style.display = DisplayStyle.Flex;
_bottomSheet.AddToClassList("bottomsheet--up");
_scrim.AddToClassList("Scrim--fadein");
AnimateGirl();
// End Transition Animation
_bottomSheet.RegisterCallback<TransitionEndEvent>(
evt => {
_bottomContainer.style.display = DisplayStyle.None;
}
);
}
private void OnCloseButtonClicked(ClickEvent evt) {
// Close Bottom-Sheet
_bottomSheet.RemoveFromClassList("bottomsheet--up");
_scrim.RemoveFromClassList("Scrim--fadein");
}
그런데 막상 실행시켜보면 실행이 이상하다. 왜냐하면 Open 시에도 Transition Animation이 실행되는데 올라가는 애니메이션이 끝나도 TransitionEndEvent가 호출되어서 바로 창이 꺼져버리는 문제가 생긴다.
따라서 Close 시에 내려오는 이벤트만을 구분해서 실행해야 한다. 기존에 사용했던 ClassListContains 함수를 사용해서 원하는 트랜지션만을 골라보자.
// End Transition Animation
_bottomSheet.RegisterCallback<TransitionEndEvent>(
evt => {
if (!_bottomSheet.ClassListContains("bottomsheet--up"))
_bottomContainer.style.display = DisplayStyle.None;
}
);
bottomsheet--up이 없는 상황에서 Transition이 끝났다는 것은 bottomsheet를 Close 했다는 것을 의미하고 그 때 display를 None으로 바꿔주면 된다.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using DG.Tweening;
using TMPro.EditorUtilities;
using DG.Tweening.Core;
public class UIController : MonoBehaviour
{
// Bottom - Sheet
private VisualElement _bottomContainer;
private Button _openButton;
private Button _closeButton;
private VisualElement _bottomSheet;
private VisualElement _scrim;
private VisualElement _boy;
private VisualElement _girl;
private Label _message;
private void Start() {
// Find Root-Element
var root = GetComponent<UIDocument>().rootVisualElement;
// UQuery
_bottomContainer = root.Q<VisualElement>("Container_Bottom");
_openButton = root.Q<Button>("Button_Open");
_closeButton = root.Q<Button>("Button_Close");
_bottomSheet = root.Q<VisualElement>("BottomSheet");
_scrim = root.Q<VisualElement>("Scrim");
_boy = root.Q<VisualElement>("Image_Boy");
_girl = root.Q<VisualElement>("Image_Girl");
_message = root.Q<Label>("Message");
// Hide bottom-sheet
_bottomContainer.style.display = DisplayStyle.None;
// Button Callback
_openButton.RegisterCallback<ClickEvent>(OnOpenButtonClicked);
_closeButton.RegisterCallback<ClickEvent>(OnCloseButtonClicked);
// Boy Animation
Invoke("AnimateBoy", .1f);
}
private void AnimateBoy() {
_boy.RemoveFromClassList("image--boy--inair");
}
private void OnOpenButtonClicked(ClickEvent evt) {
// Open Bottom-Sheet
_bottomContainer.style.display = DisplayStyle.Flex;
_bottomSheet.AddToClassList("bottomsheet--up");
_scrim.AddToClassList("Scrim--fadein");
AnimateGirl();
// End Transition Animation
_bottomSheet.RegisterCallback<TransitionEndEvent>(
evt => {
if (!_bottomSheet.ClassListContains("bottomsheet--up"))
_bottomContainer.style.display = DisplayStyle.None;
}
);
}
private void OnCloseButtonClicked(ClickEvent evt) {
// Close Bottom-Sheet
_bottomSheet.RemoveFromClassList("bottomsheet--up");
_scrim.RemoveFromClassList("Scrim--fadein");
}
private void AnimateGirl() {
_girl.ToggleInClassList("image--girl--up");
_girl.RegisterCallback<TransitionEndEvent>(
evt => _girl.ToggleInClassList("image--girl--up")
);
// Typing Animation
_message.text = string.Empty;
string m = "\"Sed in rebus apertissimis nimium longi sumus.\"";
DOTween.To(() => _message.text, x => _message.text = x, m, 3f).SetEase(Ease.Linear);
}
}
간단한 Sample Scene을 제작해보면서 UI Tool Kit에 대한 기본적인 사용법을 알아봤다. 아직 UI Tool Kit이 기존 UGUI에서 지원하는 기능을 전부 지원하지 못하지만 Unity에서 자체적으로 3세대 UI로 밀고 있고, 여러 기능을 지속적으로 업데이트 및 지원하고 있으니 공부를 해서 나쁠 건 없다고 본다.

중요한 점은 UGUI와 UI Tool Kit을 적절히 잘 섞어서 사용하면 기존 UGUI로만 UI를 제작했던 것보다는 훨씬 빠르게 UI 구현이 가능할 것 같다. UI 에서 공식으로 제공하는 Dragon Crahsers UI ToolKit Sample을 좀 뜯어보면서 공부하는 것도 좋을 것 같고 나중에 문서로 작성하는 것도 좋을 것 같다.