353 lines
18 KiB
Markdown
353 lines
18 KiB
Markdown
# 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
|
||
|
||
1. Zainstaluj UE 5.7 i otwórz `ExampleProject.uproject`. Poczekaj na kompilację shaderów.
|
||
2. Zainstaluj FFmpeg z enkoderem `libx264` i dodaj do PATH, jeżeli chcesz MP4.
|
||
Bez FFmpeg można renderować PNG: `render.start(shot, mp4=False)`.
|
||
3. Zwykły Python 3.10+ jest potrzebny tylko do launchera z terminala.
|
||
4. Sprawdź `python tools/run_workshop.py --check` i 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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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:
|
||
|
||
```powershell
|
||
$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'
|
||
```
|
||
|
||
```bash
|
||
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:
|
||
|
||
```python
|
||
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](docs/animations.md): 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`.
|
||
|
||
```python
|
||
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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```python
|
||
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`:
|
||
|
||
```python
|
||
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):
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
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
|
||
|
||
```text
|
||
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`.
|