⏳
【Unity】エディタ上 進捗バーの作り方【IMGUI, UIToolKit】
1:背景
- 開発ツールで激重処理があり、フリーズを疑うレベルで処理待ちが発生することがあった
- 進捗バー(プログレスバー)を使いたい
- ちなみに↑の開発ツールは Tilemap を分割するもの。よかったら見てね
2:結論とプログラム
- 実際に試したい場合は「具体的なプログラム」セクションをコピーして
Unity プロジェクトの Editor フォルダ下に配置し、
画面上部 → Examples → ProgressBarWindow_… を選択で同様のウィンドウが出る
uGUI(IMGUI) 版
-
EditorGUI.ProgressBar

-
EditorUtility.DisplayProgressBar

-
EditorUtility.DisplayCancelableProgressBar

具体的なプログラム
処理の流れ
実際のコード
using UnityEditor;
using UnityEngine;
public class ProgressBarWindow_IMGUI : EditorWindow
{
private enum DisplayMode { ProgressBar, Modal_Bar, Cancelable_Modal_Bar}
private DisplayMode displayMode = DisplayMode.ProgressBar;
private float durationSeconds = 5f;
private bool isProcessing = false;
private double startTime;
private float progress = 0f;
[MenuItem("Examples/ProgressBarWindow_IMGUI")]
public static void ShowWindow(){ GetWindow<ProgressBarWindow_IMGUI>("ProgressBarWindow_IMGUI"); }
private void OnGUI()
{
GUILayout.Label("秒数指定プログレスバー", EditorStyles.boldLabel);
//表示方法の選択, 処理時間設定
displayMode = (DisplayMode)EditorGUILayout.EnumPopup("表示方法", displayMode);
durationSeconds = EditorGUILayout.FloatField("処理時間(秒)", durationSeconds);
//開始ボタン設定
EditorGUI.BeginDisabledGroup(isProcessing);
if (GUILayout.Button("処理開始")) StartTimedOperation();
EditorGUI.EndDisabledGroup();
if (isProcessing) //進捗表示
{
GUILayout.Space(10);
//IMGUI内に埋め込みバーを描く
if (displayMode == DisplayMode.ProgressBar)
{
var rect = GUILayoutUtility.GetRect(200, 20);
EditorGUI.ProgressBar(rect, progress, $"進捗: {progress * 100:0.0}%");
}
else //モーダル表示中は小さくラベルだけ
{
GUILayout.Label($"進捗: {progress * 100:0.0}%");
}
}
}
private void StartTimedOperation()
{
if (durationSeconds <= 0f)
{
EditorUtility.DisplayDialog("エラー", "0 より大きい値を指定せんかい", "OK");
return;
}
isProcessing = true;
startTime = EditorApplication.timeSinceStartup;
progress = 0f;
EditorApplication.update += UpdateProgress;
}
private void UpdateProgress()
{
//経過秒数計算
var elapsed = EditorApplication.timeSinceStartup - startTime;
progress = Mathf.Clamp01((float)(elapsed / durationSeconds));
//表示方法ごとにバー更新
var info = $"処理進行中({elapsed:0.0}s / {durationSeconds:0.0}s)";
switch (displayMode)
{
case DisplayMode.Modal_Bar:
EditorUtility.DisplayProgressBar("", info, progress);
break;
case DisplayMode.Cancelable_Modal_Bar:
bool canceled = EditorUtility.DisplayCancelableProgressBar("", info, progress);
if (canceled)
{
Cleanup("キャンセル");
return;
}
break;
case DisplayMode.ProgressBar: //OnGUI 側で描画するので何もしない
break;
}
if (progress >= 1f) Cleanup("処理完了");
}
private void Cleanup(string message)
{
EditorApplication.update -= UpdateProgress;
isProcessing = false;
//モーダルの場合はプログレスバーをクリア
if (displayMode != DisplayMode.ProgressBar) EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("完了", message, "OK");
}
}
UIElements(UIToolKit) 版
-
ProgressBar
具体的なプログラム
処理の流れ
実際のコード
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class ProgressBarWindow_UIToolKit : EditorWindow
{
private float durationSeconds = 5f;
//内部状態管理用
private bool isProcessing;
private double startTime;
private float progress;
//UI 要素
private FloatField durationField;
private Button startButton;
private Button cancelButton;
private ProgressBar progressBar;
private Label progressLabel;
private Label messageLabel;
[MenuItem("Examples/ProgressBarWindow_UIToolKit")]
public static void ShowWindow()
{
// ウィンドウを開き、最小サイズを設定
var wnd = GetWindow<ProgressBarWindow_UIToolKit>("ProgressBarWindow_UIToolKit");
wnd.minSize = new Vector2(350, 160);
}
//ウィンドウ初期化時に呼ばれ、UI Toolkit の要素を構築する
public void CreateGUI()
{
var root = rootVisualElement;
root.style.paddingLeft = 10;
root.style.paddingRight = 10;
root.style.paddingTop = 10;
//処理時間入力フィールド
durationField = new FloatField("処理時間(秒)");
durationField.value = durationSeconds;
durationField.RegisterValueChangedCallback(evt => durationSeconds = evt.newValue);
root.Add(durationField);
//開始, キャンセルボタン
var buttonRow = new VisualElement();
buttonRow.style.flexDirection = FlexDirection.Row;
buttonRow.style.marginTop = 10;
buttonRow.style.justifyContent = Justify.SpaceBetween;
startButton = new Button(OnStart) { text = "開始" };
cancelButton = new Button(OnCancel) { text = "キャンセル" };
cancelButton.SetEnabled(false);
buttonRow.Add(startButton);
buttonRow.Add(cancelButton);
root.Add(buttonRow);
//プログレスバー
progressBar = new ProgressBar();
progressBar.lowValue = 0;
progressBar.highValue = 1;
progressBar.value = 0;
progressBar.style.height = 18;
progressBar.style.marginTop = 10;
root.Add(progressBar);
//進捗表示テキスト
progressLabel = new Label("進捗: 0.0%");
progressLabel.style.marginTop = 5;
root.Add(progressLabel);
//エラーや完了メッセージを表示するテキスト
messageLabel = new Label("");
messageLabel.style.color = new StyleColor(Color.red);
messageLabel.style.marginTop = 5;
messageLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
root.Add(messageLabel);
}
private void OnEnable() { EditorApplication.update -= UpdateProgress; }
private void OnDisable() { EditorApplication.update -= UpdateProgress; }
private void OnStart()
{
messageLabel.text = "";
if (durationSeconds <= 0f)
{
messageLabel.text = "エラー:0 より大きい値を指定せんかい";
return;
}
//内部状態をリセット, 更新
isProcessing = true;
startTime = EditorApplication.timeSinceStartup;
progress = 0f;
startButton.SetEnabled(false);
cancelButton.SetEnabled(true);
progressBar.value = 0f;
progressLabel.text = "進捗: 0.0%";
EditorApplication.update += UpdateProgress;
}
private void OnCancel()
{
if (isProcessing == false) return;
Cleanup("処理キャンセル");
}
private void UpdateProgress()
{
//進捗計算
var elapsed = (float)(EditorApplication.timeSinceStartup - startTime);
progress = Mathf.Clamp01(elapsed / durationSeconds);
Debug.Log(progress);
//プログレスバーとテキストに反映
progressBar.value = progress;
progressLabel.text = $"進捗: {progress * 100:0.0}%";
if (progress >= 1f) Cleanup("処理完了");
}
//処理終了後の共通の処理
private void Cleanup(string message)
{
isProcessing = false;
EditorApplication.update -= UpdateProgress;
startButton.SetEnabled(true);
cancelButton.SetEnabled(false);
messageLabel.text = message;
}
}
3:実装のポイント
UniTask, EditorCoroutineUtility を導入するかどうか
- 「一部だけフレームを跨いで処理したい」, 「フリーズさせず非同期で処理を流したい」
- ↑のような要望がある場合、導入はアリ
- プログレスバーのためだけの導入は要注意
EditorApplication.update の解除管理
-
OnDisable,Cleanup(またはOnCancel)両方でEditorApplication.update -= UpdateProgressを行い、ウィンドウ破棄, キャンセル時のリークを防ぐ
バーの再描画(IMGUI版)
-
UpdateProgressでRepaint()実行かEditorApplication.QueuePlayerLoopUpdate()を用いて即時に UI を更新すると、埋め込みバーが途切れず滑らかに動作する
CancelableProgressBar からの復帰
- モーダルのキャンセル後に progressBar(UIElements 版)や進捗ラベルを 0 に設定しておくと、再度開始したときに前の状態が残らない
Discussion