Każdy przykład w tym poradniku wyszedł z jednego projektu: gęstej gry symulacyjnej z około dwudziestoma ekranami, wszystkimi budowanymi w C# na jednym pliku USS, wydanej jako publiczny build.
Piszę to, bo oficjalna dokumentacja nie ma przewodnika koncepcyjnego, a większość pytań o UI Toolkit, których szukałem w trakcie pracy, nie miała zaakceptowanej odpowiedzi. To nie jest tutorial. To lista rzeczy, z których każda kosztowała mnie wieczór.
Czego USS nie ma
Wzór jest konsekwentny: USS dobrze pokrywa układ i kolor, a kończy się na wszystkim, co maluje kształt albo reaguje na pozycję w liście.
| Czego chcesz | Status | Czego użyć zamiast |
|---|---|---|
linear-gradient() |
Niedostępne | Wypal do tekstury 256×1 i rozciągnij jako tło, albo namaluj przez Painter2D |
| Łuki, pierścienie, koła | Niedostępne | Painter2D w generateVisualContent. Pierścień z gradientem trzeba obrysować krótkimi odcinkami, bo Painter2D rysuje jednym kolorem |
clip-path, maski |
Niedostępne | Wypal kształt w kanale alfa samego obrazu |
@keyframes |
Niedostępne | transition do prostych przypadków albo podmiana klas z C# na harmonogramie |
:last-child, :first-child, :nth-child |
Niedostępne | Ujemny margines na kontenerze albo wyczyszczenie marginesu ostatniego dziecka z C# |
Układ siatki, gap |
Niedostępne | Flexbox z marginesami na dzieciach |
Pierwsze cztery są niewygodne i oczywiste: dowiadujesz się natychmiast, bo nic się nie pojawia. Piąta jest tą groźną.
Reguły, które się parsują i nic nie robią
Selektory pozycyjne zawodzą po cichu, u gracza, na zawsze
Reguła w rodzaju .pack:last-child { margin-right: 0; } jest przyjmowana
przez parser, odrzucana i logowana jako Unknown pseudo class "last-child"
w logu gracza przy każdym uruchomieniu. Nic w edytorze ci tego nie powie. Reguła po
prostu nigdy nie działa.
W buildzie, który wydałem, pięć takich reguł było martwych od dnia napisania. Znalazłem je, czytając log skompilowanego gracza, a nie z żadnego testu, bo test nigdy nie czyta logu.
Jeśli chcesz rzędu elementów bez marginesu na końcu, są dwie uczciwe opcje. Dajesz kontenerowi ujemny prawy margines równy odstępowi między dziećmi, żeby margines ostatniego został wchłonięty. Albo czyścisz go z C# po wypełnieniu rzędu:
Błąd, który kasuje pół ekranu
To jest najdroższa pomyłka w UI Toolkit, jaką popełniłem, i nie ma żadnego komunikatu.
Klasa nazwana z C#, której nigdy nie wpisano do arkusza
AddToClassList("creator-column") kompiluje się. Działa. Element bierze
domyślny flex, co w kolumnie zwykle znaczy, że kurczy się do zera. U mnie cały panel z
odczytami i notatkami był niewidoczny na ekranie, który oglądałem codziennie, a
244 testy automatyczne były przez cały czas zielone.
Dwie obrony. Ustaw flex-shrink: 0 na swojej klasie panelu, żeby brak
reguły degradował się do przepełnienia, a nie do zniknięcia. I napisz test, który
czyta arkusz stylów, zbiera każdy napis przekazany do AddToClassList i
EnableInClassList w całym źródle i wywala się, gdy któryś nie ma reguły.
Ten strażnik złapał tę klasę błędu w tym projekcie częściej niż jakikolwiek inny test, który mam, w tym dwa razy w trakcie pisania artykułu, który właśnie czytasz.
Runtime theme już styluje twoje kontrolki
Suwaki, przełączniki, pola tekstowe i listy rozwijane przychodzą wstępnie ostylowane przez runtime theme Unity, w jasnym idiomie edytorskim. Na ciemnym ekranie gry wyglądają jak blade szare płyty.
Odruch mówi, żeby sięgnąć do wewnętrznych nazw elementów motywu i je nadpisać. Ten odruch kosztował mnie cztery błędne poprawki z rzędu.
Nie walcz z wnętrzem motywu
Celowanie w .unity-toggle__checkmark, żeby usunąć białą płytę, usunęło
też znacznik, więc kontrolka stała się pudełkiem, które nie umie pokazać własnego
stanu. Celowanie w unity-base-slider__dragger-border dało cztery
podejścia, z których każde naprawiało objaw gdzie indziej.
Niezawodną odpowiedzią jest przestać nadpisywać i zacząć rysować. Znacznik to przycisk, narysowane pudełko i wypełniony blok. To piętnaście linii, nie da się tego zepsuć aktualizacją motywu i wygląda jak reszta twojej gry.
Uważaj też na globalne ograniczenia. Reguła w rodzaju
.unity-base-slider { max-width: 560px } gdziekolwiek w arkuszu dotyczy każdego
suwaka w projekcie. Na rzędzie szerokim na 1370 pikseli moje suwaki kończyły się na
560, a nakładka oznaczająca ich zablokowaną część zgadzała się z rowkiem tylko przez
przypadek.
Cztery rzeczy, które wyglądają dobrze i nie są
Button z wysokością nie centruje własnej etykiety
Rysuje tekst w lewym górnym rogu pudełka. Ustaw
-unity-text-align: middle-center na każdym przycisku, któremu nadajesz
jawną wysokość.
Runtime theme dodaje margines każdemu przyciskowi
Cztery karty dopasowane do strony o szerokości 1340 pikseli mierzyły 1352, bo
każda odziedziczyła margines z motywu. Czwarta zawinęła się do własnego rzędu i czytało
się to jak celowy układ trzy w rzędzie, a nie jak przepełnienie. Ustawiaj
margin-left i margin-top jawnie na każdym stylowanym Buttonie.
Formatowanie liczb idzie za maszyną, nie za grą
Surowe $"{value:N2}" wypisuje 20,00 na maszynie z polskim
ustawieniem regionalnym. To nie jest wina UI Toolkit, ale wychodzi w napisach UI
Toolkit częściej niż gdziekolwiek indziej, bo tam liczby spotykają się z ekranem.
Przepuszczaj każdą liczbę przez jeden formater używający
InvariantCulture. Złapało mnie to na tym samym projekcie trzy razy.
Referencja do PanelSettings potrafi zniknąć przy przebudowie sceny
Przypisanie document.panelSettings z narzędzia edytorskiego nie przeżywało
zapisu na dysk dla drugiej sceny w przebiegu, za każdym razem, deterministycznie.
Interfejs był budowany do żadnego panelu i rysowany nigdzie: ekran był kolorem
czyszczenia kamery i niczym więcej, co wygląda dokładnie jak zawieszenie. Przypisuj
pola serializowane przez SerializedObject w narzędziach edytorskich i
odczytuj pole serializowane z powrotem, a nie właściwość, którą właśnie ustawiłeś.
Jak w ogóle to zobaczyć
Żaden z powyższych błędów nie rzuca wyjątku. Większość jest niewidoczna dla zestawu testów, który nigdy nie otwiera okna. Jedyną niezawodną metodą, jaką znalazłem, jest wyrenderowanie interfejsu do tekstury i popatrzenie na obrazek.
Test PlayMode, który wczytuje scenę, kieruje panel na RenderTexture, przechodzi
każdy ekran i zapisuje każdą klatkę do PNG, kosztuje jakieś osiemdziesiąt linii. Jeden
przegląd powstałej kontaktówki znalazł cztery usterki, których 683 zielone testy nie
zauważyły: każdy stylowany przycisk rysujący etykietę w lewym górnym rogu, jedenaście
kafelków bez grafiki, bo plików nigdy nie zrobiono, pasek różnicy skalowany złą wartością i
pieniądze wypisane jako $20,00.
Sprawdź w teście dwie rzeczy o klatce, żeby obrazek nie mógł cię okłamać: że nie jest jednolitym kolorem, bo tekstura, do której nikt nic nie narysował, jest nie do odróżnienia od ciemnego ekranu, który zadziałał, oraz że nic nie wychodzi poza prawą krawędź.
Czy UI Toolkit i tak się opłaca?
W tym projekcie tak i wybrałbym go ponownie. Gęsty interfejs budowany w C# na jednym arkuszu jest dużo łatwiejszy do utrzymania w spójności niż ten sam interfejs składany ręcznie w scenie, a każdy ekran może budować funkcja, którą test umie wywołać.
Ale wchodź w to ze świadomością wymiany. Dokumentacja jest uboga, spora część pytań na forum nie ma odpowiedzi, motyw styluje twoje kontrolki, zanim zrobisz to ty, a brakujące funkcje USS to dokładnie te, po które sięgasz pierwszego dnia. Nic z tego nie jest zabójcze. Wszystko jest łatwiejsze, gdy ktoś wcześniej spisał listę.
Wszystko tutaj wyszło ze Scaling Laws, tycoona o firmie AI zbudowanego w Unity i wydanego jako darmowy publiczny build. Kod jest otwarty, łącznie z arkuszem stylów i opisanymi wyżej testami strażniczymi.
Pytania, które ludzie zadają
Czy USS obsługuje gradienty?
Nie. Wypal gradient do tekstury 256×1 i rozciągnij albo namaluj przez Painter2D. Tekstura jest tańsza i jest właściwą odpowiedzią dla poziomego paska.
Czy USS obsługuje :last-child?
Nie, i żadnej innej pseudoklasy pozycyjnej też. Reguła się parsuje, zostaje odrzucona i ostrzega w logu gracza przy każdym uruchomieniu, nie stylując niczego.
Dlaczego moja klasa USS nic nie robi?
Najczęściej dlatego, że jest nazwana z C# i nigdy nie trafiła do arkusza. Element bierze domyślny flex i zwija się po cichu. Napisz test porównujący obie listy.
Dlaczego tekst na przycisku nie jest wyśrodkowany?
Button z jawną wysokością nie centruje własnej etykiety. Ustaw
-unity-text-align: middle-center.
UI Toolkit czy uGUI w 2026?
UI Toolkit do gęstego interfejsu opartego na danych i budowanego w kodzie, i to jest kierunek Unity. uGUI nadal jest prostsze przy kilku nieskomplikowanych ekranach.