MericKarabulut/unity-ui-toolkit

Construisez et déboguez l'interface utilisateur d'un jeu Unity avec UI Toolkit (UXML + USS + contrôleurs C#) pour tout genre et plateforme – HUD, barres de vie/munitions/endurance, invites d'interaction, vignettes de dégâts, menus, écrans de pause, compteurs de score, écrans de cinématiques.

Qu'est-ce que unity-ui-toolkit ?

Cette compétence fournit une approche structurée pour créer des interfaces utilisateur de jeu dans Unity en utilisant UI Toolkit, en combinant UXML pour la structure, USS pour le style et de petits contrôleurs C# réactifs. Elle couvre à la fois le nouveau workflow PanelRenderer (sans UIDocument) et le workflow classique UIDocument, en insistant sur la création d'un guide de style par jeu pour garantir la cohérence visuelle, la disposition sans deviner les pixels, et le débogage des problèmes courants de UI Builder, USS et UXML. L'architecture impose un contrôleur par partie, évite les monolithes et privilégie la poussée par événements plutôt que l'interrogation. La compétence inclut des sections détaillées sur la disposition (conteneurs flex, positionnement absolu), les états visuels via classes CSS et transitions, les modèles de code réutilisables (segments, pulsations, flashes, vignettes) et une table de dépannage complète pour les problèmes fréquents. Elle est conçue pour être utilisée avec Claude Code, Cursor, Codex ou tout agent IA intégré à Unity, dès que l'utilisateur mentionne Unity UI, UI Toolkit, UXML, USS, PanelRenderer, UIDocument, UI Builder, un HUD, une barre de vie, un compteur de munitions ou un menu.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/MericKarabulut/claude-skills/tree/HEAD/skills/unity-ui-toolkit

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

UI Toolkit pour UI de jeu Unity

Structure en UXML, apparence en USS, comportement dans de petits contrôleurs C# qui réagissent aux événements du jeu. Issu d'un projet réel (HUD de jeu d'horreur-tir) ; la liste de dépannage est la partie la plus susceptible de faire gagner du temps.

1. Avant d'écrire du code

Demandez uniquement ce qui manque, en un seul lot court :

  • API UI. [RequireComponent(typeof(PanelRenderer))] + RegisterUIReloadCallback = workflow PanelRenderer (pas de UIDocument, pas de rootVisualElement). UIDocument + rootVisualElement = classique. Déduisez-le du code collé ; si ce n'est pas clair, demandez un contrôleur fonctionnel et son UXML. Ne mélangez jamais les deux (provoque des NullReferenceExceptions). Un fichier fonctionnel de l'utilisateur est meilleur que votre mémoire de l'API.
  • Genre/thème (avec captures d'écran ou palette), plateforme et entrées, résolution/rapport de référence.
  • Les scripts du jeu auxquels l'UI s'abonne. Demandez-les, lisez les événements/propriétés publiques réels, liez-vous à ceux-ci. Si un événement manque, proposez le plus petit ajout au script du jeu.
  • Répondez dans la langue de l'utilisateur ; écrivez les commentaires de code dans la langue utilisée par les scripts existants.

2. Guide de style d'abord

La cohérence est ce que les utilisateurs remarquent en premier. Avant de construire plusieurs parties : proposez un court guide, construisez UNE petite partie, laissez l'utilisateur réagir, et transformez ses retours en règles (« pas de boîte d'arrière-plan », « plus compact »). Sauvegardez-le dans le projet (ex. Assets/UI/UI_STYLE_GUIDE.md) et dites à l'utilisateur de le coller dans ses futures demandes UI. Mettez-le à jour lorsque des parties ou des règles changent. Contenu : langage de conception (ton, conteneurs, densité, mouvement), tableau de palette (texte, atténué, avertissement, critique, ombre), typographie (tailles par rôle, règle d'ombre), mise en page (zones, marges, Panel Settings), conventions de nommage USS et classes d'état, règles d'architecture de code, pièges UXML, liste des parties existantes, liste de contrôle pour une nouvelle partie.

