Optymalizacja Unity WebGL: Oddzielne buildy dla Mobile Web i Desktop Web

Dlaczego warto stosować podwójne buildy w swojej grze Unity 3D

Kompatybilność tekstur

Jednym z kluczowych powodów jest kompatybilność zasobów. W szczególności wsparcie dla tekstur różni się znacząco pomiędzy Mobile Web (urządzenia Android i iOS) a Desktop Web (macOS, Linux, Windows). Aby lepiej zrozumieć, dlaczego ma to znaczenie, przyjrzyjmy się bliżej danym:

 Desktop BrowsersiOS and Android browser
RGB Compressed ETC2NoYes
RGB Crunched ETCNoYes
RGBA Compressed ETC2NoYes
RGBA Crunched ETC2NoYes
RGB Compressed DXT1, also known as BC1Partial (*)No
RGB Crunched DXT1, also known as BC1Partial (*)No
RGBA Compressed DXT5, also known as BC3
Partial (*)  
No
RGBA Crunched DXT5, also known as BC3Partial (*)No

* With linear rendering on web browsers that do not support sRGB DXT, textures are decompressed to RGBA32 at load time.

Formaty DXT1/5 „działają” na urządzeniach mobilnych, ale następuje fallback do nieskompresowanego RGBA, co skutkuje większym zużyciem pamięci RAM i wydłużonym czasem ładowania z powodu konwersji tekstur.

Szczegółowe informacje znajdziesz w dokumentacji Unity: https://docs.unity3d.com/2021.3/Documentation/Manual/class-TextureImporterOverride.html

Optymalizacja zasobów

Oddzielenie buildów umożliwia dostarczenie zoptymalizowanych zasobów dostosowanych do urządzeń mobilnych.

Ekrany mobilne charakteryzują się bardzo dużą gęstością pikseli (dpi – pixels per inch). Oznacza to, że pixel shader na małym wyświetlaczu 5–6 cali wykonuje prawie tyle samo obliczeń, co na pełnym monitorze 2K przy znacznie słabszym mobilnym GPU. Dodatkowo CPU w urządzeniach mobilnych jest znacząco słabsze i często ograniczone do pracy jednordzeniowej.

Tutaj z pomocą przychodzą podwójne buildy, pozwalają one dostarczyć alternatywne zasoby i ustawienia dla urządzeń mobilnych, takie jak:

  • Zoptymalizowane shadery i materiały
  • Dostosowana jakość cieni
  • Uproszczone oświetlenie (np. w pełni wypalone z obsługą tylko 1–2 dynamicznych źródeł światła)
  • Modele o zredukowanej liczbie polygonów (modele wysokopoligonowe są trudne do przetwarzania przy ograniczonej liczbie jednostek wierzchołkowych w mobilnych GPU, a dodatkowe detale są zbędne na małych ekranach)
  • Uproszczone, przyjazne GPU lub wypalone animacje (istotne dla wszystkich buildów Web; animacje szkieletowe, które nie mogą być w pełni przetwarzane przez GPU, szybko stają się problemem)

Optymalizacja zachowania i kodu

Dyrektywy kompilatora, generowanie kodu i podobne techniki pozwalają dostarczać kod i zachowania specyficzne dla danej platformy, bez wprowadzania kar wydajnościowych ani nadmiernej abstrakcji.

Takie podejście umożliwia:

  • Optymalizację metod wprowadzania danych dla różnych urządzeń (dotyk, mysz, kontroler itp.)
  • Dostosowanie układu interfejsu (UI) do mniejszych ekranów lub specyficznych metod interakcji
  • Zastępowanie lub upraszczanie kosztownych procedur zoptymalizowanymi wersjami

Jak tworzyć podwójne buildy

Tutaj znajdziesz mój przykładowy projekt pokazujący automatyzację buildów, rozdzielenie Mobile/Desktop Web oraz wsparcie dla Dockerizacji:
https://github.com/AndreiMaksimovich/Unity-Build-and-Test-Automation

Oddzielne buildy

Przykład tego podejścia jest dostępny w repozytorium wymienionym powyżej.
Główna idea polega na generowaniu oddzielnych buildów dla każdego wymaganego typu platformy – takich jak Mobile Web, Desktop Web, Tablety mobilne (ekrany dotykowe 9 cali i większe) – a następnie automatycznym przekierowaniu gracza do odpowiedniego builda.

