UnityのUI Toolkitで指定した時間後に処理を実行するIVisualElementSchedulerの使い方についてまとめました。
Unity2022.2.19
はじめに
UIアニメーションを実装するときなど、指定した時間の後にプロパティを変更するなどの処理をしたい時があります。
UI Toolkitではこれを実現する仕組みとしてIVisualElementScheduler
があります。
本記事ではこれについて説明します。
準備として、下図のように中央に円形の要素Circleを配置したUXMLファイルを用意しておきます。
出力されたUXMLファイルは以下の通りです。
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <ui:VisualElement name="Background" style="flex-grow: 1; background-color: rgb(255, 246, 220); height: 248px; justify-content: center;"> <ui:VisualElement name="Circle" style="flex-grow: 0; background-color: rgb(255, 0, 0); width: 100px; height: 100px; border-top-left-radius: 50px; border-bottom-left-radius: 50px; border-top-right-radius: 50px; border-bottom-right-radius: 50px; transition-property: scale; transition-duration: 500ms; transition-timing-function: ease-out; position: relative; align-self: center;" /> </ui:VisualElement> </ui:UXML>
CircleのスケールプロパティにはTransitionを設定し、スケールが変更されたらアニメーションをするようにしてあります。
Transitionについては下の記事にまとめていますので、必要に応じて参照してください。
指定した時間後に処理を実行する
処理を遅延実行させるには、VisualElement.schedule
によりIVisualElementScheduler
を取得します。
このExecute
に処理を書くと、その処理はこのVisualElement
がパネルにアタッチされたタイミングか、アタッチ済みの場合には即座に実行されます。
さらに以下のようにStartingIn
メソッドを使うと処理の実行を遅延させることができます。
using UnityEditor; using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleWindow : EditorWindow { // uxml [SerializeField] private VisualTreeAsset _layout; private void OnEnable() { _layout.CloneTree(rootVisualElement); var circle = rootVisualElement.Q("Circle"); circle.schedule .Execute(() => circle.transform.scale = Vector3.one * 2) // スケールを2倍にする .StartingIn(1000); // 1秒後に実行 } [MenuItem("Window/Example")] private static void Open() { GetWindow<ExampleWindow>(); } }
このスクリプトのInspectorからLayoutプロパティに前節のUXMLファイルをアサインし、Window > Exampleからウィンドウを開くと以下の結果が得られます。
1秒遅延してからスケーリングしていることを確認できました。
繰り返しと期間
指定した処理を一定時間間隔で繰り返すには、以下のようにEvery
を使います。
またForDuration
を使うことで全体の(ここでは繰り返しの)継続期間を指定できます。
using UnityEditor; using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleWindow : EditorWindow { // uxml [SerializeField] private VisualTreeAsset _layout; private void OnEnable() { _layout.CloneTree(rootVisualElement); var circle = rootVisualElement.Q("Circle"); var pingPong = false; circle.schedule .Execute(() => { // 実行されるごとにサイズを(1,1,1) or (2,2,2)に変更 circle.transform.scale = pingPong ? Vector3.one : Vector3.one * 2; pingPong = !pingPong; }) .StartingIn(500) // 0.5秒後に開始 .Every(500) // 0.5秒ごとに実行 .ForDuration(2000); // 2秒間有効 } [MenuItem("Window/Example")] private static void Open() { GetWindow<ExampleWindow>(); } }
これを実行すると以下の結果が得られます。
ちなみに上記のコードではForDuration
を使っていますが、Until
を使うとフラグを使って任意のタイミングまで処理を継続できます。
任意のタイミングで時間計測を開始する
これまでの方法では、遅延時間の計測が即座に開始されます。
もし、ボタンを押してから1秒など任意のタイミングで時間計測を開始したい場合には、StartingIn
を使用せずにPause
とExecuteLater
を使用します。
using UnityEditor; using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleWindow : EditorWindow { // uxml [SerializeField] private VisualTreeAsset _layout; private void OnEnable() { _layout.CloneTree(rootVisualElement); var circle = rootVisualElement.Q("Circle"); // 実行ボタンを追加 var executeLaterButton = new Button { text = "Execute" }; rootVisualElement.Add(executeLaterButton); var scheduledItem = circle.schedule .Execute(() => circle.transform.scale = Vector3.one * 2); // ポーズしておく scheduledItem.Pause(); executeLaterButton.clickable.clicked += () => { // ボタンが押されたタイミングで計測開始して1秒後に実行 scheduledItem.ExecuteLater(1000); }; } [MenuItem("Window/Example")] private static void Open() { GetWindow<ExampleWindow>(); } }
実行すると以下の結果が得られます。
ボタンを押下してから1秒後にサイズが変更していることを確認できました。
PauseとResume
Pause
とResume
を使うと時間計測を一時停止/再開できます。
using UnityEditor; using UnityEngine; using UnityEngine.UIElements; public sealed class ExampleWindow : EditorWindow { // uxml [SerializeField] private VisualTreeAsset _layout; private void OnEnable() { _layout.CloneTree(rootVisualElement); var circle = rootVisualElement.Q("Circle"); var pingPong = false; var scheduledItem = circle.schedule .Execute(() => { circle.transform.scale = pingPong ? Vector3.one : Vector3.one * 2; pingPong = !pingPong; }) .Every(500); // Pauseボタンを追加 var paused = false; var pauseButton = new Button(); pauseButton.text = "Pause"; rootVisualElement.Add(pauseButton); pauseButton.clickable.clicked += () => { if (paused) { scheduledItem.Resume(); pauseButton.text = "Pause"; paused = false; } else { scheduledItem.Pause(); pauseButton.text = "Resume"; paused = true; } }; } [MenuItem("Window/Example")] private static void Open() { GetWindow<ExampleWindow>(); } }