Building and optimization
Export Presets, Profiler, the web target and WASM, a release checklist.
In Godot a build is an Export Preset (a per-platform config) + an Export Template (the engine binary without the editor). First-party platforms: Windows, macOS, Linux, Android, iOS, Web, visionOS (since 4.5).
Export Presets
Project → Export opens a window with the configs. Add a preset for the platform you need:
- Platform (Windows Desktop, Android, Web, …).
- Bundle Identifier / Package Name (for mobile/Mac/iOS).
- Icon and Splash.
- Permissions (Android — camera, location, etc.).
- Encryption — encrypt the pack file.
- Export Mode — All resources / Resources from group / By files.
- Debug / Release — release produces an optimized build without the debugger.
Export Templates
These are the engine binaries for all target platforms (without the editor). Download them via Editor → Manage Export Templates → Download. Once per editor version (~1 GB).
Then on Export Project Godot assembles: your resources + the template = the finished game.
Platform specifics
Windows / macOS / Linux
- Windows —
.exe. Sign it withsigntool.exe(options in the preset’s Inspector). - macOS —
.app(or.dmgwith automatic packaging). Notarization via an Apple Developer account for a silent launch. Requires a Mac for signing; cross-compilation is theoretically possible. - Linux —
.x86_64. PIE/non-PIE options for compatibility.
Android
- Android SDK + JDK — you need to install them and set the paths in Editor Settings → Export → Android.
- APK / AAB — chosen by the preset.
- 16 KB page size — supported since 4.5 (a Google Play requirement as of November 1, 2025).
- Architectures —
arm64-v8a+x86_64for emulators.
iOS
- Requires a Mac + Xcode.
- Export creates an Xcode project — you open it in Xcode, build it, and ship from there.
- Signing and provisioning profile — standard, via an Apple Developer account.
Web (HTML5)
This is one of the trickiest targets. Specifics:
- The Compatibility renderer is mandatory. Forward+/Mobile on WebGL2 are experimental.
- Threading in WASM requires Cross-Origin Isolation: server headers:
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp - As of Godot 4.2/4.3 there is a single-threaded export (without SharedArrayBuffer). It works everywhere, including on default hosting (itch.io, S3). Enabled with the “Threads” → Off checkbox.
- C# does not export to the web — an open issue, in progress.
- Bundle size: an empty project is ~25 MB (after Brotli), real games are 50–150 MB.
The output of a web export:
index.html ← the launch page
index.js ← the JS loader
index.wasm ← the engine and your code
index.pck ← the packed assets
index.icon.png
Serve it through any web server.
If you enable “PWA → Enabled” in the preset, Godot generates a Service Worker that injects the COOP/COEP headers. Then threads will work even on hosting that doesn’t support these headers (itch.io, GitHub Pages).
Profiler
Debugger → at the bottom of the editor → tabs:
- Profiler — the CPU profiler. Enable it → run the game → it collects data. Shows the time spent in each method and node.
- Visual Profiler — the GPU profiler. The time of each render pass.
- Network Profiler — for multiplayer.
- Monitors — real-time metric graphs: FPS, draw calls, objects, memory, video memory.
- Remote Scene Tree — the live node tree of the running game; you can inspect values.
Frame budget
- 60 FPS = 16.6 ms
- 30 FPS = 33.3 ms
- 120 FPS = 8.3 ms
- VR 90 FPS = 11.1 ms, with no room for drops
A breakdown in a typical 3D scene:
- 2–4 ms — rendering on the GPU.
- 2–6 ms — physics + your scripts on the CPU.
- The rest — buffer, VSync.
Main optimizations
Texture compression
In the Import dock for each texture — Compress Mode:
- VRAM Compressed — for runtime (BCn for PC, ASTC for Android/iOS).
- VRAM Uncompressed — uncompressed.
- Basis Universal — cross-platform via super-compression, transcoded to the GPU target.
- Lossless — PNG.
VRAM Compressed is mandatory for most projects; it saves video memory and draw time.
LOD — Level of Detail
Unlike Unity’s LODGroup, Godot applies mesh-level LOD automatically if a mesh contains
several detail levels (generated at import time when “Generate LODs” is enabled). MeshInstance3D
has a mesh_lod_threshold property (the px on screen at which to switch).
Occlusion Culling
Godot supports OccluderInstance3D — occluder nodes (static geometry) that tell the renderer “nothing is visible behind this wall.” Baked via Bake Occluders in the editor.
Static Lighting
Baking light with LightmapGI removes the realtime cost for static geometry. See the chapter on lighting.
Fewer draw calls — MultiMesh
For repeating objects (grass, props) use MultiMeshInstance3D — it renders N instances with a single draw call. The analog of Unity GPU Instancing.
Profiling GDScript
- Static typing gives a 28–59% speedup on hot code. Use
var x: intinstead of justvar x. - Every
$Nameaccess is aget_node, which isn’t free. Cache it in@onready. - Signals are cheaper than polling: instead of checking
if hp != last_hp:every frame, emit a signal on change.
Release checklist
- The profiler shows a stable 60 FPS on the target device.
- Memory doesn’t grow over a long gameplay session (no leaks, Resource Monitor is stable).
- Textures are compressed (VRAM Compressed), not raw PNGs.
- LOD on heavy models.
- Occlusion baked for interiors.
- Lightmaps baked for static scenes.
- Project Settings → Application: version, icon, splash configured.
- The build is tested on the target device (not just on dev).
- Print statements / debug code cleaned up or wrapped in
if OS.is_debug_build(). - The Settings menu (volume, graphics, keys) is saved via
ConfigFileoruser://. - For the web — the COOP/COEP headers or a single-threaded build are verified.
Files in user:// go to a special location (Roaming AppData / Library / .local) for save files
and user settings. Don’t write to res:// at runtime — it’s read-only after the build.
That wraps up the main section on 3D development in Godot. Next — a glossary of terms with web analogs and parallels to Unity.