3. Architecture

  • Une partie = un contrôleur + un UXML + un USS, avec une source de données. Pas de méga-contrôleur.
  • Les parties qui doivent être ensemble partagent un UXML/conteneur flex (deux contrôleurs peuvent partager un GameObject/PanelRenderer ; chacun trouve ses propres éléments par nom).
  • Les contrôleurs ne font que trouver des éléments, définir du texte/valeurs et basculer des classes USS. Les couleurs, tailles et animations vivent dans USS.
  • PanelRenderer : la racine arrive via RegisterUIReloadCallback(OnUIReload) (enregistrer dans OnEnable, désenregistrer dans OnDisable). Il se déclenche quand l'UI est prête et à chaque rechargement, donc TOUTES les recherches Q<T>("name"), la mise en cache, la liaison d'événements et la réinitialisation d'état y vont. Protégez Update() avec un null-guard jusqu'à ce que les éléments existent.
  • Les événements du jeu peuvent se déclencher avant que l'UI n'existe : mettez en cache les dernières valeurs dans des champs, réappliquez à la fin de OnUIReload.
  • Les objets joueur/jeu peuvent apparaître tard : référence sérialisée, sinon FindFirstObjectByType limité (~0.5 s), puis liez ; désabonnez-vous dans OnDisable.
  • Poussez, ne scrutez pas. Abonnez-vous aux événements et écrivez uniquement en cas de changement. Scrutez uniquement les valeurs sans événement (endurance continue, un booléen de rechargement), en basculant les classes en cas de changement. Pas de construction de chaîne par frame.
  • Construisez les éléments répétés (pips, segments) en code une fois ; reconstruisez uniquement lorsque leur source change ; effacez les listes mises en cache lors du rechargement.
  • Minuteurs/pulsations : element.schedule.Execute(...).Every(ms) créé en pause, repris uniquement si nécessaire. Flashs uniques : .StartingIn(ms).
  • Éléments HUD : picking-mode="Ignore" partout, pas de contrôles focusables. Les menus sont l'exception.
  • Nom de fichier = nom de la classe MonoBehaviour.

4. Mise en page (source de la plupart des bugs visuels)

  • Ne placez jamais des parties liées avec des décalages en pixels devinés séparément : deux panneaux absolus se chevauchaient parce que bottom a été estimé à l'aveugle. Mettez-les dans un seul conteneur flex et positionnez uniquement ce conteneur.
  • Un conteneur en position absolue par zone d'écran ; les enfants s'écoulent avec flex et marges.
  • Masquez avec opacity + transition lorsque la mise en page ne doit pas sauter ; display:none uniquement lorsque l'espace doit se réduire.
  • width fixe sur les étiquettes numériques afin que 9, 10 et 100 ne décalent pas les voisins.
  • Les boîtes sont en border-box : largeur de rangée de segments = count x (width + margin) - margin. Alignez les widgets liés par un bord gauche identique et une largeur calculée.
  • Panel Settings : Scale With Screen Size (ex. 1920x1080, match 0.5) ; pour les HUD, désactivez Clear Color et Clear Depth Stencil. Utilisez Sort Order du PanelRenderer pour superposer les parties (vignette plein écran en dessous du texte).

5. USS et état

  • État = classe basculée depuis C# (EnableInClassList) ; animation = transition USS. Ne définissez jamais les couleurs ou n'animez pas en code.
  • Nommez les états par leur signification : --low, --critical, --hidden, --hit, --pulse, --unavailable. Préférez BEM pour les nouvelles parties.
  • Les règles de surcharge (segments dépensés, flash) vont APRÈS les règles qu'elles remplacent ; spécificité égale, la dernière gagne.
  • Une transition utilise la durée/délai de l'état de destination : .x--hidden { opacity:0; transition-duration:.5s; transition-delay:.9s; } disparaît tard et apparaît instantanément ; .x--flash { transition-duration:.05s; } s'allume rapidement, s'éteint à la vitesse de base.
  • Le texte flottant sans arrière-plan a besoin de text-shadow. Pas de text-transform (majuscule en C#) ; pas de dégradés radiaux (voir vignette).
  • Pour une UI re-skinable, mettez les couleurs/tailles dans des variables USS sur une classe racine (--c-text, utilisé comme var(--c-text)) ; vérifiez que la version Unity de l'utilisateur le prend en charge.

6. Squelette de contrôleur (PanelRenderer)

[RequireComponent(typeof(PanelRenderer))]
public class PartController : MonoBehaviour
{
    [SerializeField] private SourceComponent source;      // vide = trouver à l'exécution
    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();                      // récupérer l'état actuel une fois
    }
    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 : éléments introuvables dans UXML (vérifier Source Asset).", this); _block = null; return; }
        _shown = -1; Apply();                              // nouvel arbre : forcer la réécriture depuis le cache
    }
    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(); }   // écrire uniquement en cas de changement
        _block.EnableInClassList("part--low", _v <= 0.3f);
    }
}

