UE Procedural Generation
Target engine: UE 5.8. APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use".
Covers the PCG framework (plugin PCG at Engine/Plugins/PCG, enabled by default, Build.cs module PCG), runtime mesh building with UProceduralMeshComponent (plugin ProceduralMeshComponent, module ProceduralMeshComponent) and UDynamicMeshComponent + Geometry Script (modules GeometryFramework and GeometryScriptingCore), instancing with UInstancedStaticMeshComponent/UHierarchicalInstancedStaticMeshComponent, spline-driven placement, and deterministic noise/random from Core.
Context
Read .agents/ue-project-context.md if it exists (module names, conventions, enabled plugins, GAS/networking setup). Do not stop if it is missing.
Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code.
PCG setup
PCG component and runtime generation
EPCGComponentGenerationTrigger (PCGComponent.h:77): GenerateOnLoad, GenerateOnDemand, GenerateAtRuntime.
Partitioning and runtime generation:
SetIsPartitioned(bool)/IsPartitioned()back thebIsComponentPartitionedproperty; partitioned components dispatch work to local components on a grid.GenerateAtRuntimehands the component to the runtime-gen scheduler. Tune it withSchedulingPolicyClass/SchedulingPolicy(UPCGSchedulingPolicyBase) andbOverrideGenerationRadii+GenerationRadii(FPCGRuntimeGenerationRadii).- Scheduler CVars:
pcg.RuntimeGeneration.Enable,pcg.RuntimeGeneration.NumGeneratingComponents,pcg.RuntimeGeneration.GlobalRadiusMultiplier,pcg.RuntimeGeneration.EnablePooling,pcg.RuntimeGeneration.BasePoolSize,pcg.RuntimeGeneration.FramesBeforeFirstGenerate,pcg.RuntimeGeneration.EnableChangeDetection,pcg.RuntimeGeneration.EnableDebugging. UPCGSubsystem::GetSubsystemForCurrentWorld()returns the subsystem;RefreshAllComponentsFiltered(Filter, ChangeType)forces a refresh of a subset (WITH_EDITORonly,Subsystems/PCGSubsystem.h:227-230).
Hierarchical generation lives on UPCGGraph: bUseHierarchicalGeneration, HiGenGridSize (EPCGHiGenGrid::Grid4 … Grid2048, plus Unbounded), HiGenGridSizeMultiplier, bUse2DGrid.
Graph parameters are an FInstancedPropertyBag UserParameters on UPCGGraph, read and written through UPCGGraphInterface:
Point data
Point collections are UPCGBasePointData. UPCGPointArrayData is the structure-of-arrays implementation; UPCGPointData is the array-of-FPCGPoint implementation kept for compatibility. Allocate through the context so the project-configured class is used:
Never iterate GetPoints()/GetMutablePoints() in new code — that only exists on UPCGPointData and forces a conversion. Read and write through value ranges instead:
Per-point native properties (EPCGPointNativeProperties in PCGPointPropertiesTraits.h): Transform, Density, BoundsMin, BoundsMax, Color, Steepness, Seed, MetadataEntry, plus All and AllProperties. Sizing and allocation:
Other data types: UPCGSpatialData (base), UPCGSplineData, UPCGLandscapeData, UPCGVolumeData, UPCGTextureData, UPCGPrimitiveData, UPCGDynamicMeshData, and UPCGParamData for attribute sets. UPCGSpatialData::ToBasePointData(FPCGContext*, const FBox&) discretizes any spatial data into points.
Custom PCG node in C++
A node is a UPCGSettings subclass plus an IPCGElement. Settings hold data; the element is const and stateless and reads everything from FPCGContext.
Rules that fall out of the headers:
SupportsBasePointDataInputsreturningfalse(the default) makes PCG convert every input toUPCGPointDatabefore your element runs. Returntrueand use value ranges.IsCacheablemust returnfalseif the node spawns actors or components, or reads untracked data.CanExecuteOnlyOnMainThreadreturningtrueserializes the node onto the game thread; keep itfalseunless you touchUWorldor components.- Long loops belong in
FPCGAsync::AsyncProcessingRangeEx(&Context->AsyncState, NumIterations, Initialize, ProcessRange, MoveDataRange, Finished, bEnableTimeSlicing)(Helpers/PCGAsync.h), which time-slices and multithreads. - Pin labels come from
PCGPinConstants::DefaultInputLabel("In"),DefaultOutputLabel("Out"),DefaultParamsLabel("Overrides"),DefaultExecutionDependencyLabel. - Blueprint nodes derive from
UPCGBlueprintBaseElementand override theExecute(const FPCGDataCollection&, FPCGDataCollection&)BlueprintNativeEvent; seed helpers areGetSeedWithContext(GetContextHandle())andGetRandomStreamWithContext(GetContextHandle()).
See PCG node reference [blocked] for node settings classes, pin behaviour, metadata attributes and GPU nodes.
ProceduralMeshComponent
Triangle-level control at runtime. No Nanite support, no automatic LODs.
Materials come from UMeshComponent::SetMaterial(int32 ElementIndex, UMaterialInterface* Material) — one material slot per section index.
Collision:
bUseComplexAsSimpleCollision(default true) uses the rendered triangles for collision. Accurate, expensive, and cannot be simulated — set it tofalseand feedAddCollisionConvexMeshwhen the mesh must be dynamic.bUseAsyncCookingmoves physics cooking off the game thread. Collision lags a frame or more behind the visual mesh; use it for far-away streamed geometry.
UpdateMeshSection_LinearColor moves existing vertices and refreshes collision, but cannot change vertex or triangle count — call CreateMeshSection_LinearColor when topology changes. Build the arrays on a worker thread, then call the component on the game thread; see async mesh generation [blocked].
Dynamic Mesh and Geometry Script
UDynamicMeshComponent (GeometryFramework, header Components/DynamicMeshComponent.h) plus the Geometry Script libraries (GeometryScriptingCore) are the modern path: boolean operations, remeshing, normals recomputation, and baking to a UStaticMesh asset (editor only — CopyMeshToStaticMesh errors "Not currently supported at Runtime" outside WITH_EDITOR, MeshAssetFunctions.cpp:471).
A worked example is in dynamic mesh with Geometry Script [blocked].
Collision on UDynamicMeshComponent: EnableComplexAsSimpleCollision(), SetComplexAsSimpleCollisionEnabled(bool bEnabled, bool bImmediateUpdate), SetSimpleCollisionShapes(const FKAggregateGeom&, bool bUpdateCollision), bDeferCollisionUpdates + UpdateCollision(bool bOnlyIfPending). ADynamicMeshActor ships a component at the root via GetDynamicMeshComponent().
Instanced static meshes
Per-instance floats set with SetNumCustomDataFloats / SetCustomDataValue are read in materials through the PerInstanceCustomData node. Cull properties: InstanceStartCullDistance, InstanceEndCullDistance, InstanceLODDistanceScale, bUseGpuLodSelection.
Batch large populations: PreAllocateInstancesMemory first, build the whole TArray<FTransform>, then one AddInstances call. A full seeded scatter that traces onto terrain and fills per-instance custom data is in vegetation scatter [blocked].
Foliage (module Foliage): painted foliage lives on AInstancedFoliageActor backed by UFoliageInstancedStaticMeshComponent; simulation-driven placement uses UProceduralFoliageComponent with a UProceduralFoliageSpawner. PCG's Static Mesh Spawner node is usually the better fit for graph-driven scatter.
Splines
ESplinePointType::Type: Linear, Curve, Constant, CurveClamped, CurveCustomTangent. ESplineCoordinateSpace::Type: Local, World.
Pass bUpdateSpline = false while batching edits and call UpdateSpline() once — each update rebuilds the reparameterization table. Distance along the spline is arc length; the input key is not, so always space instances by distance.
Noise and deterministic random
Determinism rules:
- Derive every stream from one project seed.
FRandomStream::Initialize(int32)resets a stream;GetCurrentSeed()/GetInitialSeed()let you checkpoint one. - Inside PCG, seed per point from
ReadRanges.SeedRange[Index], or take the node seed fromFPCGContext::GetSeed(). Do not callFMath::Rand. - For networked generation, replicate the seed (GameState or spawn parameter) and use
Generate(bForce);GenerateLocalnever replicates. - Sort inputs before consuming them when order affects the result — iteration order of gathered actor data is not guaranteed stable.
Octave/fractal noise, Poisson disc sampling, marching cubes, BSP dungeons and wave function collapse are implemented in procedural mesh patterns [blocked].
Deprecated — do not use
Common Mistakes
Iterating FPCGPoint arrays in a custom element: GetPoints() only exists on UPCGPointData, so PCG silently converts every input and you pay a full copy. Override SupportsBasePointDataInputs to return true and read FConstPCGPointValueRanges.
Forgetting AllocateProperties before writing: FPCGPointValueRanges built with bAllocate = false leaves unallocated ranges empty, so indexing them fails the checkf range check (Utils/PCGValueRange.h:139). Call SetNumPoints then AllocateProperties for every property you intend to write.
GenerateLocal in multiplayer: it is not a network function, so clients never generate. Use Generate(bool bForce) (NetMulticast, Reliable) and replicate the seed.
Generating from Tick: PCG generation schedules graph tasks. Use GenerateOnDemand and call Generate only when inputs change, or GenerateAtRuntime and let the scheduler budget it.
Caching a node that spawns actors: leaving IsCacheable at true for a node that creates actors or components produces duplicated or missing artifacts on regeneration. Return false.
Expecting UpdateMeshSection_LinearColor to change topology: it only rewrites existing vertices. Adding or removing triangles requires CreateMeshSection_LinearColor.
Clockwise triangle winding: front faces are counter-clockwise, so clockwise triangles vanish under back-face culling.
Calling AddSplinePoint with bUpdateSpline = true in a loop: each call rebuilds the whole reparameterization table. Pass false and call UpdateSpline() once.
Spacing instances by spline input key: the key is not proportional to arc length. Step by distance and use GetTransformAtDistanceAlongSpline.
bMarkRenderStateDirty = true on every instance update: each call re-uploads the instance buffer. Leave it false in the loop and call MarkRenderStateDirty() once.
Expecting Nanite from UProceduralMeshComponent: it has no Nanite path. Bake to a UStaticMesh in the editor with CopyMeshToStaticMesh (editor-only) or scatter Nanite static meshes with PCG and instanced components.
Related Skills
ue-actor-component-architecture— component construction, registration, attachment and lifecycle for the components created hereue-physics-collision— collision profiles, body setup, complex vs simple collision, traces used to project scatter onto terrainue-materials-rendering— material instances, PerInstanceCustomData, Nanite and virtual texturing for generated geometryue-world-level-streaming— World Partition, data layers and HLODs that PCG partitioning and runtime generation build onue-async-threading—ParallelFor,Async, task graph and thread-safety rules for background mesh and point computationue-mass-entity— large agent populations, an alternative to instanced components for crowdsue-data-assets-tables— data assets and data tables that drive generation parameters and mesh/prop tablesue-niagara-effects— Niagara systems, user parameters, data interfaces and data channels


