unity-ui について
UI Toolkit、uGUI、IMGUI の用途と実装パターンを解説する Unity UI 開発スキルです。レイアウト、イベント、データバインディング、テキスト描画を扱い、ランタイム UI とエディター拡張に役立ちます。
主な機能
- 01Unity の 3 種類の UI システムの比較
- 02UXML、USS、C# による UI Toolkit の構築
- 03イベント伝播、データバインディング、マニピュレーター
- 04uGUI の Canvas、レイアウト、アンカーの解説
- 05UI の性能とコールバック解除に関する指針
- 06テキスト描画に TextMeshPro を推奨
活用シーン
- 01新規プロジェクトのランタイム UI 選定
- 02Unity エディターウィンドウや Inspector の構築
- 03uGUI プロジェクトの段階的な移行計画
- 04メニュー、HUD、ワールドスペース UI の実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nice-wolf-studio/ unity-claude-skills --skill unity-uiインストール後、Claude Code で/unity-uiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/textmeshpro.mdugui-legacy.mdui-data-binding.mdui-toolkit-guide.md詳細
Unity UI システム
Unity には 3 つの UI フレームワークがあります。新しいプロジェクトには UI Toolkit が推奨されます。 uGUI はレガシー用途や一部のランタイム用途で引き続きサポートされています。IMGUI は Editor のツール作成とデバッグ専用です。
UI システムの比較
| 機能 | UI Toolkit | uGUI(Canvas) | IMGUI |
|---|---|---|---|
| 新しいプロジェクトに推奨 | はい | いいえ(レガシー) | いいえ |
| ランタイムのゲーム UI | はい | はい | 推奨されません |
| Editor 拡張機能 | はい | いいえ | はい |
| アプローチ | Web に着想を得た方式(UXML + USS + C#) | GameObject + Component | コードのみ(OnGUI) |
| レイアウトシステム | Flexbox(Yoga) | RectTransform + Anchors | 即時モード |
| スタイル設定 | USS スタイルシート | コンポーネントごとのプロパティ | GUIStyle / GUISkin |
| ビジュアル編集 | UI Builder | Scene View | なし |
| パフォーマンス | 最適化された保持モード | Canvas のバッチ処理 | 毎フレーム再描画 |
| データバインディング | SerializedObject + ランタイムバインディング | コードによる手動設定 | コードによる手動設定 |
| ワールドスペース UI | サポートされています | Canvas の World Space モード | サポートされていません |
| 入力との連携 | ポインター/キーボードイベント | EventSystem + Raycasters | Event.current |
選択ガイド:
- 新規のランタイム UI(メニュー、HUD、インベントリ) --> UI Toolkit
- 新規の Editor ウィンドウ/インスペクター --> UI Toolkit
- uGUI を使用している既存プロジェクト --> uGUI を継続し、段階的に移行
- Editor の簡易デバッグオーバーレイ --> IMGUI
- 3D オブジェクト上のワールドスペース UI --> UI Toolkit または uGUI World Space Canvas
UI Toolkit
UI Toolkit は、Web テクノロジーに着想を得た Unity の最新 UI フレームワークです。構造には UXML、スタイル設定には USS、ロジックには C# を使用します。
コアアーキテクチャ
UIDocument (MonoBehaviour on GameObject)
--> VisualTreeAsset (.uxml) -- defines structure
--> StyleSheet (.uss) -- defines appearance
--> C# script -- defines behavior
すべての UI 要素は VisualElement を継承します。ルートには rootVisualElement からアクセスできます。
UXML の構造
UXML では UI 階層を宣言的に定義します。
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
<ui:Style src="MainMenu.uss" />
<ui:VisualElement name="root-container" class="container">
<ui:Label text="Game Menu" class="title" />
<ui:Button text="Play" name="play-button" class="menu-btn" />
<ui:Button text="Settings" name="settings-button" class="menu-btn" />
<ui:Toggle label="Fullscreen" name="fullscreen-toggle" />
<ui:Slider label="Volume" low-value="0" high-value="100" name="volume-slider" />
<ui:TextField label="Player Name" name="player-name" />
</ui:VisualElement>
</ui:UXML>
主なポイント:
xmlns:ui="UnityEngine.UIElements"は標準の名前空間です<ui:Style src="..." />で USS ファイルを参照します- C# から検索する要素には
name属性、USS のスタイル設定にはclassを使用します <ui:Template src="other.uxml" name="other" />でテンプレートをインポートできます
USS によるスタイル設定
USS では CSS に似た構文と Unity 固有の拡張機能を使用します。Unity 固有の機能に関する USS プロパティには、すべて -unity- 接頭辞が付きます。
/* Type selector */
Button {
background-color: #2D2D2D;
border-radius: 4px;
padding: 8px 16px;
-unity-font-style: bold;
}
/* Class selector */
.menu-btn {
width: 200px;
height: 40px;
margin: 4px 0;
font-size: 16px;
color: #FFFFFF;
}
/* Name selector */
#play-button {
background-color: #4CAF50;
}
/* Pseudo-class */
.menu-btn:hover {
background-color: #555555;
scale: 1.05 1.05;
}
.menu-btn:active {
background-color: #333333;
}
.menu-btn:disabled {
opacity: 0.5;
}
/* Descendant selector */
.container > Label {
-unity-text-align: middle-center;
}
/* USS variables */
:root {
--primary-color: #4CAF50;
--font-large: 24px;
}
.title {
color: var(--primary-color);
font-size: var(--font-large);
}
セレクターの種類: タイプ(Button)、名前(#name)、クラス(.class)、ユニバーサル(*)、子孫(A B)、子(A > B)、複合(A.class)、疑似クラス(:hover、:active、:focus、:disabled、:checked)。
レイアウトは Flexbox ベースです: flex-direction、flex-grow、flex-shrink、justify-content、align-items、align-self、flex-wrap を使用します。デフォルトの方向は column です。
C# のセットアップと操作
using UnityEngine;
using UnityEngine.UIElements;
public class MainMenuController : MonoBehaviour
{
[SerializeField] private UIDocument uiDocument;
private Button playButton;
private Button settingsButton;
private Toggle fullscreenToggle;
private Slider volumeSlider;
private TextField playerNameField;
private void OnEnable()
{
var root = uiDocument.rootVisualElement;
// Query single elements by name
playButton = root.Q<Button>("play-button");
settingsButton = root.Q<Button>("settings-button");
fullscreenToggle = root.Q<Toggle>("fullscreen-toggle");
volumeSlider = root.Q<Slider>("volume-slider");
playerNameField = root.Q<TextField>("player-name");
// Register click callbacks
playButton.RegisterCallback<ClickEvent>(OnPlayClicked);
settingsButton.RegisterCallback<ClickEvent>(OnSettingsClicked);
// Register value change callbacks
fullscreenToggle.RegisterValueChangedCallback(OnFullscreenChanged);
volumeSlider.RegisterValueChangedCallback(OnVolumeChanged);
// Query multiple elements by class
var allButtons = root.Query<Button>(className: "menu-btn").ToList();
}
private void OnDisable()
{
playButton.UnregisterCallback<ClickEvent>(OnPlayClicked);
settingsButton.UnregisterCallback<ClickEvent>(OnSettingsClicked);
fullscreenToggle.UnregisterValueChangedCallback(OnFullscreenChanged);
volumeSlider.UnregisterValueChangedCallback(OnVolumeChanged);
}
private void OnPlayClicked(ClickEvent evt) => Debug.Log("Play clicked");
private void OnSettingsClicked(ClickEvent evt) => Debug.Log("Settings clicked");
private void OnFullscreenChanged(ChangeEvent<bool> evt)
{
Screen.fullScreen = evt.newValue;
}
private void OnVolumeChanged(ChangeEvent<float> evt)
{
AudioListener.volume = evt.newValue / 100f;
}
}
プログラムによる UI の作成(UXML なし):
private void CreateUIFromCode()
{
var root = uiDocument.rootVisualElement;
var container = new VisualElement();
container.AddToClassList("container");
root.Add(container);
var label = new Label("Created from C#");
container.Add(label);
var button = new Button(() => Debug.Log("Clicked")) { text = "Click Me" };
button.name = "dynamic-button";
container.Add(button);
}
イベントシステム
UI Toolkit のイベントは 2 つのフェーズで伝播します。
- トリクルダウン -- ルートから対象の要素へ
- バブルアップ -- 対象の要素からルートへ戻る
// Default: bubble-up phase
element.RegisterCallback<PointerDownEvent>(OnPointerDown);
// Trickle-down phase (parent reacts before children)
element.RegisterCallback<PointerDownEvent>(OnPointerDown, TrickleDown.TrickleDown);
// Pass custom data to callbacks
element.RegisterCallback<ClickEvent, string>(OnClickWithData, "my-data");
// Set value without triggering ChangeEvent
myControl.SetValueWithoutNotify(newValue);
データバインディング
SerializedObject バインディング(Editor/Inspector UI):
// In UXML: <ui:IntegerField binding-path="m_Health" label="Health" />
// In C#:
var healthField = new IntegerField("Health") { bindingPath = "m_Health" };
root.Add(healthField);
root.Bind(new SerializedObject(targetComponent));
バインド可能なオブジェクト:MonoBehaviour、ScriptableObject、Unity のネイティブ型、プリミティブ型。
INotifyValueChanged 要素の value プロパティのみバインドできます。
ランタイムバインディングは、通常の C# オブジェクトを UI コントロールに接続し、Editor とランタイムの両方のコンテキストで動作します。要素にデータソースを設定し、同期の方向をバインディングモードで定義します。
参照:references/ui-data-binding.md
マニピュレーター
マニピュレーターはイベント処理ロジックをカプセル化し、操作処理を UI コードから分離します。
public class DragManipulator : PointerManipulator
{
private Vector3 startPosition;
private bool isDragging;
public DragManipulator(VisualElement target)
{
this.target = target;
}
protected override void RegisterCallbacksOnTarget()
{
target.RegisterCallback<PointerDownEvent>(OnPointerDown);
target.RegisterCallback<PointerMoveEvent>(OnPointerMove);
target.RegisterCallback<PointerUpEvent>(OnPointerUp);
}
protected override void UnregisterCallbacksFromTarget()
{
target.UnregisterCallback<PointerDownEvent>(OnPointerDown);
target.UnregisterCallback<PointerMoveEvent>(OnPointerMove);
target.UnregisterCallback<PointerUpEvent>(OnPointerUp);
}
private void OnPointerDown(PointerDownEvent evt)
{
startPosition = evt.position;
isDragging = true;
target.CapturePointer(evt.pointerId);
evt.StopPropagation();
}
private void OnPointerMove(PointerMoveEvent evt)
{
if (!isDragging) return;
var delta = evt.position - startPosition;
target.transform.position += (Vector3)delta;
startPosition = evt.position;
}
private void OnPointerUp(PointerUpEvent evt)
{
isDragging = false;
target.ReleasePointer(evt.pointerId);
evt.StopPropagation();
}
}
// Usage:
myElement.AddManipulator(new DragManipulator(myElement));
組み込みのマニピュレータークラス: Manipulator(基底クラス)、PointerManipulator、MouseManipulator、Clickable、ContextualMenuManipulator、KeyboardNavigationManipulator。
カスタムコントロール
// Unity 6+ recommended pattern: [UxmlElement] attribute (replaces deprecated UxmlFactory/UxmlTraits)
[UxmlElement]
public partial class HealthBar : VisualElement
{
[UxmlAttribute]
public float MaxHealth { get; set; } = 100f;
private VisualElement fillBar;
private float currentHealth;
public float CurrentHealth
{
get => currentHealth;
set
{
currentHealth = Mathf.Clamp(value, 0, MaxHealth);
fillBar.style.width = Length.Percent(currentHealth / MaxHealth * 100f);
}
}
public HealthBar()
{
AddToClassList("health-bar");
fillBar = new VisualElement();
fillBar.AddToClassList("health-bar__fill");
Add(fillBar);
}
}
uGUI/Canvas システム(レガシー)
uGUI は、Unity の古い GameObject ベースの UI システムです。Canvas、RectTransform、EventSystem を使用します。
Canvas のレンダリングモード
| モード | 説明 | 用途 |
|---|---|---|
| Screen Space - Overlay | すべての要素の上に描画され、画面に合わせて拡大縮小されます | 標準的な HUD、メニュー |
| Screen Space - Camera | 特定のカメラで描画され、遠近法の影響を受けます | 奥行き効果のある UI |
| World Space | シーン内の 3D オブジェクトとして Canvas を配置します | ワールド内のディスプレイ、VR UI |
コアコンポーネント
ビジュアル: Text、Image、RawImage インタラクション: Button、Toggle、ToggleGroup、Slider、Scrollbar、Dropdown、InputField、ScrollRect レイアウト: HorizontalLayoutGroup、VerticalLayoutGroup、GridLayoutGroup、ContentSizeFitter、AspectRatioFitter、LayoutElement
RectTransform とアンカー
すべての uGUI 要素では、Transform の代わりに RectTransform を使用します。アンカーは、要素を親に対してどのように配置するかを定義します。
- Anchor Min/Max は割合で指定します(0.0 = 左/下、1.0 = 右/上)
- アンカーが一致している場合:位置を固定します(Pos X、Pos Y、Width、Height)
- アンカーが離れている場合:伸縮します(Left、Right、Top、Bottom の余白)
- Pivot:回転と拡大縮小の中心点
uGUI の例
using UnityEngine;
using UnityEngine.UI;
public class MenuManager : MonoBehaviour
{
[SerializeField] private Button playButton;
[SerializeField] private Slider volumeSlider;
[SerializeField] private Toggle muteToggle;
private void OnEnable()
{
playButton.onClick.AddListener(OnPlayClicked);
volumeSlider.onValueChanged.AddListener(OnVolumeChanged);
muteToggle.onValueChanged.AddListener(OnMuteToggled);
}
private void OnDisable()
{
playButton.onClick.RemoveListener(OnPlayClicked);
volumeSlider.onValueChanged.RemoveListener(OnVolumeChanged);
muteToggle.onValueChanged.RemoveListener(OnMuteToggled);
}
private void OnPlayClicked() => Debug.Log("Play");
private void OnVolumeChanged(float value) => AudioListener.volume = value;
private void OnMuteToggled(bool muted) => AudioListener.pause = muted;
}
描画順序
要素は Hierarchy の順序で描画されます。最初の子が最初に描画され、最後の子が最前面に描画されます。Transform.SetAsFirstSibling()、SetAsLastSibling()、SetSiblingIndex() で順序を変更できます。
アンチパターン
| アンチパターン | 問題 | 適切な方法 |
|---|---|---|
| あらゆる場所でインラインスタイルを使用する | 要素ごとにメモリのオーバーヘッドが発生します | 共通スタイルには USS ファイルを使用します |
複雑な USS でユニバーサルセレクター(A * B)を使用する | 規模が大きくなるとセレクターのパフォーマンスが低下します | BEM クラス命名と子セレクターを使用します |
子孫が多い要素で重い :hover を使用する | マウスの移動によって階層全体が無効化されます | :hover は末端の要素に限定します |
CreateInspectorGUI() 内で Bind() を呼び出す | 二重バインドが発生します。自動バインドは呼び出しから戻った後に実行されます | 自動バインドに任せるか、手動で作成した UI に対してのみ Bind を呼び出します |
| フレームごとに UI 全体を再構築する | リテインドモードの利点が失われます | 変更された要素のみを更新します |
| 動的コンテンツを含む Canvas を複数使用する(uGUI) | 子要素が変更されるたびに Canvas のバッチが再構築されます | 静的 UI と動的 UI を別々の Canvas に分けます |
| コールバックの登録を解除しない | メモリリークや古い参照が発生します | 必ず OnDisable または OnDestroy で登録を解除します |
| ランタイムのゲーム UI に IMGUI を使用する | フレームごとに再描画され、パフォーマンスが低下します | UI Toolkit または uGUI を使用します |
| シーン内の EventSystem を忘れる(uGUI) | 入力イベントが処理されません | シーン内に EventSystem が 1 つ存在することを確認します |
主要 API クイックリファレンス
UI Toolkit
| API | 目的 |
|---|---|
UIDocument | VisualTreeAsset をホストする MonoBehaviour |
rootVisualElement | ビジュアルツリーのルート |
Q<T>("name") | 名前で単一の要素を検索します |
Q<T>(className: "cls") | クラスで単一の要素を検索します |
Query<T>().ToList() | 複数の要素を検索します |
RegisterCallback<TEvent>(callback) | イベントハンドラーを登録します |
UnregisterCallback<TEvent>(callback) | イベントハンドラーを削除します |
RegisterValueChangedCallback(callback) | 値の変更を監視します |
SetValueWithoutNotify(value) | 通知なしで値を設定します |
AddToClassList("class") | USS クラスを追加します |
RemoveFromClassList("class") | USS クラスを削除します |
AddManipulator(manipulator) | イベントマニピュレーターをアタッチします |
style.display = DisplayStyle.None | 要素を非表示にします |
style.display = DisplayStyle.Flex | 要素を表示します |
VisualTreeAsset.Instantiate() | UXML からインスタンスを作成します |
element.Bind(serializedObject) | SerializedObject にバインドします |
uGUI
| API | 目的 |
|---|---|
Canvas | すべての uGUI 要素のルートコンテナー |
CanvasScaler | 解像度に応じた UI のスケーリングを制御 |
GraphicRaycaster | Canvas 上での入力検出を有効化 |
EventSystem | 入力イベントを一元的にディスパッチ |
RectTransform | アンカー設定とサイズ指定ができる Transform |
Button.onClick | クリック時の UnityEvent |
Toggle.onValueChanged | Toggle の変更時の UnityEvent |
Slider.onValueChanged | Slider の変更時の UnityEvent |
LayoutGroup | 子要素の自動レイアウト |
関連スキル
- unity-foundations -- GameObject、Component、MonoBehaviour のライフサイクル
- unity-scripting -- C# のパターン、SerializeField、イベント
- unity-input -- UI との Input System の統合
TextMeshPro
すべてのテキスト描画には、レガシーな UI.Text ではなく TextMeshPro(TMP)を使用してください。TMP は SDF レンダリングを使用するため、どのスケールでもテキストが鮮明です。Canvas UI には TextMeshProUGUI、3D ワールドテキストには TextMeshPro を使用してください。アロケーションなしで更新するには、SetText("Score: {0}", value) を使用してください。API、リッチテキストタグ、フォントアセット、パターンの詳細は、references/textmeshpro.md を参照してください。
追加リソース
- UI Toolkit | USS | UXML | データバインディング | Manipulator
- uGUI Canvas | uGUI Layout