Running the Unreal side of a port
The scripts in tools/ue-export/ue/ run inside the Unreal editor (Python). Most of what goes wrong in a port goes wrong here, quietly: the run ends, a file is written, and something in it is empty or wrong. This page lists how to run them and what to check after each run. The pipeline itself is in Porting from Unreal Engine 5.
The order that works#
- Census first (a headless script, seconds): actor count per class, unique meshes against instances, triangles, materials and textures with their sizes, landscape, foliage, lights, volumes, every Blueprint instance with its variables, the level loaders. It sets the budgets and finds the surprises before any export (an NPC listed in an old inventory that is no longer in the map; 27 textures at 4096 px; 5.5 M of 6.1 M triangles in foliage).
- Spike: the stock glTF exporter (
ue/gltf.py) on the whole level, opened in the Ferret viewer as is. Record time and size. In the ports so far the stock exporter was enough for geometry, materials, lights and the scene tree; everything else is post-processing. - The rest of the level's data:
environment.py,collision.py,landscape.py,physics.py,blueprints.py/blueprint_text.py,level_script.py,materials.py(the material parameters and source textures, needed for world-aligned materials: see Making a ported level look right). - Post-processing in Node (
postprocess.mjs), then the viewer, then the parity check (Parity with the original).
A whole level took 18 minutes warm and 54 cold (the first run compiles shaders) for a castle yard, 26 minutes for a school hall. Runs over a quarter of an hour belong in the background with a log, not in a foreground shell.
Run settings that matter#
| Setting | Why |
|---|---|
Rendering on: -AllowCommandletRendering, materials compiled before the export | without it every baked texture comes out empty or grey (the first character export came out grey) |
| A short path without spaces for the project and the output | a long path with spaces crashed the editor |
| The real project opened in the installed engine version | a scratch copy without the game's C++ module and plugins does not load the actors of those classes (10 of 2837 were missing, silently) and cannot compile the Blueprints that use them (BP_Door, the game instance, the game mode) |
default_material_bake_size per tier | the baked texture size comes from it |
| The Unreal project is not changed | export scripts read; the export writes to its own folder; scratch maps are deleted after the run |
Traps found in real ports#
- Empty fields from API changes. In 5.8 per-mesh vertex counts and simple-collision counts came back empty from the census while triangles were right. Check every column of a census for zeros and nulls before trusting it.
- Instance transforms are not in the census. The exporter walks them; do not plan from the census for placement.
- Counting strings is not counting actors. A plan said four
PlayerStarts because the string occurs four times in the map file; there were two actors. - Animations export only through a playing component. An animation sequence cannot be exported as an asset;
characters.pywithFERRET_CLIPS=1puts one actor per sequence into a scratch level and exports that world. One mesh per editor run: the scratch level keeps the actors of the meshes before it, so with several meshes in one run a file carried the clips of every earlier family (4 clips expected, 116 written). - Same-named assets. Two folders had a
Sneaker_01and aTShirt_01; the exporter puts the folder name in front, and every script after it must repeat the rule. Animation Blueprints with one name in several folders (threeThirdPerson_AnimBP) overwrote each other in a dump: key dumps by the full path. - Morph targets weigh more than the mesh. A head with 104 morph targets exported at 65 MB; drop the targets a game does not drive (
optimizeCharacter,keepMorphs). - 16-bit textures come out linear. A 16-bit source texture is written to PNG with linear values, while 8-bit colour textures are sRGB. Used as a glTF base colour it is too dark and too saturated (tan walls instead of cream). Check
sharp(file).metadata().depth === 'ushort'and encode to sRGB before using it as colour. Normal and roughness maps are data and stay as they are. -gameruns the real game. A capture run in-gamemode starts the game's own logic, including its network: one run posted to the live game server for minutes. Use it for screenshots only, stop it when the shots are written, and never point it at a backend you must not touch.- Your own processes. An editor or game started for a capture does not always quit; check the process list by command line and creation time and stop the one you started, never one the owner may be using.
- A level loader is not a level. A door in the original often opens a loader level (
MAP_LOADER_...) which then opens the target with an options string (PS_MAIN); read the Blueprint for the real target and spawn name.
Check after each run#
- The log has no Python errors (the OpenXR and plugin warnings are noise).
- Counts against the census: nodes, meshes, materials, textures, lights, cameras.
- Textures: none empty, none 1x1 where a map is expected, none suspiciously grey.
- Sizes: the level file and the biggest textures; a 120 MB
level.glbis normal before post-processing. - Open the result in the viewer before writing any game code on top of it.