Jak przetrwać cichy auto-update Unreal Engine w dzielonym projekcie: Rebuildowanie, synchronizacja i ponowna synchronizacja środowisk zespołowych
W skrócie
Wpis szczegółowo omawia problem cichej, automatycznej aktualizacji silnika Unreal Engine przez Epic Games Launcher oraz jej negatywny wpływ na synchronizację pracy w zespole deweloperskim. Autor analizuje techniczne skutki rozjazdu wersji patcha, w tym błędy serializacji assetów oraz niezgodności sieciowe w architekturze multiplayer. Jako skuteczną linię obrony przedstawia gotowy skrypt w Pythonie do walidacji wersji silnika oraz opisuje proces przejścia zespołu na budowanie silnika bezpośrednio z kodu źródłowego z GitHub. Wskazuje także na korzyści płynące z odseparowania trwałych systemów gry przy użyciu dedykowanego rozwiązania Backend-as-a-Service, takiego jak horizOn.
Wasz zespół jest od pięciu miesięcy w fazie produkcji wspólnego projektu Unreal Engine, historia w git jest czysta, i nagle jeden z deweloperów pobiera najnowsze zmiany (pull) i nie może już uruchomić edytora, ponieważ jego lokalny silnik po cichu zaktualizował się w nocy. Cichy patch upgrade – na przykład przeskok z wersji 5.7.2 do 5.7.4 bez zgody użytkownika – to klasyczny zapalnik do rozbicia współpracy w zespole, uszkodzenia binarnych formatów blueprintów i zamiany kompilacji pluginów C++ w prawdziwe piekło zależności (dependency hell). Jeśli choć jeden członek zespołu otworzy i zapisze jakikolwiek asset za pomocą automatycznie zaktualizowanego edytora, po cichu podbije wersję serializacji, blokując dostęp wszystkim pozostałym, dopóki cały zespół nie zostanie zmuszony do wyrównania wersji swoich silników.
Dla zespołów współpracujących w ramach jednego repozytorium, utrzymanie spójności środowisk binarnych jest już i tak chodzeniem po linie. Cichy update silnika zamienia to w wielodniową akcję ratunkową. W tym poradniku przyjrzymy się bliżej, dlaczego Epic Games Launcher wymusza te ciche aktualizacje, przeanalizujemy problemy techniczne, które z tego wynikają, oraz omówimy programowe mechanizmy obronne i rozwiązania typu source-pinning, aby Wasz zespół już nigdy nie stracił synchronizacji.
Anatomia desynchronizacji wymuszonej przez Launcher
Źródło problemu tkwi w tym, jak Epic Games Launcher zarządza zainstalowanymi silnikami. Kiedy Epic wydaje drobny patch – np. przechodząc z wersji 5.7.2 na 5.7.4 w celu poprawy stabilności – launcher traktuje go jako bezpośredni update (drop-in update), a nie osobną wersję. Domyślnie pobiera on w tle pliki binarne patcha o rozmiarze około 2,4 GB i bez wiedzy użytkownika aktualizuje katalog pod ścieżką C:\Program Files\Epic Games\UE_5.7 bez pytania o zgodę.
Dla deweloperów pracujących solo taki auto-update jest zazwyczaj niegroźny. Jednak w projekcie wieloosobowym ta cicha aktualizacja natychmiast rozbija lokalną synchronizację zespołu. Kiedy plik .uproject dewelopera zawiera:
{
"FileVersion": 3,
"EngineAssociation": "5.7",
"Category": "",
"Description": ""
}
System rozwiązuje powiązanie "EngineAssociation": "5.7" do silnika zarejestrowanego pod kluczem "5.7" w rejestrze Windows (HKEY_LOCAL_MACHINE\SOFTWARE\EpicGames\UnrealEngine\5.7) lub plikach konfiguracyjnych Linux. Ponieważ launcher po cichu zaktualizował pliki z wersji 5.7.2 do 5.7.4 pod dokładnie tym samym kluczem rejestru, kolejne dwukrotne kliknięcie na plik .uproject uruchomi wersję 5.7.4.
To natychmiast generuje niezgodności plików binarnych. Wszelkie customowe moduły C++ lub wtyczki firm trzecich (third-party plugins) prekompilowane w katalogu Binaries/ projektu pod wersję 5.7.2 nie załadują się, zgłaszając dobrze znane ostrzeżenie: Plugin 'MyPlugin' failed to load because module 'MyModule' does not appear to be compatible with the current engine version (5.7.4). Deweloper jest zmuszony lokalnie zrebuildować swoje pluginy. Jeśli jednak zatwierdzi (commit) te skompilowane pliki binarne do repozytorium, popsuje edytor każdemu innemu członkowi zespołu, który nadal pracuje na wersji 5.7.2.
Techniczny koszt rozjazdów wersji patcha
Konsekwencje rozjazdu wersji (version drift) to coś więcej niż tylko lokalne błędy kompilatora – sięgają one głęboko w formaty serializacji oraz sieciowy netcode.
Dryft serializacji assetów
W Unreal Engine każdy zapisany plik .uasset lub .umap zawiera nagłówek pakietu (package header) z tablicą CustomVersion oraz głównym numerem wersji silnika. Jeśli deweloper korzystający z wersji 5.7.4 kliknie „Save All” na współdzielonym levelu lub wspólnym, bazowym blueprintcie, ten asset zostanie po cichu zaktualizowany do schematu serializacji z wersji 5.7.4.
Kiedy inny członek zespołu pracujący na wersji 5.7.2 pobierze zmiany i spróbuje otworzyć ten blueprint, edytor nie odczyta pliku z powodu nierozpoznanej wersji pakietu. Często wywołuje to poważne crashe przy serializacji lub błędy takie jak Unreal Package HasValidBlueprint Ensure Crash, gdy inni członkowie zespołu próbują załadować te zasoby na starszych wersjach silnika.
Niezgodność sieciowa i RPC
Podczas lokalnego testowania funkcji multiplayer lub wdrażania buildów stagingowych, używanie niezgodnych wersji patchy może prowadzić do uszkodzenia stanu gry lub wywołać subtelne desynchronizacje multiplayer między klientami a serwerami Dedicated Server skompilowanymi na różnych dystrybucjach binarnych. System replikacji w Unreal Engine opiera się na precyzyjnych offsetach pól (field offsets) i serializacji strukturalnej. Nawet drobny patch modyfikujący niskopoziomową strukturę C++ w kodzie źródłowym silnika może spowodować niezgodności w replikacji netcode, skutkując cichymi błędami RPC lub rozjazdami w client-side prediction.
Programowa obrona: Walidator wersji silnika przed uruchomieniem
Aby zapobiec otwieraniu projektu przez deweloperów z użyciem niewłaściwej wersji silnika, można wdrożyć bootstrapper napisany w języku Python, uruchamiany przed edytorem. Ten skrypt odczytuje plik .uproject, pobiera powiązanie silnika (engine association), tłumaczy je na lokalną ścieżkę do silnika za pomocą rejestru (Windows) lub plików konfiguracyjnych (Linux/macOS), a następnie sprawdza plik JSON znajdujący się w katalogu [EnginePath]/Engine/Build/Build.version.
Oto kompletny, gotowy produkcyjnie skrypt Python, który deweloperzy mogą zintegrować ze swoim workflow uruchamiania projektu lub wywoływać za pomocą skryptu .bat lub .sh przed uruchomieniem Unreal Editor:
# Save this as tools/validate_engine.py
import os
import json
import sys
import platform
def get_engine_path(association):
if not association:
return None
# If the association is an absolute path (source builds)
if os.path.exists(association):
return association
if platform.system() == "Windows":
try:
import winreg
# Query custom source builds registered by GUID
key_path = r"Software\Epic Games\Unreal Engine\Builds"
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) as key:
val, _ = winreg.QueryValueEx(key, association)
return val
except (FileNotFoundError, ImportError):
pass
try:
# Query standard Launcher installations
key_path = r"SOFTWARE\EpicGames\UnrealEngine\{}".format(association)
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
val, _ = winreg.QueryValueEx(key, "InstalledDirectory")
return val
except (FileNotFoundError, ImportError):
pass
elif platform.system() == "Linux":
config_path = os.path.expanduser("~/.config/Epic/UnrealEngine/Install.ini")
if os.path.exists(config_path):
with open(config_path, "r") as f:
for line in f:
if line.startswith(f"{association}="):
return line.split("=")[1].strip()
return None
def check_engine_version(engine_path, expected_patch):
version_file = os.path.join(engine_path, "Engine", "Build", "Build.version")
if not os.path.exists(version_file):
print(f"[ERROR] Engine build version file not found at {version_file}")
return False
with open(version_file, "r") as f:
data = json.load(f)
actual_version = f"{data.get('MajorVersion')}.{data.get('MinorVersion')}.{data.get('PatchVersion')}"
print(f"[INFO] Local engine version detected: {actual_version}")
if actual_version != expected_patch:
print(f"[CRITICAL] Engine Version Mismatch!")
print(f"Expected: {expected_patch}")
print(f"Actual: {actual_version}")
return False
return True
def main():
uproject_file = "MyProject.uproject"
expected_version = "5.7.2" # The team's pinned version
if not os.path.exists(uproject_file):
print(f"[ERROR] Could not find uproject file: {uproject_file}")
sys.exit(1)
with open(uproject_file, "r") as f:
project_data = json.load(f)
association = project_data.get("EngineAssociation")
print(f"[INFO] Project Engine Association: {association}")
engine_path = get_engine_path(association)
if not engine_path:
print(f"[ERROR] Could not resolve engine path for association: {association}")
sys.exit(1)
print(f"[INFO] Engine path resolved to: {engine_path}")
if not check_engine_version(engine_path, expected_version):
print("\n" + "="*80)
print("LAUNCH BLOCKED: Your local Unreal Engine has silently auto-updated!")
print("Please rollback your local engine or rebuild from the team source branch.")
print("="*80 + "\n")
sys.exit(1)
print("[SUCCESS] Engine versions match. Proceeding to launch editor...")
if __name__ == "__main__":
main()
Umieszczając tę weryfikację w swoim pipeline CI (continuous integration) lub wykorzystując ją jako git pre-commit hook, możesz zablokować deweloperom możliwość pushowania niezgodnych assetów binarnych lub plików C++ zbudowanych z niezatwierdzonym patchem silnika.
Rebuildowanie ze źródeł: Jedyna niezawodna strategia blokowania wersji silnika (Engine Pinning)
Chociaż skrypty sprawdzające wersję zapobiegają przypadkowemu uruchomieniu edytora, Epic Games Launcher nie oferuje żadnego prostego mechanizmu przywrócenia (rollback) starszej wersji patcha po nadpisaniu instalacji. Jeśli launcher dewelopera zaktualizuje się automatycznie do wersji 5.7.4, jego jedynym rozwiązaniem opartym na launcherze jest całkowite odinstalowanie silnika i nadzieja, że uda mu się zablokować aktualizacje przy świeżej instalacji.
Jedynym niezawodnym, profesjonalnym rozwiązaniem klasy enterprise jest zbudowanie silnika ze źródeł. Przejście zespołu na silnik skompilowany ze źródeł gwarantuje pełną kontrolę nad aktualizacjami i tworzy stabilny, powtarzalny development pipeline.
Krok po kroku: Proces budowania silnika ze źródeł
Aby zablokować zespół na konkretnym buildzie silnika, sklonuj repozytorium Epic i wskaż dokładny release tag wersji patcha, którą chcesz zablokować (np. 5.7.2-release):
Sklonuj kod źródłowy silnika: Zainicjuj płytki klon (shallow clone) konkretnego tagu wydania z GitHub:
git clone --depth 1 --branch 5.7.2-release https://github.com/EpicGames/UnrealEngine.git UE_5.7.2_Source cd UE_5.7.2_SourcePobierz zależności: Uruchom setup script, aby pobrać prekompilowane zależności binarne. Ten krok pobierze około 15 GB do 20 GB skompilowanych assetów, shaderów oraz zewnętrznych SDK niezbędnych do zbudowania silnika:
./Setup.batWygeneruj konfiguracje buildów: Wygeneruj pliki projektu dla wybranego środowiska IDE (Visual Studio lub Rider na Windows, Clang na Linux):
./GenerateProjectFiles.batSkompiluj edytor: Otwórz plik
UE5.slnw Visual Studio lub Rider, ustaw konfigurację na Development Editor dla swojej platformy docelowej (Win64 lub Linux) i zbuduj targetUE5. Alternatywnie, możesz skompilować go bezpośrednio z wiersza poleceń za pomocą MSBuild:MSBuild.exe UE5.sln /t:UE5 /p:Configuration="Development Editor" /p:Platform=Win64
W zależności od specyfikacji sprzętowej, kompilacja całego silnika potrwa od 30 minut na maszynie z procesorem AMD Threadripper do kilku godzin na standardowym laptopie deweloperskim. Po zakończeniu kompilacji otrzymasz w pełni niezależny, customowy build silnika, całkowicie odcięty od Epic Games Launcher.
Synchronizacja powiązań silnika (Engine Associations) w dzielonym projekcie
Gdy budujesz silnik ze źródeł, wygenerowany plik wykonywalny rejestruje się z unikalnym identyfikatorem GUID powiązania silnika (Engine Association GUID) zamiast prostego ciągu znaków wersji, takiego jak "5.7". Aby skonfigurować projekt do korzystania z tego customowego silnika ze źródeł:
- Przejdź do katalogu swojego customowego silnika ze źródeł:
Engine/Binaries/Win64/. - Uruchom
UnrealVersionSelector.exez parametrem/registerlub wykonaj UnrealVersionSelector na swoim pliku.uproject, aby połączyć go z tym customowym buildem. - Po zarejestrowaniu Twój plik
.uprojectzaktualizuje pole"EngineAssociation"do unikalnego identyfikatora GUID, na przykład:"EngineAssociation": "{E9059F23-45B0-4A00-BFDF-E8C13E784013}" - Udostępnij tę modyfikację pliku
.uprojectswojemu zespołowi. Każdy deweloper, który sklonuje projekt, musi również zbudować silnik ze źródeł z tego samego commitu w git i zarejestrować go pod dokładnie tym samym powiązaniem w rejestrze. Gwarantuje to, że pliki binarne silnika, lokalne pluginy C++ oraz docelowy kod gry są zablokowane na identycznej wersji patcha i changeliście, co całkowicie eliminuje ingerencję ze strony launchera.
Jednolite klienty i serwery: Wyzwanie z Backendem w chmurze
W przypadku gier multiplayer lokalny dryft wersji klienta to tylko połowa sukcesu. Jeśli silnik klienta u deweloperów po cichu zaktualizuje się do wersji 5.7.4, podczas gdy buildy Dedicated Server wciąż kompilowane są w kontenerze z wersją 5.7.2, fundujesz sobie poważne problemy sieciowe. Sterowniki sieciowe i systemy replikacji w Unreal Engine są niezwykle wrażliwe na niezgodności wersji patcha. Klient działający na wersji 5.7.4 łączący się z Dedicated Serverem na wersji 5.7.2 może wywołać ciche błędy serializacji RPC, packet drops lub całkowite zerwanie połączenia (session timeouts).
Utrzymywanie identycznych toolchainów silnika w zespole deweloperskim i we flocie zdalnych Dedicated Serverów to operacyjny koszmar. Budowanie customowych, skonteneryzowanych pipeline'ów buildów serwerów w celu zapewnienia, że każdy patch klienta pasuje do wdrożenia serwerowego, wymaga tygodni skomplikowanego DevOps engineeringu. Konfiguracja load balancerów, database sharding oraz zarządzanie stanem backendu w czasie rzeczywistym potrafią z łatwością pochłonąć 4 do 6 tygodni pracy nad infrastrukturą.
Właśnie w tym miejscu dedykowana platforma backendowa, taka jak horizOn, całkowicie zmienia reguły gry. Zamiast marnować czas na zarządzanie customowymi pipeline'ami backendowymi i synchronizacją wersji silnika po stronie serwera, horizOn umożliwia orkiestrację dedykowanych serwerów gry, tabel wyników (leaderboards) oraz stanów rozgrywki multiplayer w czasie rzeczywistym bezpośrednio out-of-the-box. Izoluje ona infrastrukturę serwerową od aktualizacji lokalnych buildów klienta, pozwalając zespołowi skupić się na rozwiązywaniu lokalnych problemów z wersjami, podczas gdy skalowanie backendu, Matchmaking i zarządzanie stanem Multiplayer pozostają stabilne, bezpieczne i gotowe do wdrożenia produkcyjnego.
Dzięki odseparowaniu trwałych systemów gry (takich jak ekwipunki, lobby meczowe oraz persistent player states) od wersji pliku wykonywalnego silnika, architektura Backend-as-a-Service zapobiega sytuacji, w której lokalny dryft wersji silnika między klientem a serwerem paraliżuje operacje Backendu w chmurze.
5 dobrych praktyk zapobiegających dryftowi wersji silnika w zespołach wieloosobowych
Aby zabezpieczyć zespół przed cichymi aktualizacjami silnika i desynchronizacją buildów, wdrożyj te 5 sprawdzonych w boju dobrych praktyk w swoim cyklu deweloperskim:
Wyłącz globalne auto-update'y w Epic Games Launcher: Otwórz Epic Games Launcher, kliknij ikonę swojego profilu, przejdź do Ustawień (Settings), przewiń w dół do sekcji Zarządzaj grami (Manage Games) i odznacz opcję Zezwalaj na automatyczne aktualizacje (Allow Auto-Updates). Choć jest to pierwsza linia obrony, pamiętaj, że launcher wciąż może wymusić aktualizacje przy uruchomieniu, jeśli wykryje krytyczny update – stąd też gorąco zalecamy stosowanie source-pinningu.
Przejdź na customowe buildy ze źródeł z GitHub na produkcji: W projektach produkcyjnych nie polegaj na binarnych buildach z launchera. Pobierając silnik z repozytorium GitHub firmy Epic i blokując projekt na konkretnym tagu wydania (np.
5.7.2-release), chronisz swoje środowisko deweloperskie przed aktualizacjami launchera i zapewniasz spójność kodu w czasie kompilacji na wszystkich maszynach.Wdróż skrypty walidacyjne przed uruchomieniem: Użyj opisanego wyżej skryptu walidatora w Pythonie jako git pre-commit hook lub jako część skrótu uruchomieniowego (bootstrap) na pulpicie. Zablokuje to deweloperom możliwość uruchomienia edytora lub zatwierdzania assetów, jeśli ich lokalna instalacja silnika po cichu się zaktualizowała lub odbiega od wersji patcha ustalonej dla zespołu.
Trzymaj customowe pluginy w katalogu projektu: Unikaj instalowania pluginów bezpośrednio w katalogu silnika (
Engine/Plugins/Marketplace). Zamiast tego umieszczaj je w folderzePlugins/swojego projektu. Dzięki temu podczas kompilacji projektu pluginy będą budowane pod kątem aktywnego powiązania silnika na poziomie projektu. Ewentualna niezgodność wersji wywoła natychmiastowe błędy kompilacji, zamiast uruchamiania niedopasowanych plików binarnych, które prowadzą do cichych crashy w runtime.Utrzymuj jednolite środowiska buildów CI/CD: Jeśli kompilujesz serwery dedykowane, korzystaj z kontenerów Docker lub maszyn buildowych typu self-hosted z zainstalowanymi środowiskami Unreal Engine zbudowanymi ze źródeł. Upewnij się, że Twoje buildy klienta oraz buildy Dedicated Server są kompilowane na podstawie tego samego hasha commitu źródeł silnika, aby uniknąć niezgodności replikacji sieciowej i desynchronizacji klient-serwer w środowiskach produkcyjnych (live).
Chcesz skalować swój Multiplayer Backend bez bólu głowy związanego z zarządzaniem infrastrukturą? Wypróbuj horizOn za darmo lub zapoznaj się z dokumentacją API, aby dowiedzieć się, jak bezproblemowo zintegrować usługi backendowe multiplayer w czasie rzeczywistym ze swoim projektem Unreal Engine.
Źródło: unreal engine updated itself. will this affect a diversion project?