Scalony build

Idea stojąca za tą metodą polega na połączeniu wielu buildów w jeden. Dzięki temu możemy programistycznie wybrać różne źródła danych, kodu i zasobów jeszcze przed inicjalizacją Unity playera. Przyjrzyjmy się, jak można to zrealizować.

Przyjrzyjmy się domyślnemu szablonowi Unity Web w Unity 6.2 – a dokładniej plikowi index.html w liniach 61–81:

      var buildUrl = "Build";
      var loaderUrl = buildUrl + "/{{{ LOADER_FILENAME }}}";
      var config = {
        arguments: [],
        dataUrl: buildUrl + "/{{{ DATA_FILENAME }}}",
        frameworkUrl: buildUrl + "/{{{ FRAMEWORK_FILENAME }}}",
#if USE_THREADS
        workerUrl: buildUrl + "/{{{ WORKER_FILENAME }}}",
#endif
#if USE_WASM
        codeUrl: buildUrl + "/{{{ CODE_FILENAME }}}",
#endif
#if SYMBOLS_FILENAME
        symbolsUrl: buildUrl + "/{{{ SYMBOLS_FILENAME }}}",
#endif
        streamingAssetsUrl: "StreamingAssets",
        companyName: {{{ JSON.stringify(COMPANY_NAME) }}},
        productName: {{{ JSON.stringify(PRODUCT_NAME) }}},
        productVersion: {{{ JSON.stringify(PRODUCT_VERSION) }}},
        showBanner: unityShowBanner,
      };

To, na co tutaj patrzymy, to konfiguracja inicjalizacji instancji Unity. Kluczową kwestią jest to, że konfiguracja ta jest skryptowalna – co oznacza, że możemy dynamicznie zmieniać adresy URL pliku Data, pliku Wasm oraz Streaming Assets „w locie”, skutecznie przełączając zarówno bazę kodu, jak i zasoby.

Teraz przyjrzyjmy się plikowi index.html w buildzie webowym:

      var buildUrl = "Build";
      var loaderUrl = buildUrl + "/Build.loader.js";
      var config = {
        arguments: [],
        dataUrl: buildUrl + "/Build.data.gz",
        frameworkUrl: buildUrl + "/Build.framework.js.gz",
        codeUrl: buildUrl + "/Build.wasm.gz",
        streamingAssetsUrl: "StreamingAssets",
        companyName: "DefaultCompany",
        productName: "Demo",
        productVersion: "1.0.2",
        showBanner: unityShowBanner,
      };

Można to łatwo zmodyfikować do czegoś takiego:

      const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
      var buildUrl = "Build";
      var loaderUrl = buildUrl + "/Build.loader.js";
      var config = {
        arguments: [],
        dataUrl: buildUrl + (isMobile ? "/Build.Mobile.data.gz" : "/Build.data.gz"),
        frameworkUrl: buildUrl + "/Build.framework.js.gz",
        codeUrl: buildUrl + (isMobile ? "/Build.Mobile.wasm.gz" : "/Build.wasm.gz"),
        streamingAssetsUrl: (isMobile ? "StreamingAssets.Mobile" : "StreamingAssets"),
        companyName: "DefaultCompany",
        productName: "Demo",
        productVersion: "1.0.2",
        showBanner: unityShowBanner,
      };

To zapewnia prosty sposób na rozróżnienie i dostarczenie osobnego kodu oraz zasobów dla Mobile i Desktop.

Jak można to zautomatyzować:

  1. Utwórz niestandardowy szablon, w którym dataUrl, codeUrl i streamingAssetsUrl zostaną zastąpione tak, jak pokazano powyżej.
  2. Użyj tego szablonu, aby zbudować projekt osobno dla Mobile i Desktop.
  3. Połącz buildy w jeden – zasadniczo poprzez skopiowanie plików Data, Wasm i Asset z jednego builda do drugiego oraz zmianę ich nazw tak, aby odpowiadały konfiguracji w szablonie.

Uwaga: To jest uproszczone wyjaśnienie, ale działa i może zostać rozszerzone, aby dopasować się do konkretnych wymagań.