
ue-procedural-generation
Use when generating world content or geometry procedurally in Unreal Engine: PCG graphs, custom PCG nodes in C++, runtime mesh building, instancing and seeded noise. Also use when the user mentions 'PCG', 'UPCGComponent', 'UPCGSettings', 'IPCGElement', 'UPCGBasePointData', 'surface sampler', 'static mesh spawner', 'runtime generation', 'CreateMeshSection', 'UProceduralMeshComponent', 'UDynamicMeshComponent', 'Geometry Script', 'AddInstance', 'HISM', 'spline mesh', 'PerlinNoise', 'FRandomStream', 'scatter foliage' or 'marching cubes'. For collision on generated geometry, see ue-physics-collision; for instance materials, see ue-materials-rendering; for background work, see ue-async-threading.
- Installs
- 1.2K
- Last 24h
- +14
- Trending rank
- #7287
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/quodsoler/unreal-engine-skills --skill ue-procedural-generation- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads ue-procedural-generation automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
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.
| Request is about… | Go to |
|---|---|
| Enabling PCG, module/plugin wiring | PCG setup |
| Driving generation from an actor, runtime generation, partitioning | PCG component and runtime generation |
| Reading/writing points inside a graph | Point data |
| Writing a new PCG node in C++ | Custom PCG node in C++ |
| Which node does X, pin labels, node settings fields | PCG node reference |
| Building triangles at runtime | ProceduralMeshComponent |
| Boolean ops, primitives, baking to a static mesh | Dynamic Mesh and Geometry Script |
| Thousands of repeated meshes | Instanced static meshes |
| Roads, rivers, fences, cables | Splines |
| Heightfields, scatter, reproducible results | Noise and deterministic random |
| Marching cubes, BSP dungeons, WFC, Poisson disc | Procedural mesh patterns |
PCG setup
// MyGame.Build.cs
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "PCG" });
{ "Name": "PCG", "Enabled": true }
| Class | Header | Role |
|---|---|---|
UPCGComponent | PCGComponent.h | Actor component that owns a graph and drives generation |
UPCGGraph | PCGGraph.h | Graph asset: nodes, edges, UserParameters |
UPCGGraphInstance | PCGGraph.h | Graph instance with per-instance parameter overrides |
UPCGGraphInterface | PCGGraph.h | Common base of graph and graph instance |
UPCGSettings | PCGSettings.h | Node settings base class |
IPCGElement | PCGElement.h | The executable half of a node |
FPCGContext | PCGContext.h | Per-execution state: InputData, OutputData, Node, ExecutionSource |
UPCGBasePointData | Data/PCGBasePointData.h | Abstract point collection |
UPCGPointArrayData | Data/PCGPointArrayData.h | Structure-of-arrays point data |
UPCGSubsystem | Subsystems/PCGSubsystem.h | Scheduling, partitioning, runtime generation |
APCGVolume | PCGVolume.h | Volume actor carrying a UPCGComponent |
PCG component and runtime generation
#include "PCGComponent.h"
#include "PCGGraph.h"
void AMyGenerator::StartGeneration(UPCGGraphInterface* Graph, int32 InSeed)
{
UPCGComponent* PCG = FindComponentByClass<UPCGComponent>();
if (!PCG)
{
return;
}
PCG->Seed = InSeed;
PCG->bActivated = true;
PCG->SetGraph(Graph); // NetMulticast, Reliable
PCG->Generate(/*bForce=*/true); // NetMulticast, Reliable
}
| Call | Replication | Use for |
|---|---|---|
Generate(bool bForce) / Cleanup(bool bRemoveComponents) | NetMulticast, Reliable | Server-driven generation that must appear on clients |
GenerateLocal(bool bForce) / CleanupLocal(bool bRemoveComponents) | none | Client-side or single-player generation |
NotifyPropertiesChangedFromBlueprint() | none | Mark dirty and conditionally regenerate after editing exposed properties |
CancelGeneration() | none | Abort an in-flight generation |
GetGeneratedGraphOutput() | none | Read back the FPCGDataCollection the graph produced |
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:
TValueOrError<double, EPropertyBagResult> Result = Graph->GetGraphParameter<double>(TEXT("SpawnRadius"));
if (Result.HasValue())
{
const double Radius = Result.GetValue();
Graph->SetGraphParameter<double>(TEXT("SpawnRadius"), Radius * 2.0);
}
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:
UPCGBasePointData* Points = FPCGContext::NewPointData_AnyThread(Context);
Never iterate GetPoints()/GetMutablePoints() in new code — that only exists on UPCGPointData and forces a conversion. Read and write through value ranges instead:
#include "Data/PCGBasePointData.h"
const FConstPCGPointValueRanges ReadRanges(InputPoints);
FPCGPointValueRanges WriteRanges(OutputPoints, /*bAllocate=*/false);
WriteRanges.TransformRange[Index] = ReadRanges.TransformRange[Index];
WriteRanges.DensityRange[Index] = ReadRanges.DensityRange[Index];
Per-point native properties (EPCGPointNativeProperties in PCGPointPropertiesTraits.h): Transform, Density, BoundsMin, BoundsMax, Color, Steepness, Seed, MetadataEntry, plus All and AllProperties. Sizing and allocation:
Output->SetNumPoints(Input->GetNumPoints(), /*bInitializeValues=*/false);
Output->AllocateProperties(Input->GetAllocatedProperties() | EPCGPointNativeProperties::Density);
Output->CopyUnallocatedPropertiesFrom(Input);
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.
// MyPCGJitter.h
#pragma once
#include "PCGElement.h"
#include "PCGSettings.h"
#include "MyPCGJitter.generated.h"
UCLASS(BlueprintType, ClassGroup = (Procedural))
class MYGAME_API UMyPCGJitterSettings : public UPCGSettings
{
GENERATED_BODY()
public:
#if WITH_EDITOR
virtual FName GetDefaultNodeName() const override { return FName(TEXT("MyJitter")); }
virtual FText GetDefaultNodeTitle() const override { return NSLOCTEXT("MyPCGJitter", "NodeTitle", "My Jitter"); }
virtual EPCGSettingsType GetType() const override { return EPCGSettingsType::PointOps; }
#endif
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Settings, meta = (PCG_Overridable))
double JitterRadius = 100.0;
protected:
virtual TArray<FPCGPinProperties> InputPinProperties() const override { return Super::DefaultPointInputPinProperties(); }
virtual TArray<FPCGPinProperties> OutputPinProperties() const override { return Super::DefaultPointOutputPinProperties(); }
virtual FPCGElementPtr CreateElement() const override;
};
class FMyPCGJitterElement : public IPCGElement
{
protected:
virtual bool ExecuteInternal(FPCGContext* Context) const override;
virtual bool IsCacheable(const UPCGSettings* InSettings) const override { return true; }
virtual bool CanExecuteOnlyOnMainThread(FPCGContext* Context) const override { return false; }
virtual bool SupportsBasePointDataInputs(FPCGContext* InContext) const override { return true; }
virtual EPCGElementExecutionLoopMode ExecutionLoopMode(const UPCGSettings* Settings) const override { return EPCGElementExecutionLoopMode::SinglePrimaryPin; }
};
// MyPCGJitter.cpp
#include "MyPCGJitter.h"
#include "PCGContext.h"
#include "Data/PCGBasePointData.h"
#include "Data/PCGSpatialData.h"
#include "Math/RandomStream.h"
FPCGElementPtr UMyPCGJitterSettings::CreateElement() const
{
return MakeShared<FMyPCGJitterElement>();
}
bool FMyPCGJitterElement::ExecuteInternal(FPCGContext* Context) const
{
const UMyPCGJitterSettings* Settings = Context->GetInputSettings<UMyPCGJitterSettings>();
check(Settings);
const double JitterRadius = Settings->JitterRadius;
const TArray<FPCGTaggedData> Inputs = Context->InputData.GetInputsByPin(PCGPinConstants::DefaultInputLabel);
TArray<FPCGTaggedData>& Outputs = Context->OutputData.TaggedData;
for (const FPCGTaggedData& Input : Inputs)
{
const UPCGSpatialData* SpatialData = Cast<UPCGSpatialData>(Input.Data);
if (!SpatialData)
{
continue;
}
const UPCGBasePointData* InputPoints = SpatialData->ToBasePointData(Context);
if (!InputPoints)
{
continue;
}
UPCGBasePointData* OutputPoints = FPCGContext::NewPointData_AnyThread(Context);
OutputPoints->InitializeFromDataWithParams(FPCGInitializeFromDataParams(InputPoints));
OutputPoints->SetNumPoints(InputPoints->GetNumPoints(), /*bInitializeValues=*/false);
OutputPoints->AllocateProperties(InputPoints->GetAllocatedProperties() | EPCGPointNativeProperties::Transform);
OutputPoints->CopyUnallocatedPropertiesFrom(InputPoints);
const FConstPCGPointValueRanges ReadRanges(InputPoints);
FPCGPointValueRanges WriteRanges(OutputPoints, /*bAllocate=*/false);
for (int32 Index = 0; Index < InputPoints->GetNumPoints(); ++Index)
{
WriteRanges.SetFromValueRanges(Index, ReadRanges, Index);
const FRandomStream Stream(ReadRanges.SeedRange[Index]);
FTransform Jittered = ReadRanges.TransformRange[Index];
Jittered.AddToTranslation(Stream.VRand() * (Stream.FRand() * JitterRadius));
WriteRanges.TransformRange[Index] = Jittered;
}
FPCGTaggedData& Output = Outputs.Add_GetRef(Input);
Output.Data = OutputPoints;
}
return true;
}
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 for node settings classes, pin behaviour, metadata attributes and GPU nodes.
ProceduralMeshComponent
Triangle-level control at runtime. No Nanite support, no automatic LODs.
PublicDependencyModuleNames.Add("ProceduralMeshComponent");
// Full form (ProceduralMeshComponent.h:190) also takes UV1, UV2 and UV3 between UV0 and VertexColors
// UV0-only convenience overload (ProceduralMeshComponent.h:193)
void CreateMeshSection_LinearColor(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles,
const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FLinearColor>& VertexColors,
const TArray<FProcMeshTangent>& Tangents, bool bCreateCollision, bool bSRGBConversion = false);
void UpdateMeshSection_LinearColor(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals,
const TArray<FVector2D>& UV0, const TArray<FLinearColor>& VertexColors,
const TArray<FProcMeshTangent>& Tangents, bool bSRGBConversion = true); // true here, false on Create (ProceduralMeshComponent.h:230)
void ClearMeshSection(int32 SectionIndex);
void ClearAllMeshSections();
void SetMeshSectionVisible(int32 SectionIndex, bool bNewVisibility);
int32 GetNumSections() const;
void AddCollisionConvexMesh(TArray<FVector> ConvexVerts);
void ClearCollisionConvexMeshes();
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.
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).
PublicDependencyModuleNames.AddRange(new string[] { "GeometryFramework", "GeometryScriptingCore" });
| Library | Representative functions |
|---|---|
UGeometryScriptLibrary_MeshPrimitiveFunctions | AppendBox, AppendSphereLatLong, AppendSphereBox, AppendCapsule, AppendBoxWithCollision |
UGeometryScriptLibrary_MeshBooleanFunctions | ApplyMeshBoolean(TargetMesh, TargetTransform, ToolMesh, ToolTransform, Operation, Options, Debug) |
UGeometryScriptLibrary_MeshDeformFunctions | ApplyPerlinNoiseToMesh2(TargetMesh, Selection, Options, Debug) |
UGeometryScriptLibrary_MeshNormalsFunctions | RecomputeNormals(TargetMesh, CalculateOptions, bDeferChangeNotifications, Debug), SetPerFaceNormals |
UGeometryScriptLibrary_StaticMeshFunctions | CopyMeshToStaticMesh, CopyMeshFromStaticMeshV2 |
A worked example is in dynamic mesh with Geometry Script.
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
UInstancedStaticMeshComponent | UHierarchicalInstancedStaticMeshComponent | |
|---|---|---|
| Header | Components/InstancedStaticMeshComponent.h | Components/HierarchicalInstancedStaticMeshComponent.h |
| Best for | Small, frequently mutated sets | Large, mostly static sets |
| Culling | Start/end cull distance | Hierarchical tree plus cull distance |
| Removal | Cheap | Triggers a tree rebuild (bAutoRebuildTreeOnInstanceChanges, BuildTreeIfOutdated) |
virtual int32 AddInstance(const FTransform& InstanceTransform, bool bWorldSpace = false);
virtual TArray<int32> AddInstances(const TArray<FTransform>& InstanceTransforms, bool bShouldReturnIndices,
bool bWorldSpace = false, bool bUpdateNavigation = true);
virtual bool UpdateInstanceTransform(int32 InstanceIndex, const FTransform& NewInstanceTransform,
bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
virtual bool BatchUpdateInstancesTransforms(int32 StartInstanceIndex, const TArray<FTransform>& NewInstancesTransforms,
bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
bool GetInstanceTransform(int32 InstanceIndex, FTransform& OutInstanceTransform, bool bWorldSpace = false) const;
virtual bool RemoveInstance(int32 InstanceIndex);
virtual bool RemoveInstances(const TArray<int32>& InstancesToRemove);
virtual void PreAllocateInstancesMemory(int32 AddedInstanceCount);
int32 GetNumInstances() const;
virtual void SetNumCustomDataFloats(int32 InNumCustomDataFloats);
virtual bool SetCustomDataValue(int32 InstanceIndex, int32 CustomDataIndex, float CustomDataValue,
bool bMarkRenderStateDirty = false);
void SetCullDistances(int32 StartCullDistance, int32 EndCullDistance);
const TArray<FBodyInstance*>& GetInstanceBodies() const;
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.
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
// USplineComponent — Components/SplineComponent.h
void AddSplinePoint(const FVector& Position, ESplineCoordinateSpace::Type CoordinateSpace, bool bUpdateSpline = true);
void SetSplinePoints(const TArray<FVector>& Points, ESplineCoordinateSpace::Type CoordinateSpace, bool bUpdateSpline = true);
void SetSplinePointType(int32 PointIndex, ESplinePointType::Type Type, bool bUpdateSpline = true);
void SetTangentsAtSplinePoint(int32 PointIndex, const FVector& InArriveTangent, const FVector& InLeaveTangent,
ESplineCoordinateSpace::Type CoordinateSpace, bool bUpdateSpline = true);
void SetClosedLoop(bool bInClosedLoop, bool bUpdateSpline = true);
virtual void UpdateSpline();
float GetSplineLength() const;
FVector GetLocationAtDistanceAlongSpline(float Distance, ESplineCoordinateSpace::Type CoordinateSpace) const;
FTransform GetTransformAtDistanceAlongSpline(float Distance, ESplineCoordinateSpace::Type CoordinateSpace,
bool bUseScale = false) const;
void GetLocationAndTangentAtSplinePoint(int32 PointIndex, FVector& Location, FVector& Tangent,
ESplineCoordinateSpace::Type CoordinateSpace) const;
float FindInputKeyClosestToWorldLocation(const FVector& WorldLocation) const;
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.
// USplineMeshComponent — one deformed mesh per spline segment
void AMyRoadActor::BuildSegment(USplineComponent* Spline, UStaticMesh* RoadMesh, int32 SegmentIndex)
{
USplineMeshComponent* SegmentMesh = NewObject<USplineMeshComponent>(this);
SegmentMesh->SetMobility(EComponentMobility::Movable);
SegmentMesh->SetupAttachment(Spline);
SegmentMesh->SetStaticMesh(RoadMesh);
SegmentMesh->SetForwardAxis(ESplineMeshAxis::X, /*bUpdateMesh=*/false);
SegmentMesh->RegisterComponent();
FVector StartPos, StartTangent, EndPos, EndTangent;
Spline->GetLocationAndTangentAtSplinePoint(SegmentIndex, StartPos, StartTangent, ESplineCoordinateSpace::Local);
Spline->GetLocationAndTangentAtSplinePoint(SegmentIndex + 1, EndPos, EndTangent, ESplineCoordinateSpace::Local);
SegmentMesh->SetStartAndEnd(StartPos, StartTangent, EndPos, EndTangent, /*bUpdateMesh=*/true);
}
Noise and deterministic random
#include "Math/RandomStream.h"
#include "Math/UnrealMathUtility.h"
// All three return a continuous value in [-1, 1]
float SampleNoise(const FVector& Position, float Frequency)
{
const float N1 = FMath::PerlinNoise1D(Position.X * Frequency);
const float N2 = FMath::PerlinNoise2D(FVector2D(Position.X, Position.Y) * Frequency);
const float N3 = FMath::PerlinNoise3D(Position * Frequency);
return (N1 + N2 + N3) / 3.f;
}
FTransform MakeSeededTransform(int32 Seed, const FVector& Origin)
{
FRandomStream Stream(Seed);
const float Unit = Stream.GetFraction(); // [0, 1), same as FRand()
const double Offset = Stream.FRandRange(-50.0, 50.0);
const int32 Variant = Stream.RandRange(0, 3); // inclusive on both ends
const FVector Direction = Stream.VRand(); // uniform unit vector
return FTransform(FRotator(0.0, Unit * 360.0, 0.0),
Origin + Direction * Offset,
FVector(1.0 + Variant * 0.1));
}
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.
Deprecated — do not use
| Do not emit | Use in 5.8 | Source |
|---|---|---|
FSimplePCGElement | IPCGElement | UE_DEPRECATED(5.4) in PCGElement.h:271 |
IPCGElement::Initialize(const FPCGDataCollection&, TWeakObjectPtr<UPCGComponent>, const UPCGNode*) | Initialize(const FPCGInitializeElementParams&) | UE_DEPRECATED(5.6) in PCGElement.h:147 |
FPCGContext::SourceComponent | FPCGContext::ExecutionSource | UE_DEPRECATED(5.6) in PCGContext.h:122 |
FPCGContext::GetComponentName() | GetExecutionSourceName() | UE_DEPRECATED(5.6) in PCGContext.h:207 |
FPCGContext::Stack | GetStack() | UE_DEPRECATED(5.6) in PCGContext.h:147 |
UPCGSpatialData::IntersectWith/ProjectOn/UnionWith/Subtract/CopyInternal without a context | overloads taking FPCGContext* | UE_DEPRECATED(5.5) in Data/PCGSpatialData.h:208-280 |
ToPointData() (no context) | ToBasePointData(FPCGContext*) / ToBasePointDataWithContext | DeprecatedFunction in Data/PCGSpatialData.h:159 |
UPCGPointData::GetOctree() / IsOctreeDirty() | GetPointOctree() / IsPointOctreeDirty() | UE_DEPRECATED(5.6) in Data/PCGPointData.h:145,147 |
UPCGComponent::CleanupLocal(bRemoveComponents, bSave) | CleanupLocal(bRemoveComponents) | UE_DEPRECATED(5.6) in PCGComponent.h:236 |
UPCGComponent::GenerateLocal(..., EPCGHiGenGrid Grid, ...) | overload taking a uint32 grid size | UE_DEPRECATED(5.8) in PCGComponent.h:872 |
UPCGGraph::HiGenExponential / GetGridExponential() | HiGenGridSizeMultiplier / GetGridSizeMultiplier() | UE_DEPRECATED(5.8) in PCGGraph.h:866,872 |
UPCGGraph::bIsEditorOnly | ShouldCook (FPerPlatformBool) | UE_DEPRECATED(5.8) in PCGGraph.h:868 |
UPCGSettings::BP_GetTypeUnionOfIncidentEdges | GetTypeUnionIDOfIncidentEdges | UE_DEPRECATED(5.7) in PCGSettings.h:520 |
UInstancedStaticMeshComponent::InstanceBodies | GetInstanceBodies() | UE_DEPRECATED(5.8) in Components/InstancedStaticMeshComponent.h:524 |
UInstancedStaticMeshComponent::InitInstanceBody | InstancePhysicsBodies | UE_DEPRECATED(5.8) in Components/InstancedStaticMeshComponent.h:771 |
ApplyPerlinNoiseToMesh | ApplyPerlinNoiseToMesh2 | UE_DEPRECATED(5.7) in GeometryScript/MeshDeformFunctions.h:401 |
CopyMeshToStaticMesh without bUseSectionMaterials | overload taking bUseSectionMaterials | UE_DEPRECATED(5.5) in GeometryScript/MeshAssetFunctions.h:311 |
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
FAQ
What does the ue-procedural-generation skill do?
Use when generating world content or geometry procedurally in Unreal Engine: PCG graphs, custom PCG nodes in C++, runtime mesh building, instancing and seeded noise. Also use when the user mentions 'PCG', 'UPCGComponent', 'UPCGSettings', 'IPCGElement', 'UPCGBasePointData', 'surface sampler', 'static mesh spawner', 'runtime generation', 'CreateMeshSection', 'UProceduralMeshComponent', 'UDynamicMeshComponent', 'Geometry Script', 'AddInstance', 'HISM', 'spline mesh', 'PerlinNoise', 'FRandomStream', 'scatter foliage' or 'marching cubes'. For collision on generated geometry, see ue-physics-collision; for instance materials, see ue-materials-rendering; for background work, see ue-async-threading.
How do I install ue-procedural-generation?
Run npx skills add https://github.com/quodsoler/unreal-engine-skills --skill ue-procedural-generation in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/ue-procedural-generation/ (personal) or .claude/skills/ue-procedural-generation/ (project).
Which AI agents can use ue-procedural-generation?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is ue-procedural-generation safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains ue-procedural-generation?
It is published by quodsoler in quodsoler/unreal-engine-skills. It has 1,170 recorded installs.
More from quodsoler/unreal-engine-skills
ue-cpp-foundations
quodsoler/unreal-engine-skills
Use when writing Unreal Engine C++ that touches the reflection system or core types: UCLASS, UPROPERTY, UFUNCTION, USTRUCT, UENUM, GENERATEDBODY, TObjectPtr, TWeakObjectPtr, TSharedPtr, FGCObject, TArray, TMap, TSet, TOptional, TVariant, DECLAREDYNAMICMULTICASTDELEGATE, AddDynamic, FName, FString, FText, UGameInstanceSubsystem, UWorldSubsystem, GetDefault, TNotNull. Also use when the user mentions 'UE C++', 'garbage collection', 'CDO', 'smart pointers' or 'specifiers'. For Actor and component lifecycle, see ue-actor-component-architecture; for Build.cs and modules, see ue-module-build-system; for Blueprint-facing functions and latent actions, see ue-blueprint-cpp-interop.
ue-ui-umg-slate
quodsoler/unreal-engine-skills
Use when writing or fixing Unreal Engine UI in C++ — UMG user widgets, Slate widgets, Common UI screens, or MVVM view models. Also use when the user mentions 'UUserWidget', 'NativeConstruct', 'BindWidget', 'CreateWidget', 'AddToViewport', 'ESlateVisibility', 'OnClicked', 'UListView', 'widget animation', 'SNew', 'SLATEBEGINARGS', 'SCompoundWidget', 'UCommonActivatableWidget', 'activatable widget stack', 'UCommonButtonBase', 'gamepad focus', 'FUIInputConfig', 'UMVVMViewModelBase', 'FieldNotify', or 'WidgetComponent'. For input modes and Enhanced Input, see ue-input-system; for Slate in editor tooling, see ue-editor-tools.
ue-editor-tools
quodsoler/unreal-engine-skills
Use when extending the Unreal Editor from C++ with detail customizations, editor utility widgets, menus, editor modes, asset definitions or editor subsystems. Also use when the user mentions 'IDetailCustomization', 'IPropertyTypeCustomization', 'CustomizeDetails', 'RegisterCustomClassLayout', 'IDetailLayoutBuilder', 'AddCustomRow', 'IPropertyHandle', 'UToolMenus', 'ExtendMenu', 'FToolMenuEntry', 'UEditorUtilityWidget', 'Blutility', 'UAssetActionUtility', 'UEditorSubsystem', 'UEdMode', 'UAssetDefinition', 'UFactory', 'FScopedTransaction', or 'editor module'. For Slate fundamentals, see ue-ui-umg-slate; for Build.cs wiring, see ue-module-build-system.
ue-project-context
quodsoler/unreal-engine-skills
Use when creating, refreshing or repairing .agents/ue-project-context.md, the project document every other UE skill reads first. Also use when the user says 'project context', 'set up context', 'UE context', 'scan my project', 'onboard the agent to my project', 'update project context', 'configure project', 'what engine version is this project on', 'which plugins do we have enabled', or complains that UE advice keeps coming out generic. For Build.cs and Target.cs mechanics, see ue-module-build-system; for naming and API-macro conventions, see ue-cpp-foundations; for GAS specifics, see ue-gameplay-abilities.
ue-testing-debugging
quodsoler/unreal-engine-skills
Use when writing or running Unreal Engine tests, adding logging, or profiling a build. Also use when the user mentions 'UELOG', 'log category', 'DEFINELOGCATEGORY', 'UELOGFMT', 'verbosity', 'check', 'ensure', 'ensureMsgf', 'verify', 'IMPLEMENTSIMPLEAUTOMATIONTEST', 'automation test', 'TESTCLASS', 'CQTest', 'AFunctionalTest', 'latent command', 'Unreal Insights', 'TRACECPUPROFILEREVENTSCOPE', 'TRACEBOOKMARK', 'stat unit', 'SCOPECYCLECOUNTER', 'memreport', 'DrawDebugLine', 'UEVLOG', 'gameplay debugger', 'ShowDebug', 'console command', 'CVar', or 'why is my frame time bad'. For module and target wiring, see ue-module-build-system; for editor tooling, see ue-editor-tools.
ue-module-build-system
quodsoler/unreal-engine-skills
Use when writing or fixing Build.cs, Target.cs, .uproject or .uplugin files, creating a module or plugin, or resolving an Unreal build error. Also use when the user mentions 'unresolved external symbol', 'LNK2019', 'cannot open include file', 'C1083', 'MODULENAMEAPI', 'PublicDependencyModuleNames', 'IWYU', 'PCHUsage', 'IMPLEMENTMODULE', 'IMPLEMENTPRIMARYGAMEMODULE', 'LoadingPhase', 'circular dependency', 'Missing precompiled manifest', 'EngineAssociation', 'IncludeOrderVersion', 'Live Coding'. For UCLASS/UPROPERTY rules see ue-cpp-foundations; for editor modules see ue-editor-tools; for Game Feature plugins see ue-game-features.
Popular education & science skills
teach
mattpocock/skills
Teach the user a new skill or concept, within this workspace.
scaffold-exercises
mattpocock/skills
Create exercise directory structures with sections, problems, solutions, and explainers that pass linting. Use when user wants to scaffold exercises, create exercise stubs, or set up a new course section.
paper-context-resolver
lllllllama/rigorpilot-skills
Rigor Paper Context helper for README-first deep learning repo reproduction. Use only when the README and repository files leave a narrow reproduction-critical gap and the task is to resolve a specific paper detail such as dataset split, preprocessing, evaluation protocol, checkpoint mapping, or runtime assumption from primary paper sources while recording conflicts. Do not use for general paper summary, repo scanning, environment setup, command execution, title-only paper lookup, or replacing README guidance by default.
caveman-learn
juliusbrussee/caveman
Act on a Caveman learn report - review the ranked token sinks, apply cost-lowering fixes with per-edit consent, and report what those fixes returned. Use when asked to lower an agent's token cost, what caveman has saved, to trim a heavy CLAUDE.md, or to offload re-pasted context into cavemem.
gsap-plugins
greensock/gsap-skills
Official GSAP skill for GSAP plugins — registration, ScrollToPlugin, ScrollSmoother, Flip, Draggable, Inertia, Observer, SplitText, ScrambleText, SVG and physics plugins, CustomEase, EasePack, CustomWiggle, CustomBounce, GSDevTools. Use when the user asks about a GSAP plugin, scroll-to, flip animations, draggable, SVG drawing, or plugin registration.
teach-impeccable
pbakaus/impeccable
DEPRECATED: Use /impeccable teach instead. This command has been folded into the impeccable skill.