Unity UI Toolkit ゲームUI
UXMLで構造、USSで見た目、ゲームイベントに反応する小さなC#コントローラーで動作を定義します。実プロジェクト(ホラーシューターHUD)から抽出した内容です。特にトラブルシューティングリストは時間節約に最も役立ちます。
1. コードを書く前に
不足している点だけを、一度の短いバッチで質問してください:
- UI API.
[RequireComponent(typeof(PanelRenderer))]+RegisterUIReloadCallback= PanelRendererワークフロー(UIDocumentなし、rootVisualElementなし)。UIDocument+rootVisualElement= 従来の方式。貼り付けられたコードから推測します。不明な場合は、動作しているコントローラーとそのUXMLを1つ尋ねてください。決して両方を混在させないでください(NullReferenceExceptionの原因になります)。動作実績のあるファイルは、APIの記憶よりも優先されます。 - ジャンル/テーマ(スクリーンショットやパレットがあれば)、プラットフォームと入力方式、基準解像度/アスペクト比。
- UIがリッスンするゲームスクリプト。 それらを要求し、実際のpublicイベント/プロパティを読み、それらにバインドしてください。イベントがない場合は、ゲームスクリプトへの最小限の追加を提案してください。
- ユーザーの言語で返信し、既存のスクリプトが使用している言語でコードコメントを記述してください。
2. スタイルガイドを先に
ユーザーが最初に気付くのは一貫性です。多くの部品を作成する前に:短いガイドを提案し、1つの小さな部品を作成してユーザーの反応を待ち、そのフィードバックをルールに変換します(「背景ボックスなし」「もっとコンパクトに」)。それをプロジェクトに保存し(例:Assets/UI/UI_STYLE_GUIDE.md)、将来のUIリクエストに貼り付けるようにユーザーに伝えます。部品やルールが変更されたら更新します。内容:デザイン言語(トーン、コンテナ、密度、モーション)、パレットテーブル(テキスト、ミュート、警告、クリティカル、シャドウ)、タイポグラフィ(ロールごとのサイズ、シャドウルール)、レイアウト(ゾーン、マージン、Panel Settings)、USSの命名規則と状態クラス、コードアーキテクチャルール、UXMLの注意点、既存部品のリスト、新規部品のチェックリスト。
3. アーキテクチャ
- 1部品 = 1コントローラー + 1UXML + 1USS、データソースは1つ。巨大コントローラーは禁止。
- 一緒に配置する必要がある部品は、1つのUXML/flexコンテナを共有します(2つのコントローラーが1つのGameObject/PanelRendererを共有しても構いません。各コントローラーは名前で自身の要素を見つけます)。
- コントローラーは要素を見つけ、テキスト/値を設定し、USSクラスを切り替えるだけです。色、サイズ、アニメーションはUSSに記述します。
- PanelRenderer:ルートは
RegisterUIReloadCallback(OnUIReload)を介して取得します(OnEnableで登録、OnDisableで解除)。UIの準備ができたときと、リロードのたびに呼び出されるため、すべてのQ<T>("name")検索、キャッシング、イベントバインディング、状態リセットはここで行います。Update()では要素が存在するまでnullガードします。 - ゲームイベントはUIが存在する前に発生する可能性があります:最新の値をフィールドにキャッシュし、
OnUIReloadの最後で再適用します。 - プレイヤー/ゲームオブジェクトは遅れてスポーンする可能性があります:シリアライズされた参照を使用するか、
FindFirstObjectByTypeをスロットル(約0.5秒)してからバインドし、OnDisableで購読解除します。 - プッシュ型で、ポーリングはしません。イベントを購読し、変更があったときだけ書き込みます。イベントがない値(継続的なスタミナ、リロードbool)のみポーリングし、変更時にクラスを切り替えます。フレームごとの文字列構築は禁止。
- 繰り返し要素(ピップ、セグメント)はコードで一度だけ構築し、ソースが変更されたときにのみ再構築します。リロード時にキャッシュリストをクリアします。
- タイマー/パルス:
element.schedule.Execute(...).Every(ms)は一時停止状態で作成し、必要な間だけ再開します。ワンショットフラッシュ:.StartingIn(ms)。 - HUD要素:
picking-mode="Ignore"をすべてに設定し、フォーカス可能なコントロールは使用しません。メニューは例外です。 - ファイル名 = MonoBehaviourのクラス名。
4. レイアウト(ほとんどのビジュアルバグの原因)
- 関連する部品を個別に推測したピクセルオフセットで配置しないでください:2つの絶対パネルが
bottomの盲目的な推測により重なっていました。それらを1つのflexコンテナに入れ、そのコンテナのみを配置します。 - 画面ゾーンごとに1つの絶対配置コンテナを使用し、子要素はflexとマージンでフローさせます。
- レイアウトがジャンプしてはいけない場合は
opacity+ トランジションで非表示にします。display:noneはスペースを折りたたむ必要がある場合のみ使用します。 - 数値ラベルには固定
widthを設定し、9から10、100に変わっても隣の要素がずれないようにします。 - ボックスはborder-box:セグメント行の幅 =
count x (width + margin) - margin。関連するウィジェットは、同一の左端と計算された幅で位置を揃えます。 - Panel Settings:Scale With Screen Size(例:1920x1080、match 0.5)。HUDの場合はClear ColorとClear Depth StencilをOFFにします。PanelRendererのSort Orderを使用して部品をレイヤー化します(全画面ビネットはテキストの下に)。
5. USSと状態
- 状態 = C#から切り替えるクラス(
EnableInClassList)。アニメーション = USSトランジション。コード内で色を設定したりアニメーションさせたりしないでください。 - 状態は意味で命名:
--low、--critical、--hidden、--hit、--pulse、--unavailable。新しい部品ではBEMを推奨します。 - オーバーライドルール(使用済みセグメント、フラッシュ)は、上書きするルールの後に配置します。同一の詳細度で、最後のルールが優先されます。
- トランジションは、行き先の状態のduration/delayを使用します:
.x--hidden { opacity:0; transition-duration:.5s; transition-delay:.9s; }は遅れてフェードアウトし、即座にフェードイン。.x--flash { transition-duration:.05s; }は即座にオンになり、基本速度でフェードオフ。 - 背景なしで浮かぶテキストには
text-shadowが必要です。text-transformは使用しないでください(C#で大文字化)。ラジアルグラデーションは使用しないでください(ビネットを参照)。 - スキン変更可能なUIの場合は、色/サイズをルートクラスのUSS変数(
--c-text、var(--c-text)で使用)に配置します。ユーザーのUnityバージョンがサポートしているか確認してください。
6. コントローラースケルトン(PanelRenderer)
[RequireComponent(typeof(PanelRenderer))]
public class PartController : MonoBehaviour
{
[SerializeField] private SourceComponent source; // null = 実行時に検索
private PanelRenderer _pr; private VisualElement _block; private Label _value;
private bool _bound; private float _v = -1f; private int _shown = -1; private float _nextFind;
private void Awake() => _pr = GetComponent<PanelRenderer>();
private void OnEnable() { _pr.RegisterUIReloadCallback(OnUIReload); TryBind(); }
private void OnDisable() { _pr.UnregisterUIReloadCallback(OnUIReload); Unbind(); _block = null; _value = null; }
private void Update()
{
if (_bound || Time.unscaledTime < _nextFind) return;
_nextFind = Time.unscaledTime + 0.5f; TryBind();
}
private void TryBind()
{
if (_bound) return;
if (source == null) source = FindFirstObjectByType<SourceComponent>();
if (source == null) return;
source.OnChanged += OnChanged; _bound = true;
_v = source.Current; Apply(); // 現在の状態を一度取得
}
private void Unbind() { if (_bound && source != null) source.OnChanged -= OnChanged; _bound = false; }
private void OnUIReload(PanelRenderer r, VisualElement root)
{
_block = root.Q<VisualElement>("part"); _value = root.Q<Label>("part-value");
if (_block == null || _value == null) { Debug.LogError("Part: elements not found in UXML (check Source Asset).", this); _block = null; return; }
_shown = -1; Apply(); // 新しいツリー:キャッシュから強制書き換え
}
private void OnChanged(float v) { _v = v; Apply(); }
private void Apply()
{
if (_block == null || _v < 0f) return;
int n = Mathf.CeilToInt(_v);
if (n != _shown) { _shown = n; _value.text = n.ToString(); } // 変更時のみ書き込み
_block.EnableInClassList("part--low", _v <= 0.3f);
}
}
7. 再利用可能なスニペット
- セグメント/ピップ:
OnUIReload内でrow.Clear(); list.Clear();を実行し、pickingMode=IgnoreのVisualElementをN個、クラスsegで追加します。変更時はlist[i].EnableInClassList("seg--spent", i >= filled)。値 > 0 の間は最低1つはfilledを表示:filled = v<=0 ? 0 : Clamp(CeilToInt(v*N-0.001f),1,N)。 - パルス:
_pulse = _block.schedule.Execute(() => _block.ToggleInClassList("part--pulse")).Every(400); _pulse.Pause();状態に入るときに再開し、離れるときにPause + クラス削除。_pulsingboolを追跡し、リロード時にリセットします。 - イベント時のフラッシュ:
var b=_block; b.AddToClassList("part--hit"); b.schedule.Execute(()=>b.RemoveFromClassList("part--hit")).StartingIn(150); - ビネット: Awakeで一度だけ128x128の
Texture2D(RGBA32、Clamp、Bilinear、HideFlags.HideAndDontSave)を生成します。アルファは中心からの距離に基づきSmoothStep(0,1,InverseLerp(inner,outer,dist/1.4142))、RGBは白にします。OnUIReloadで_el.style.backgroundImage = new StyleBackground(tex)を設定し、OnDestroyで破棄します。USS:全画面絶対要素、opacity:0、-unity-background-scale-mode: stretch-to-fill、-unity-background-image-tint-colorで色を設定、クラス--low/--critical/--pulse/--flashでopacityを設定(flashは最後、高速duration)。テキスト部品より低いSort Orderにします。
8. ジャンルとプラットフォーム
アーキテクチャはジャンル間で同一です。スタイルガイドと入力モデルが変わります。アーケード:大きくてごつい数字、カウントアップ、スケールポップ、コンボグロー、パンチの効いた0.08〜0.25秒のトランジション、コンテナを歓迎。レトロ/ピクセル:ピクセルフォント、整数スケーリング、Pointフィルターテクスチャ、スプライト9スライスボーダー。モバイル:タッチターゲット約44〜48dp、デバイスのセーフエリアをルートパディングとして適用(ユーザーのUnityバージョンに適したAPIを確認)、ホバー状態なし、大きめの最小フォント、実機でテスト。コンソール/ゲームパッド:フォーカス可能な要素と明確な :focus スタイル、明示的な初期フォーカス、TVセーフマージン(3〜5%)、大きめのテキスト、アクティブなデバイスに応じて切り替わるグリフ。VR/ワールドスペース:Unityバージョンに依存するため、まず調べてください。これらのプラットフォームポイントは一般的な良いプラクティスですが、実プロジェクトでテストされていません。その旨を伝え、ユーザーに実機で確認するよう依頼してください。入力ラベル(キー名またはグリフ)は、コントローラーが読み取るデータとして保持し、UXMLにハードコードしないでください。
9. 部品の納品
順序:完全なUXML、完全なUSS、完全なコントローラー、セットアップ手順(GameObject、PanelRenderer + 共有Panel Settings + Source Asset、Sort Order、埋めるべき参照、Playで確認すべきこと)、チューニング箇所(どのUSS値がサイズ/位置/色/閾値を変更するか)、および短いスタイルガイドエントリ。既存ファイルを変更する場合、ファイルの大部分が変わる場合はファイル全体を提示してください(部分的な貼り付けは実際のエラーを引き起こしました)。
10. トラブルシューティング
| 症状 | 原因 / 修正方法 |
|---|---|
rootVisualElement / GetComponent<UIDocument>() でNullReference | コードがUIDocumentを使用しているが、オブジェクトにはPanelRendererがある。RegisterUIReloadCallback を使用し、OnUIReload 内でバインドする |
| コントローラーが「要素が見つかりません」とログ出力 | Source Assetが間違っている、Q<>() と名前が一致しない、またはUXMLが無効 |
| UI Builderで「Failed to open asset」 | 無効なUXML(重複した </ui:UXML>、不正な <Style>、プレースホルダーGUID、不可視文字)。修正後、ファイルを右クリック > Reimport。汎用エラーの上のコンソールメッセージを読む |
| スタイルが適用されない | <Style> がない、またはパスが間違っている。UI Builderで:StyleSheets > + > Add Existing USS(project://database/...guid=... という完全パスが書き込まれる) |
| 編集後すべてが消えた | UXMLが壊れた、または名前変更後にSource Assetの参照が古くなった。修正、Reimport、再割り当て。ルートに background-color: rgba(255,0,0,.3) を適用してテスト |
| 部品が別の部品とずれている、または重なっている | 古いUXMLを描画している古いGameObjectが残っている、または個別の絶対オフセット。古いオブジェクトを削除し、隣接する部品を1つのコンテナにマージ |
| Gameビューにプレースホルダーテキストが表示される | 編集モードではUXMLが描画されます。Playを押す |
| スクリプトを追加できない | ファイル名 != クラス名。UnityのProjectウィンドウ内で名前を変更し、.meta が追随するようにする |
有効でない修正方法:スタイルシートタグは <Style src="..."/> であり、ui: プレフィックスは不要(<ui:Style> は黙って無視されます)。プレースホルダーGUIDを発明したり残したりしないでください。標準ヘッダーを使用:<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">。
11. エディタを見ずに作業する
レイアウトの不具合についてはスクリーンショットを要求し、それを注意深く読んでください。これにより、推測では解決できなかったバグを解決できました。自分のミスは簡潔に認め、完全なファイルで修正します。コンパイルエラーについては、再推測する代わりに、正確なテキストとUnityバージョンを要求してください。デバイスの挙動やパフォーマンス値は約束しないでください。構造を作成し、ユーザーにテストを依頼してください。