mcpField
U
unity-ui

unity-ui

github.com/nice-wolf-studioデザイン・UI4068 回インストール6 週間前に更新
リポジトリ

unity-ui について

UI Toolkit、uGUI、IMGUI の用途と実装パターンを解説する Unity UI 開発スキルです。レイアウト、イベント、データバインディング、テキスト描画を扱い、ランタイム UI とエディター拡張に役立ちます。

主な機能

  1. 01Unity の 3 種類の UI システムの比較
  2. 02UXML、USS、C# による UI Toolkit の構築
  3. 03イベント伝播、データバインディング、マニピュレーター
  4. 04uGUI の Canvas、レイアウト、アンカーの解説
  5. 05UI の性能とコールバック解除に関する指針
  6. 06テキスト描画に TextMeshPro を推奨

活用シーン

  1. 01新規プロジェクトのランタイム UI 選定
  2. 02Unity エディターウィンドウや Inspector の構築
  3. 03uGUI プロジェクトの段階的な移行計画
  4. 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.md
ugui-legacy.md
ui-data-binding.md
ui-toolkit-guide.md

詳細

Unity UI システム

Unity には 3 つの UI フレームワークがあります。新しいプロジェクトには UI Toolkit が推奨されます。 uGUI はレガシー用途や一部のランタイム用途で引き続きサポートされています。IMGUI は Editor のツール作成とデバッグ専用です。

UI システムの比較

機能UI ToolkituGUI(Canvas)IMGUI
新しいプロジェクトに推奨はいいいえ(レガシー)いいえ
ランタイムのゲーム UIはいはい推奨されません
Editor 拡張機能はいいいえはい
アプローチWeb に着想を得た方式(UXML + USS + C#)GameObject + Componentコードのみ(OnGUI)
レイアウトシステムFlexbox(Yoga)RectTransform + Anchors即時モード
スタイル設定USS スタイルシートコンポーネントごとのプロパティGUIStyle / GUISkin
ビジュアル編集UI BuilderScene Viewなし
パフォーマンス最適化された保持モードCanvas のバッチ処理毎フレーム再描画
データバインディングSerializedObject + ランタイムバインディングコードによる手動設定コードによる手動設定
ワールドスペース UIサポートされていますCanvas の World Space モードサポートされていません
入力との連携ポインター/キーボードイベントEventSystem + RaycastersEvent.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 つのフェーズで伝播します。

  1. トリクルダウン -- ルートから対象の要素へ
  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() で順序を変更できます。

参照:references/ugui-legacy.md


アンチパターン

アンチパターン問題適切な方法
あらゆる場所でインラインスタイルを使用する要素ごとにメモリのオーバーヘッドが発生します共通スタイルには 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目的
UIDocumentVisualTreeAsset をホストする 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 のスケーリングを制御
GraphicRaycasterCanvas 上での入力検出を有効化
EventSystem入力イベントを一元的にディスパッチ
RectTransformアンカー設定とサイズ指定ができる Transform
Button.onClickクリック時の UnityEvent
Toggle.onValueChangedToggle の変更時の UnityEvent
Slider.onValueChangedSlider の変更時の 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 を参照してください。

追加リソース

コメント