Technologia · Unity

USS to nie CSS,
a luki są dokładnie tam,
gdzie sięgasz najpierw.

Bez gradientów. Bez łuków. Bez masek. Bez keyframes. Bez selektorów pozycyjnych. Czego naprawdę brakuje w Unity UI Toolkit, czego użyć zamiast tego i które reguły parsują się czysto, a nie stylują zupełnie niczego.

Marcin Firmuga·2026-08-11·akt. 2026-09-01·9 min czytania·Technologia

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.

Uwaga o wersji. Wszystko tutaj zaobserwowane na Unity 6000.5 z runtime UI Toolkit. Unity nadal dokłada rzeczy do USS, więc traktuj to jak zrzut stanu z datą, a nie jak listę wieczystą.

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 chceszStatusCzego 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:

// USS nie ma selektorów pozycyjnych, więc ostatnie dziecko domykamy stąd. var last = row.Children().LastOrDefault(); if (last != null) { last.style.marginRight = 0; }

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.

// Strażnik w jednym zdaniu: każda klasa, którą interfejs nazywa, musi być ostylowana. var declared = ClassNamesIn(File.ReadAllText(stylesheetPath)); foreach (var used in ClassNamesUsedInSource()) { Assert.That(declared.Contains(used), Is.True, $"{used} jest nazwana z C# i nie ma jej w arkuszu, więc bierze domyślny " + "flex i po cichu zwija to, na czym siedzi."); }

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.

Strona gry i kod źródłowy

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.

MF

Marcin Firmuga

Solo developer · HCK_Labs · buduję publicznie

Piszę o tym, co faktycznie wydałem, z prawdziwymi liczbami i prawdziwym kodem, łącznie z tym, co nic nie robiło. Więcej: moja historia.