7. Extraits réutilisables

  • Segments/pips : dans OnUIReload, row.Clear(); list.Clear(); puis ajoutez N VisualElement{pickingMode=Ignore} avec la classe seg ; en cas de changement list[i].EnableInClassList("seg--spent", i >= filled). Affichez au moins 1 rempli tant que la valeur > 0 : filled = v<=0 ? 0 : Clamp(CeilToInt(v*N-0.001f),1,N).
  • Pulsation : _pulse = _block.schedule.Execute(() => _block.ToggleInClassList("part--pulse")).Every(400); _pulse.Pause(); Reprendre en entrant dans l'état, Pause + retirer la classe en sortant ; suivez un booléen _pulsing et réinitialisez-le lors du rechargement.
  • Flash sur événement : var b=_block; b.AddToClassList("part--hit"); b.schedule.Execute(()=>b.RemoveFromClassList("part--hit")).StartingIn(150);
  • Vignette : générez une fois dans Awake un Texture2D 128x128 (RGBA32, Clamp, Bilinear, HideFlags.HideAndDontSave) où alpha = SmoothStep(0,1,InverseLerp(inner,outer,dist/1.4142)) depuis le centre, RGB blanc ; définissez _el.style.backgroundImage = new StyleBackground(tex) dans OnUIReload ; détruisez dans OnDestroy. USS : élément absolu plein écran, opacity:0, -unity-background-scale-mode: stretch-to-fill, -unity-background-image-tint-color pour la couleur, classes --low/--critical/--pulse/--flash définissant l'opacité (flash en dernier, durée rapide). Sort Order plus bas que les parties de texte.

8. Genres et plateformes

L'architecture est identique d'un genre à l'autre ; le guide de style et le modèle d'entrée changent. Arcade : gros chiffres en bloc, comptage, pop d'échelle, lueur de combo, transitions percutantes de 0,08 à 0,25 s, conteneurs bienvenus. Rétro/pixel : police pixel, mise à l'échelle entière, textures filtrées Point, bordures 9-slice sprite. Mobile : cibles tactiles d'environ 44-48 dp, zone de sécurité de l'appareil comme rembourrage racine (vérifiez l'API actuelle pour la version Unity de l'utilisateur), pas d'états au survol, police minimale plus grande, testez sur un appareil réel. Console/manette : éléments focusables avec un style :focus fort, focus initial explicite, marges TV-safe (3-5 pour cent), texte plus grand, glyphes qui changent avec l'appareil actif. VR/espace monde : dépend de la version Unity, vérifiez d'abord. Ces points de plateforme sont une bonne pratique générale mais non testés dans un projet réel : dites-le et demandez à l'utilisateur de vérifier sur l'appareil. Conservez les étiquettes d'entrée (nom de touche ou glyphe) dans les données lues par le contrôleur, pas codées en dur dans UXML.

9. Livraison d'une partie

Dans l'ordre : UXML complet, USS complet, contrôleur complet, étapes de configuration (GameObject, PanelRenderer + Panel Settings partagé + Source Asset, Sort Order, références à remplir, quoi voir dans Play), boutons de réglage (quelle valeur USS change la taille/position/couleur/seuils), et une courte entrée dans le guide de style. Lorsque vous modifiez des fichiers existants, donnez les fichiers entiers si la majeure partie du fichier change (des collages partiels ont causé de vraies erreurs).

10. Dépannage

SymptômeCause / correctif
NullReference sur rootVisualElement / GetComponent<UIDocument>()Le code utilise UIDocument mais l'objet a PanelRenderer. Utilisez RegisterUIReloadCallback, liez dans OnUIReload
Le contrôleur journalise « élément introuvable »Mauvais Source Asset, nom ne correspondant pas à Q<>(), ou UXML invalide
UI Builder « Échec d'ouverture de l'actif »UXML invalide (duplicata </ui:UXML>, mauvais <Style>, GUID de placeholder, caractères cachés). Corrigez, puis cliquez droit sur le fichier > Reimport. Lisez le message dans la console au-dessus de l'erreur générique
Éléments non stylisés<Style> manquant ou mauvais chemin. Dans UI Builder : StyleSheets > + > Add Existing USS (écrit le chemin complet project://database/...guid=...)
Tout a disparu après une modificationL'UXML s'est cassé, ou la référence Source Asset est devenue obsolète après un renommage. Corrigez, Reimport, réassignez. Testez avec background-color: rgba(255,0,0,.3) sur la racine
Partie mal placée ou chevauchant une autreGameObject obsolète dessinant encore l'ancien UXML, ou décalages absolus séparés. Supprimez l'ancien objet, fusionnez les voisins dans un seul conteneur
Texte de placeholder dans la vue GameLe mode édition dessine l'UXML ; appuyez sur Play
Le script ne peut pas être ajoutéNom de fichier != nom de classe. Renommez dans la fenêtre Projet Unity pour que .meta suive

Correctifs non valides : la balise de feuille de style est <Style src="..."/> SANS le préfixe ui: (<ui:Style> est silencieusement ignoré) ; n'inventez ni ne laissez jamais un GUID de placeholder ; utilisez l'en-tête standard <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">.

11. Travailler sans voir l'éditeur

Demandez une capture d'écran en cas de plainte de mise en page et lisez-la attentivement ; cela a résolu des bugs que les devinettes ne pouvaient pas. Reconnaissez brièvement vos erreurs et corrigez avec des fichiers complets. Pour les erreurs de compilation, demandez le texte exact et la version Unity plutôt que de deviner à nouveau. Ne promettez pas de comportement sur appareil ou de chiffres de performance ; construisez la structure et demandez à l'utilisateur de tester.

Skills associés