Python w Unrealu — warsztat na godzinę
UE 5.7, Windows / Linux. Biblioteka ue_workshop automatyzuje scenę,
Sequencer, Movie Render Queue i składanie MP4. Nie wymaga kompilacji C++ ani pip.
Plik do pokazu podstaw korzysta z własnego modelu z Blendera i obrotu kamery.
Zadanie z animacjami korzysta z dołączonych postaci Mixamo i nie wymaga Blendera.
Jeśli używasz wcześniejszej kopii projektu z C++ i edytor zgłasza
Target Upgrade Required, zamknij UE i usuń pozostałości starej kompilacji:
Binaries/, Intermediate/Build/ oraz Intermediate/TargetInfo.json.
Nowy projekt nie ma własnego targetu C++ i nie wymaga aktualizacji Target.cs.
Przygotowanie przed spotkaniem
- Zainstaluj UE 5.7 i otwórz
ExampleProject.uproject. Poczekaj na kompilację shaderów. - Zainstaluj FFmpeg z enkoderem
libx264i dodaj do PATH, jeżeli chcesz MP4. Bez FFmpeg można renderować PNG:render.start(shot, mp4=False). - Zwykły Python 3.10+ jest potrzebny tylko do launchera z terminala.
- Sprawdź
python tools/run_workshop.py --checki wykonaj ćwiczenie próbnie.
W projekcie są już włączone Python Editor Script Plugin, Editor Scripting Utilities, Sequencer Scripting, Movie Render Queue oraz Interchange. Python jest narzędziem automatyzacji edytora; nie służy tu do gameplayu.
Pierwszy skrypt w edytorze
Pliki do prowadzenia zajęć:
exercises/basics.py— prosty pokaz:character_custom.blend, podłoga, światła i kamera robiąca pełne 360° w 8 sekund. Postać pozostaje nieruchoma.exercises/animation_starter.py— punkt wyjścia dla studentów: jedna creature, dwie animacje (mutant_idle,mutant_swiping) i 15 sekund na eksperymenty. Własną scenę walki tworzą przez dodanie klipów, ruchu, drugiej postaci i zmian kamery.exercises/my_shot.py— dotychczasowy rozbudowany plik z wyborem modelu.examples/mixamo_characters.py— fighter i creature z pełnymi słownikami klipów; zachowany układ sceny i dotychczasowe klipy, gotowe do własnej sceny walki.
W nowych plikach render jest zakomentowany. Najpierw obejrzyj scenę w Sequencerze,
potem odkomentuj renderer.render(shot). Każdy plik tworzy własną nazwaną mapę.
Przed edycją zrób kopię pliku; ponowne uruchomienie przebudowuje jego scenę.
REIMPORT = False używa istniejących modeli i klipów w UE. Brakujące assety
importowane są przy pierwszym uruchomieniu. REIMPORT = True wymusza ponowny
import wszystkich wskazanych modeli i klipów, również eksport .blend z Blendera.
Po odświeżeniu ustaw ponownie False. Zmiany plików nie są wykrywane automatycznie.
Zapisz aktualną mapę. Wybierz File → Execute Python Script i wskaż
exercises/basics.py. Możesz też wpisać w konsoli UE:
py "C:/sciezka/do/projektu/exercises/basics.py"
Powstanie własna postać z Blendera, podłoga, trzy światła i ośmiosekundowy objazd kamerą. Obejrzyj go w Sequencerze. Zmień model, kolor podłogi, kamerę albo klucze i uruchom plik ponownie. Skrypt jest odczytywany ponownie z dysku.
W my_shot.py ustaw RENDER = True, aby wyrenderować film. Preset preview to 640×360,
a final to 1280×720 i więcej próbek. FPS i długość filmu pochodzą z Sequence.
Ustawienia własnego renderera są na końcu ćwiczenia.
Szybkie sprawdzenie obrazu: zamiast pełnego renderu użyj
renderer.render(shot, single_frame=24).
Własne ustawienia wpisujesz bezpośrednio w skrypcie uczestnika:
from ue_workshop import Renderer
renderer = Renderer(width=1920, height=1080, samples=8, warmup=32, mp4=True)
renderer.render(shot)
# Albo zamiast filmu: renderer.render(shot, single_frame=24)
width i height to piksele, samples to liczba próbek przestrzennych na klatkę,
a warmup to liczba klatek rozgrzewki. Wymiary i próbki muszą być dodatnimi
liczbami całkowitymi, rozgrzewka może wynosić zero. FPS pochodzi z sekwencji.
Można też użyć Renderer.from_preset("final", width=1920, height=1080):
argumenty nadpisują wybrane wartości presetu. Obiektu użyjesz ponownie do kolejnych
sekwencji po zakończeniu poprzedniego renderu. Ustawienia są zapisane w result.json.
Dotychczasowe render.start(...) nadal działa jako skrót do presetów.
Wyniki: renders/MyShot/<czas_i_id>/frames/, film.mp4, result.json.
Każdy render ma nowy katalog. Nieudane kodowanie zachowuje klatki.
MP4 powstaje dopiero po zakończeniu MRQ; nie zamykaj wtedy edytora.
Na czas przebudowy sceny i renderowania biblioteka zamyka kartę Sequencera.
Render startuje po powrocie ze skryptu. Sekwencję można ponownie otworzyć
z Content Browser; ponowne uruchomienie ćwiczenia też ją otwiera.
Z terminala, bez okna edytora
Drugi plik obok ćwiczenia uruchamia my_shot.py bez okna UE, czeka na zakończenie
renderu oraz kodowania MP4 i automatycznie zamyka uruchomiony proces edytora:
python exercises/my_shot_headless.py
Uruchamiaj go zwykłym Pythonem z terminala, nie przez Execute Python Script
w Unreal. Korzysta z obiektu renderer i ustawień zapisanych w my_shot.py.
Wymusza render również przy RENDER=False. Wynik trafia do tego samego
renders/MyShot/<czas_i_id>/film.mp4. Można dopisać --editor, --timeout
lub --check; ścieżka skryptu jest ustalana względem pliku launchera.
Domyślnie konsola pokazuje etapy: uruchamianie UE, przygotowanie sceny,
renderowanie, składanie MP4 i zamykanie UE, a na końcu wynik i ścieżkę do logu.
Podczas renderowania podaje liczbę zapisanych PNG; przy dłuższym oczekiwaniu
co 10 sekund potwierdza, że proces nadal działa. To nie jest szacowany procent
całego zadania — rozgrzewka, zapis klatek i kodowanie mają różne czasy wykonania.
Pełne logi trafiają do Saved/Workshop/<id>.log, także przy błędzie lub timeout.
Żeby widzieć logi UE na żywo w terminalu, dodaj:
python exercises/my_shot_headless.py --verbose
Ścieżki filmu i logu są klikalne w obsługujących to terminalach, m.in.
Windows Terminal i terminalu VS Code (zwykle Ctrl+klik). W pozostałych konsolach
oraz przy przekierowaniu wyjścia do pliku wypisywane są zwykłe ścieżki.
Można wymusić linki opcją --links always lub wyłączyć je przez --links never.
Ogólny launcher do pozostałych skryptów:
python tools/run_workshop.py --check
python tools/run_workshop.py exercises/my_shot.py --render
python tools/run_workshop.py exercises/my_shot.py --single-frame 24
Bez --render launcher tylko wykonuje skrypt i zapisuje scenę (chyba że sam skrypt
uruchamia render). Zwraca kod 0 przy powodzeniu i 1 przy błędzie. Domyślny limit
to 900 sekund; można zmienić --timeout. Nie używaj -nullrhi do renderowania.
Tryb bez okna nadal wymaga GPU, sterowników i na Linuksie działającego Vulkan.
Obsługa Linuksa jest przygotowana w kodzie; trzeba sprawdzić ją na docelowej maszynie.
Ścieżki programów można ustawić zmiennymi środowiskowymi:
$env:WORKSHOP_UNREAL = 'C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
$env:WORKSHOP_BLENDER = 'C:\Program Files\Blender Foundation\Blender 5.1\blender.exe'
$env:WORKSHOP_FFMPEG = 'C:\tools\ffmpeg\bin\ffmpeg.exe'
export WORKSHOP_UNREAL=/opt/UnrealEngine/Engine/Binaries/Linux/UnrealEditor
export WORKSHOP_BLENDER=/usr/bin/blender
export WORKSHOP_FFMPEG=/usr/bin/ffmpeg
python3 tools/run_workshop.py --check --blender
Ścieżki nie są zapisane na sztywno w bibliotece. Launcher ma domyślną lokalizację instalacji UE 5.7 na Windows; na Linuksie korzysta ze zmiennej lub PATH.
Wybór postaci i model pobrany z internetu
W exercises/my_shot.py wybierz MODEL:
| Wartość | Postać |
|---|---|
"primitive" |
Dotychczasowa postać z brył, bez importu. |
"fighter" |
Wojowniczka z source_assets/character/fighter. Domyślna postać. |
"creature" |
Stwór z source_assets/character/creature. |
"file" |
Własny plik wskazany w MODEL_FILE. |
Przykład własnego modelu:
MODEL = "file"
MODEL_FILE = PROJECT / "source_assets/character/my_character.fbx"
SKELETAL = True
ANIMATION_FILE = None # animacja zawarta w FBX; albo ścieżka do osobnego klipu FBX
MODEL_HEIGHT = 200
MODEL_ROTATION = (0, 0, 90)
Rozpakuj pobrany model razem z teksturami. Możesz użyć ścieżki absolutnej
albo ścieżki względem projektu. GLB z osadzonymi teksturami PNG/JPEG pozwala
przenieść podstawowe materiały PBR w jednym pliku. FBX i OBJ wymagają zachowania
plików tekstur i powiązanych FBM/MTL. Nie wczytujemy ZIP ani plików .uasset.
Import zawsze odbywa się do wydzielonego katalogu generowanych assetów.
Biblioteka łączy części modelu, ustawia jego wysokość w centymetrach i przesuwa
podstawę na Z=0. MODEL_HEIGHT=None zachowuje rozmiar źródłowy. Jeśli model leży
na boku, popraw MODEL_ROTATION (roll, pitch, yaw); obrót jest stosowany przed
dopasowaniem wysokości. Pivot animacji znajduje się na środku podstawy.
Dla pojedynczego atlasu FBX/OBJ można dołączyć mapy NazwaModelu_BC.png,
_N, _R, _M, _E (również JPG/JPEG). Biblioteka odtworzy z nich jeden
materiał dla wszystkich slotów w statycznym przepływie scene.model(...).
Bez pliku _BC pozostają materiały zaimportowane przez UE. W GLB zachowywany
jest osobny materiał dla każdego slotu.
Dla fighter i creature użyj nazwy pliku bez rozszerzenia, ze spacjami zamienionymi
na podkreślenia: np. great_sword_walk lub mutant_swiping. Dostępny jest
pełny katalog animacji: 51 klipów fighter i 12 klipów creature.
scene.skeletal_model(...) samo znajduje wszystkie pliki FBX w folderze postaci
poza modelem. Nie wymaga animations ani ręcznych aliasów. Import tworzy SkeletalMesh, szkielet,
materiały i wszystkie klipy. Sequencer odtwarza
animację kości przez 120 klatek (5 s); krótszy klip zapętla się w swojej sekcji.
Klucze location, rotation i scale mogą dodatkowo poruszać całym modelem.
Przykład dwóch postaci z różnymi klipami: examples/mixamo_characters.py.
Do pisania scen w VS Code możesz używać enumów z gotowymi długościami — bez szukania assetów ani uruchamiania komend w UE:
from ue_workshop.animations import FighterAnimations
clip = FighterAnimations.GREAT_SWORD_ATTACK
clip.seconds # czas klipu w sekundach
clip.frames(shot.fps) # liczba klatek przy FPS sekwencji
shot.animation(character, clip, start=48) # bez end: jeden przebieg
Po wpisaniu FighterAnimations. edytor podpowiada nazwy. CreatureAnimations
udostępnia klipy stwora. Dane offline, łączenie klipów i aktualizacja katalogu.
Własne modele również dostają katalog automatycznie: pierwszy import
scene.skeletal_model(path, name="Ninja") zapisze NinjaAnimations w
ue_workshop.animations. Kolejne klipy i długości są aktualizowane przy imporcie,
bez osobnego polecenia generowania. REIMPORT=False nadal wykorzystuje gotowe assety.
Gdy atak przesuwa postać, shot.animation(character, clip, start=57, keep_end_position=True) automatycznie przenosi poziomy ruch do kluczy Transform
i zachowuje końcową pozycję na kolejne klipy. Przykład slash_2.
Po śmierci shot.animation(character, CreatureAnimations.MUTANT_DYING, start=150, keep_end_pose=True) utrzymuje leżącą pozę do końca sekwencji lub następnej animacji.
character = scene.skeletal_model(
PROJECT / "source_assets/character/creature/Maw J Laygo.fbx",
name="Creature", height=200, location=(150, 0, 0),
)
shot.animation(character, "mutant_walking")
shot.animation(character, "mutant_walking", start=0, end=48, speed=0.8) odtwarza klip
w zakresie [0, 48) z prędkością 80%. Następny klip może zacząć się w klatce 48.
Nakładające się klipy tej samej postaci automatycznie tworzą płynne przejście
przez cały wspólny zakres. Helper ustawia narastanie wagi nowego klipu
i zanikanie poprzedniego; nie trzeba podawać dodatkowego parametru:
shot.animation(character, "mutant_idle", start=0, end=60)
shot.animation(character, "mutant_walking", start=48, end=120)
Przy 24 FPS przejście na klatkach 48–60 trwa 0,5 sekundy. Dodawaj klipy w kolejności
odtwarzania; naraz mogą mieszać się dwie animacje tej samej postaci.
Klipy stykające się końcami przechodzą bez blendu. Nie trzeba ponownie importować animacji.
Przy kolejnych przejściach środkowy klip zachowa zarówno wejście, jak i wyjście.
Pełne zawieranie jednego klipu w drugim oraz identyczne zakresy są odrzucane.
shot.animation(character)
wybiera jedyny klip zawarty w modelu. Każda postać zachowuje swój szkielet;
automatyczny retargeting między różnymi postaciami nie jest wykonywany.
W Mixamo pobierz FBX With Skin, aby mieć model, szkielet i animację w jednym
pliku. Kolejne klipy pobieraj dla tej samej wybranej postaci; mogą być Without Skin.
Umieść je obok pliku modelu — są wykrywane automatycznie, pod nazwami plików.
Opcjonalne animations={"mutant_idle": path1, "mutant_walking": path2} ogranicza
wybór do konkretnych klipów. Puste animations={} pomija animacje z folderu
i pozostawia tylko klipy zawarte w pliku modelu.
Jeśli samodzielnie animujesz trasę przez transform_key, wybierz In Place,
gdy jest dostępne, aby animacja kości nie dodawała własnego przesuwania.
Importy szkieletowe trafiają do Content/Workshop/Generated/Skeletal.
Przy REIMPORT=True aktualizowane są te same assety. Pivot jest na środku podstawy
pozy referencyjnej; podczas animacji obrys i pozycja stóp mogą się zmieniać.
Dla statycznego GLB, FBX, OBJ lub .blend ustaw MODEL="file", SKELETAL=False.
Dotychczasowe scene.model(...) zachowuje statyczny przepływ importu.
Wybrany model działa zarówno w edytorze, jak i przez my_shot_headless.py.
Mixamo → Blender → Unreal
Zaimportuj do Blendera FBX ze szkieletem i animacją. Zmieniaj geometrię, zachowując
Armature, wagi wierzchołków i modyfikator Armature. Siatki umieść w kolekcji
Character; muszą być przypisane do jednego Armature. Wybierz aktywną akcję
i ustaw zakres klatek sceny obejmujący animację. Zapisz .blend, a w ćwiczeniu ustaw:
MODEL = "file"
MODEL_FILE = PROJECT / "source_assets/character/my_animated_character.blend"
SKELETAL = True
ANIMATION_FILE = None
Python uruchomi Blendera w tle, wyeksportuje siatki, szkielet i aktywną animację do FBX, a następnie zaimportuje je w UE. Eksport obejmuje aktywną akcję w zakresie sceny; nie eksportuje osobnych akcji i pasków NLA jako wielu klipów. Materiały FBX przenoszą podstawowe kolory i obsługiwane tekstury. Proceduralne materiały wypal do tekstur; statyczne modele z pełniejszym PBR nadal eksportuj do GLB.
Blender potrzebuje binarnego FBX 7.1 lub nowszego. Dołączone fighter i creature są w starym FBX 6.1: UE importuje je bezpośrednio, ale Blender 5.1 ich nie otworzy. Do edycji w Blenderze pobierz współczesny wariant FBX albo wyeksportuj SkeletalMesh i animację z UE do nowego FBX.
Blender → UE → film
Własną kopię wybierzesz też bezpośrednio w my_shot.py:
MODEL = "file"
MODEL_FILE = PROJECT / "source_assets/character/character_custom.blend"
SKELETAL = False # ten przykład to statyczne siatki bez Armature
Zapisz zmiany w Blenderze i uruchom ćwiczenie z REIMPORT=True. Plik
jest eksportowany w tle i aktualizuje ten sam asset CustomCharacter w UE.
Nie trzeba ręcznie usuwać poprzedniego modelu. Eksport obejmuje kolekcję Character.
Na Windows biblioteka wykrywa też standardowe instalacje w Blender Foundation,
jeśli Blender nie jest w PATH; WORKSHOP_BLENDER pozwala wybrać inną instalację.
Przykładowy model znajduje się w source_assets/character/character.blend.
Można go też odtworzyć (generator nie nadpisuje istniejącego pliku):
blender --background --python-exit-code 1 --python tools/create_character.py
Otwórz model w Blenderze, powiększ obiekt Head, zmień kolor materiału i zapisz.
Uruchom examples/blender_character.py w UE albo:
python tools/run_workshop.py examples/blender_character.py
Skrypt uruchamia osobny proces Blendera, eksportuje kolekcję Character do GLB,
aktualizuje model i materiały w UE, buduje sekwencję i renderuje MP4.
Blender nie musi być otwarty. Eksport czyta ostatni zapis z dysku.
Umowa dla modelu: statyczne siatki w kolekcji Character, metry w Blenderze,
stopy na Z=0, przód w +X. Modyfikatory są uwzględniane, obiekty łączone do jednej
siatki z zachowaniem slotów materiałów. Origin eksportu to zero świata.
Podstawowe PBR: Principled BSDF, kolory, roughness, metallic, obsługiwane tekstury.
Tekstury PNG/JPEG są wyciągane z GLB i aktualizowane w wydzielonym katalogu
WorkshopTextures. Parametry PBR synchronizujemy jawnie, ponieważ standardowy
reimport siatki w UE zachowuje wcześniejsze materiały. Zmiany w tych importowanych
materiałach wykonuj w Blenderze; skrypt nadpisuje wspierane parametry ich źródłem.
Dowolne grafy proceduralne, włosy i shader skóry nie są przenoszone w tym przepływie.
Materiały proceduralne należy wypalić do tekstur. Modele z Armature obsługuje
scene.skeletal_model(...), opisane wyżej.
Układ i zasady
Content/Python/ue_workshop/— biblioteka, automatycznie dostępna w Pythonie UE.exercises/basics.py— pokaz podstaw na własnym modelu z Blendera.exercises/animation_starter.py— plik startowy zadania z dwiema animacjami.exercises/my_shot.py— rozbudowany wariant z wyborem postaci.examples/— dodatkowa kamera i Blender; ćwiczenie startowe jest też rozwiązaniem bazowym.tools/— launcher, eksport/generator Blendera i testy integracyjne.docs/— ściąga API i scenariusz spotkania.Content/Workshop/Generated/— mapy, sekwencje, importy i materiały generowane w UE.build/,renders/— pliki pośrednie i wyniki, pomijane przez Git.archive/legacy_python/— stare skrypty; poza ścieżką importu UE.DemoAssets/— zachowane źródłowe modele z poprzedniego projektu, opcjonalne.
Scene("MyShot") zarządza mapą /Game/Workshop/Generated/MyShot/Map.
Ponowne wykonanie usuwa w niej tylko aktorów z tagiem Workshop, a sekwencję
odbudowuje. Własną scenę nazwij inaczej, np. Scene("AniaShot").
Generowana sekwencja jest w całości zastępowana: ręczne zmiany zachowaj w jej kopii.
Mapy spoza warsztatu nie są czyszczone; przy niezapisanej obcej mapie skrypt prosi o zapis.
Nie usuwaj całego folderu Generated podczas pracy — zawiera aktualnie używane assety.
Weryfikacja
python -m unittest discover -s tests -v
python tools/run_workshop.py tools/smoke_scene.py
python tools/run_workshop.py tools/smoke_blender.py
python tools/run_workshop.py tools/smoke_renderer.py
python tools/run_workshop.py tools/smoke_skeletal.py --render
python tools/run_workshop.py tools/smoke_workshop_files.py
Pierwszy test integracyjny wykonuje ćwiczenie dwa razy, sprawdza duplikaty,
renderuje dwie klatki i koduje MP4. Drugi wymaga Blendera i sprawdza powtórny
import zmienionej geometrii oraz materiałów i render.
Opis sprawdzonego środowiska: docs/validation.md.