Loading...
Loading...
Use this skill when working with procedural generation in Unreal Engine: PCG framework, ProceduralMesh, instanced mesh, HISM, spline, runtime mesh, noise, terrain generation, or dungeon generation. See references/pcg-node-reference.md for PCG node types and references/procedural-mesh-patterns.md for mesh generation patterns. For physics on procedural geometry, see ue-physics-collision.
npx skill4agent add quodsoler/unreal-engine-skills ue-procedural-generation.agents/ue-project-context.md// Build.cs
PublicDependencyModuleNames.Add("PCG");// .uproject Plugins array
{ "Name": "PCG", "Enabled": true }| Class | Header | Purpose |
|---|---|---|
| | Actor component driving generation |
| | Asset: nodes + edges |
| | Graph instance with parameter overrides |
| | Point cloud between nodes |
| | Node settings base class |
| | Custom Blueprint node base |
PCGComponent.h// Assign graph (NetMulticast)
void SetGraph(UPCGGraphInterface* InGraph);
// Trigger generation (NetMulticast, Reliable) — use for multiplayer
void Generate(bool bForce);
// Local non-replicated generation
void GenerateLocal(bool bForce);
// Cleanup
void Cleanup(bool bRemoveComponents);
void CleanupLocal(bool bRemoveComponents);
// Notify to re-evaluate after Blueprint property change
void NotifyPropertiesChangedFromBlueprint();
// Read generated output
const FPCGDataCollection& GetGeneratedGraphOutput() const;EPCGComponentGenerationTriggerGenerateOnLoadGenerateOnDemandGenerate()GenerateAtRuntimeUPCGSubsystemPCGGraph.h// Add node by settings class
UPCGNode* AddNodeOfType(TSubclassOf<UPCGSettings> InSettingsClass, UPCGSettings*& DefaultNodeSettings);
// Connect two nodes
UPCGNode* AddEdge(UPCGNode* From, const FName& FromPinLabel, UPCGNode* To, const FName& ToPinLabel);
// Graph parameters (typed template)
template<typename T>
TValueOrError<T, EPropertyBagResult> GetGraphParameter(const FName PropertyName) const;
template<typename T>
EPropertyBagResult SetGraphParameter(const FName PropertyName, const T& Value);UPCGBlueprintBaseElementUCLASS(BlueprintType, Blueprintable)
class UMyPCGNode : public UPCGBlueprintBaseElement
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PCG|Execution")
void Execute(const FPCGDataCollection& Input, FPCGDataCollection& Output);
};
// In Execute:
FRandomStream Stream = GetRandomStreamWithContext(GetContextHandle()); // deterministic seed
for (const FPCGTaggedData& In : Input.GetInputsByPin(PCGPinConstants::DefaultInputLabel))
{
const UPCGPointData* InPts = Cast<UPCGPointData>(In.Data);
if (!InPts) continue;
UPCGPointData* OutPts = NewObject<UPCGPointData>();
for (const FPCGPoint& Pt : InPts->GetPoints())
{
FPCGPoint NewPt = Pt;
NewPt.Density = Stream.FRandRange(0.5f, 1.0f);
OutPts->GetMutablePoints().Add(NewPt);
}
Output.TaggedData.Emplace_GetRef().Data = OutPts;
}UPCGBlueprintBaseElementbIsCacheable = falsebRequiresGameThread = trueCustomInputPinsCustomOutputPinsGetRandomStreamWithContext()Seed// Set PCG seed at runtime for deterministic variation
UPCGComponent* PCG = FindComponentByClass<UPCGComponent>();
PCG->Seed = MyDeterministicSeedValue;
PCG->Generate(); // Regenerate with new seed| Type | Contains | Use for |
|---|---|---|
| Position, rotation, scale, density, color | Scatter placement, foliage, instance positioning |
| Spline points + tangents | Roads, rivers, paths, boundary definitions |
| Height + layer weight sampling | Terrain-aware placement, biome queries |
| 3D bounds | Volume-based filtering and generation |
UPCGTextureDataUPCGPrimitiveDataUPCGDynamicMeshDatareferences/pcg-node-reference.md// Build.cs
PublicDependencyModuleNames.Add("ProceduralMeshComponent");// Create section: vertices, triangles (CCW = front), normals, UVs, colors, tangents
void CreateMeshSection(int32 SectionIndex,
const TArray<FVector>& Vertices, const TArray<int32>& Triangles,
const TArray<FVector>& Normals, const TArray<FVector2D>& UV0,
const TArray<FColor>& VertexColors, const TArray<FProcMeshTangent>& Tangents,
bool bCreateCollision);
// Updates vertex positions (incl. collision if enabled). Cannot change topology.
void UpdateMeshSection(int32 SectionIndex,
const TArray<FVector>& Vertices, const TArray<FVector>& Normals,
const TArray<FVector2D>& UV0, const TArray<FColor>& VertexColors,
const TArray<FProcMeshTangent>& Tangents);
void ClearMeshSection(int32 SectionIndex);
void ClearAllMeshSections();
void SetMeshSectionVisible(int32 SectionIndex, bool bNewVisibility);
void SetMaterial(int32 ElementIndex, UMaterialInterface* Material);void ATerrainActor::Build(int32 Grid, float Cell)
{
TArray<FVector> Verts; TArray<int32> Tris; TArray<FVector> Norms;
TArray<FVector2D> UVs; TArray<FColor> Colors; TArray<FProcMeshTangent> Tangs;
for (int32 Y = 0; Y <= Grid; Y++)
for (int32 X = 0; X <= Grid; X++)
{
float Z = SampleOctaveNoise(X * Cell, Y * Cell, 4, 0.5f, 2.f, 80.f);
Verts.Add(FVector(X * Cell, Y * Cell, Z));
Norms.Add(FVector::UpVector);
UVs.Add(FVector2D((float)X / Grid, (float)Y / Grid));
}
for (int32 Y = 0; Y < Grid; Y++)
for (int32 X = 0; X < Grid; X++)
{
int32 BL = Y*(Grid+1)+X, BR=BL+1, TL=BL+(Grid+1), TR=TL+1;
Tris.Add(BL); Tris.Add(TL); Tris.Add(TR);
Tris.Add(BL); Tris.Add(TR); Tris.Add(BR);
}
ProceduralMesh->CreateMeshSection(0, Verts, Tris, Norms,
UVs, Colors, Tangs, /*bCreateCollision=*/true);
}CreateMeshSectionUpdateMeshSectionCreateMeshSectionCreateMeshSection// Background task — compute vertices
class FMeshGenTask : public FNonAbandonableTask
{
public:
TArray<FVector> Vertices;
TArray<int32> Triangles;
void DoWork() { /* Marching cubes, noise sampling, etc. */ }
FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FMeshGenTask, STATGROUP_ThreadPoolAsyncTasks); }
};
// Launch and poll
// Use FAsyncTask (not FAutoDeleteAsyncTask) when polling IsDone() is needed.
// FAutoDeleteAsyncTask deletes itself on completion — calling IsDone() afterward is a use-after-free.
auto* Task = new FAsyncTask<FMeshGenTask>();
Task->StartBackgroundTask();
// Poll safely: if (Task->IsDone()) { /* use Task->GetTask().Vertices */ delete Task; }UProceduralMeshComponent::bUseComplexAsSimpleCollision = true| Feature | ISM ( | HISM ( |
|---|---|---|
| Best for | < 1,000 dynamic instances | > 1,000 mostly static |
| Culling | Distance only | Hierarchical BVH + distance |
| LOD | GPU selection | Built-in transitions |
| Remove cost | O(n) | async BVH rebuild |
InstancedStaticMeshComponent.hvirtual int32 AddInstance(const FTransform& T, bool bWorldSpace = false);
virtual TArray<int32> AddInstances(const TArray<FTransform>& Ts,
bool bShouldReturnIndices, bool bWorldSpace = false, bool bUpdateNavigation = true);
virtual bool UpdateInstanceTransform(int32 Idx, const FTransform& NewT,
bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
virtual bool BatchUpdateInstancesTransforms(int32 StartIdx, const TArray<FTransform>& NewTs,
bool bWorldSpace = false, bool bMarkRenderStateDirty = false, bool bTeleport = false);
bool GetInstanceTransform(int32 Idx, FTransform& OutT, bool bWorldSpace = false) const;
virtual bool RemoveInstance(int32 InstanceIndex); // O(n) for ISM; triggers async BVH rebuild for HISM
virtual void PreAllocateInstancesMemory(int32 AddedCount);
int32 GetNumInstances() const;
// Per-instance custom float data (read in materials via PerInstanceCustomData)
virtual void SetNumCustomDataFloats(int32 N);
virtual bool SetCustomDataValue(int32 Idx, int32 DataIdx, float Value,
bool bMarkRenderStateDirty = false);
virtual bool SetCustomData(int32 Idx, TArrayView<const float> Floats,
bool bMarkRenderStateDirty = false);InstanceStartCullDistanceInstanceEndCullDistanceInstanceLODDistanceScalebUseGpuLodSelectionHISM->SetStaticMesh(TreeMesh);
HISM->SetNumCustomDataFloats(1);
HISM->PreAllocateInstancesMemory(Count);
FRandomStream Rand(Seed);
TArray<FTransform> Transforms; Transforms.Reserve(Count);
for (int32 i = 0; i < Count; i++)
{
FVector Loc(Rand.FRandRange(Min.X, Max.X), Rand.FRandRange(Min.Y, Max.Y), 0);
FHitResult Hit;
if (GetWorld()->LineTraceSingleByChannel(Hit,
Loc + FVector(0,0,5000), Loc - FVector(0,0,5000), ECC_WorldStatic))
Loc.Z = Hit.Location.Z;
Transforms.Add(FTransform(
FRotator(0, Rand.FRandRange(0,360), 0), Loc,
FVector(Rand.FRandRange(0.8f, 1.3f))));
}
TArray<int32> Indices = HISM->AddInstances(Transforms, true, true);
for (int32 i = 0; i < Indices.Num(); i++)
HISM->SetCustomDataValue(Indices[i], 0, Rand.FRand(), false);
HISM->MarkRenderStateDirty();AInstancedFoliageActorUHierarchicalInstancedStaticMeshComponentUProceduralFoliageComponentUProceduralFoliageSpawnerbUseDefaultCollisionstat Foliage// Built-in Perlin (all output in [-1, 1])
float N1 = FMath::PerlinNoise1D(X * Freq);
float N2 = FMath::PerlinNoise2D(FVector2D(X, Y) * Freq);
float N3 = FMath::PerlinNoise3D(FVector(X, Y, Z) * Freq);
// Octave noise
float OctaveNoise(float X, float Y, int32 Oct, float Persist, float Lacu, float Scale)
{
float V=0, A=1, F=1.f/Scale, Max=0;
for (int32 i=0; i<Oct; i++) {
V += FMath::PerlinNoise2D(FVector2D(X,Y)*F) * A;
Max += A; A *= Persist; F *= Lacu;
}
return V / Max;
}
// Seeded deterministic random
FRandomStream Stream(Seed);
float R = Stream.FRandRange(Min, Max);
int32 I = Stream.RandRange(MinI, MaxI);
FVector Dir = Stream.VRand();UTexture2DFTexturePlatformDataBulkData.Lock(LOCK_READ_ONLY)references/procedural-mesh-patterns.mdSplineComponent.h// Build spline (always batch with bUpdateSpline=false, call UpdateSpline() once after)
void AddSplinePoint(const FVector& Pos, ESplineCoordinateSpace::Type Space, bool bUpdate=true);
void SetSplinePoints(const TArray<FVector>& Pts, ESplineCoordinateSpace::Type Space, bool bUpdate=true);
void ClearSplinePoints(bool bUpdate=true);
virtual void UpdateSpline(); // Rebuild reparameterization table
// Query by arc-length distance
FVector GetLocationAtDistanceAlongSpline(float Dist, ESplineCoordinateSpace::Type Space) const;
FVector GetDirectionAtDistanceAlongSpline(float Dist, ESplineCoordinateSpace::Type Space) const;
FVector GetRightVectorAtDistanceAlongSpline(float Dist, ESplineCoordinateSpace::Type Space) const;
FRotator GetRotationAtDistanceAlongSpline(float Dist, ESplineCoordinateSpace::Type Space) const;
FTransform GetTransformAtDistanceAlongSpline(float Dist, ESplineCoordinateSpace::Type Space, bool bUseScale=false) const;
float GetSplineLength() const;
// Point editing
int32 GetNumberOfSplinePoints() const;
void SetSplinePointType(int32 Idx, ESplinePointType::Type Type, bool bUpdate=true);
void SetClosedLoop(bool bClosed, bool bUpdate=true);
void SetTangentsAtSplinePoint(int32 Idx, const FVector& Arrive, const FVector& Leave,
ESplineCoordinateSpace::Type Space, bool bUpdate=true);LinearCurveConstantCurveClampedCurveCustomTangentFindInputKeyClosestToWorldLocation(WorldLocation)AddSplinePoint()RemoveSplinePoint()SetLocationAtSplinePoint()UpdateSpline()UpdateSpline()// Place instances evenly along spline
float Len = Spline->GetSplineLength();
for (float D = 0.f; D <= Len; D += Spacing)
{
FTransform T = Spline->GetTransformAtDistanceAlongSpline(D, ESplineCoordinateSpace::World);
HISM->AddInstance(T, /*bWorldSpace=*/true);
}#include "Components/SplineMeshComponent.h"
USplineMeshComponent* SM = NewObject<USplineMeshComponent>(this);
SM->SetStaticMesh(PipeMesh);
SM->RegisterComponent();
FVector SP, ST, EP, ET;
Spline->GetLocationAndTangentAtSplinePoint(Seg, SP, ST, ESplineCoordinateSpace::Local);
Spline->GetLocationAndTangentAtSplinePoint(Seg+1, EP, ET, ESplineCoordinateSpace::Local);
SM->SetStartAndEnd(SP, ST, EP, ET, /*bUpdateMesh=*/true);
SM->SetForwardAxis(ESplineMeshAxis::X);references/procedural-mesh-patterns.mdCreateMeshSectionUSplineComponent// Marching Cubes result → ProceduralMesh
ProceduralMesh->CreateMeshSection(0, MarchVerts, MarchTris, MarchNormals,
MarchUVs, {}, {}, /*bCreateCollision=*/true);GenerateLocal()TickGenerateOnDemandGenerateLocal()Generate(bForce)bIsCacheable = truebIsEditorOnly = truebCreateCollision=falseCreateMeshSectionUpdateMeshSectionCreateMeshSectionbMarkRenderStateDirty=trueUpdateInstanceTransformtruePreAllocateInstancesMemoryAddSplinePoint(bUpdateSpline=true)falseUpdateSpline()GenerateLocal()Generate(bool)NetMulticast, Reliableue-actor-component-architectureue-physics-collisionue-cpp-foundationsNewObjectTSubclassOfTArrayreferences/pcg-node-reference.mdreferences/procedural-mesh-patterns.md