| id | staticbatchingsystem |
|---|---|
| title | Static Batching System |
| sidebar_position | 11 |
The Untold Engine provides a static batching system that dramatically reduces draw calls by combining static (non-moving) geometry into optimized batches.
Choose Your Path: You can set up Static Batching via the Editor (no code required) or programmatically in Swift.
- Select an entity with a Render Component in the Scene Hierarchy
- In the Inspector, find the "Static Batching" section
- Toggle "Mark as Static" (or "Mark Children as Static" for parent entities)
- Open the Static Batching panel in the editor sidebar
- Toggle "Enable Batching" to ON
- Click "Generate Batches"
- A success message will appear
- The Active Batches count shows how many batch groups were created
- Clear Batches: Removes all generated batches
- Regenerate: Click "Generate Batches" again after marking new entities
- Moving a static entity automatically removes it from batching and regenerates batches
- Batches are grouped by material — objects with the same material are combined
- You can mark/unmark entities as static at any time, then regenerate
// Create entities
let cube1 = createEntity()
setEntityMesh(entityId: cube1, filename: "cube", withExtension: "usdz")
translateTo(entityId: cube1, position: simd_float3(0, 0, 0))
let cube2 = createEntity()
setEntityMesh(entityId: cube2, filename: "cube", withExtension: "usdz")
translateTo(entityId: cube2, position: simd_float3(2, 0, 0))
let cube3 = createEntity()
setEntityMesh(entityId: cube3, filename: "cube", withExtension: "usdz")
translateTo(entityId: cube3, position: simd_float3(4, 0, 0))
// Mark entities as static
setEntityStaticBatchComponent(entityId: cube1)
setEntityStaticBatchComponent(entityId: cube2)
setEntityStaticBatchComponent(entityId: cube3)
// Enable batching and generate batches
enableBatching(true)
generateBatches()How it works:
- Static entities are marked for batching
generateBatches()combines entities with the same material into batch groups- Rendering system uses batched draw calls instead of per-entity calls
For better performance, use async loading and enable batching in the completion handler:
let stadium = createEntity()
setEntityMeshAsync(entityId: stadium, filename: "stadium", withExtension: "usdz") { success in
if success {
print("Scene loaded successfully")
// Mark as static AFTER mesh is loaded
setEntityStaticBatchComponent(entityId: stadium)
// Enable batching system
enableBatching(true)
// Generate batches
generateBatches()
}
}Important: Always call setEntityStaticBatchComponent() after the mesh loads successfully, then enable and generate batches.
For USDZ files with multiple meshes (like a building with walls, roof, windows):
let building = createEntity()
setEntityMeshAsync(entityId: building, filename: "office_building", withExtension: "usdz") { success in
if success {
// Mark parent entity - automatically marks all children as static
setEntityStaticBatchComponent(entityId: building)
enableBatching(true)
generateBatches()
}
}How it works: setEntityStaticBatchComponent() recursively marks the parent and all children, so the entire building is batched.
Marks an entity (and all its children) as static for batching.
setEntityStaticBatchComponent(entityId: entity)Note: Entity must have a RenderComponent (i.e., mesh must be loaded).
Removes static batching from an entity (and all its children).
removeEntityStaticBatchComponent(entityId: entity)Use case: If you need to move a previously static object.
Globally enables or disables the batching system.
enableBatching(true) // Enable batching
enableBatching(false) // Disable batchingChecks if batching is currently enabled.
if isBatchingEnabled() {
print("Batching is active")
}Generates batch groups from all entities marked as static.
generateBatches()Important: Call this after marking entities as static and enabling batching.
Clears all generated batches.
clearSceneBatches()Use case: When loading a new scene or reconfiguring static geometry.
import UntoldEngine
// Create multiple static props
var props: [EntityID] = []
for i in 0..<50 {
let rock = createEntity()
setEntityName(entityId: rock, name: "Rock_\(i)")
// Load mesh
setEntityMesh(entityId: rock, filename: "rock", withExtension: "usdz")
// Position randomly
let x = Float.random(in: -20...20)
let z = Float.random(in: -20...20)
translateTo(entityId: rock, position: simd_float3(x, 0, z))
// Mark as static
setEntityStaticBatchComponent(entityId: rock)
props.append(rock)
}
// Enable and generate batches
enableBatching(true)
generateBatches()
print("Batched \(props.count) rocks")// Load scene from file
if let sceneData = loadGameScene(from: sceneURL) {
deserializeScene(sceneData: sceneData)
// Scene automatically restores StaticBatchComponent for marked entities
// Enable batching and generate
enableBatching(true)
generateBatches()
print("Scene loaded with batching enabled")
}// Static environment
let ground = createEntity()
setEntityMesh(entityId: ground, filename: "ground_plane", withExtension: "usdz")
setEntityStaticBatchComponent(entityId: ground)
let walls = createEntity()
setEntityMesh(entityId: walls, filename: "walls", withExtension: "usdz")
setEntityStaticBatchComponent(entityId: walls)
// Dynamic objects (NOT marked as static)
let player = createEntity()
setEntityMesh(entityId: player, filename: "character", withExtension: "usdz")
// Do NOT call setEntityStaticBatchComponent for moving objects
let enemy = createEntity()
setEntityMesh(entityId: enemy, filename: "enemy", withExtension: "usdz")
// Enemies move, so no static batching
// Enable batching (only affects static entities)
enableBatching(true)
generateBatches()let cityBlock = createEntity()
setEntityMeshAsync(entityId: cityBlock, filename: "city_block", withExtension: "usdz") { success in
if success {
print("City block loaded with all buildings")
// Mark entire hierarchy as static
setEntityStaticBatchComponent(entityId: cityBlock)
// Enable batching system
enableBatching(true)
// Generate batches
generateBatches()
print("Static batching enabled - draw calls optimized")
} else {
print("Failed to load city block")
}
}✅ Good candidates:
- Environment geometry (walls, floors, ceilings)
- Props that never move (rocks, trees, furniture)
- Buildings and structures
- Terrain meshes
- Static decorations
❌ Bad candidates:
- Characters and NPCs
- Vehicles
- Projectiles
- Animated objects
- UI elements
For entities to batch together, they must have:
- ✅ Same material (textures, colors)
- ✅
StaticBatchComponentmarked - ✅ Valid
RenderComponent(mesh loaded)
Entities with different materials will be in separate batch groups.
-
Mark entities AFTER mesh loading:
setEntityMeshAsync(...) { success in setEntityStaticBatchComponent(entityId: entity) // ✅ Correct timing }
-
Enable batching once per scene:
// Game initialization or scene load enableBatching(true) generateBatches()
-
Group entities by material:
- Entities with the same material batch better
- Reduce material variations for better batching
-
Regenerate batches when needed:
// When adding/removing static entities clearSceneBatches() generateBatches()
- No dynamic batching: Only works for static geometry
- Transform baked: Entity positions are baked into batch geometry
- Material grouping: Different materials create separate batches
- No skeletal meshes: Animated/skinned meshes cannot be batched