Agent skill

Ue Procedural Generation

by quodsoler in quodsoler/unreal-engine-skills

A skill your agent uses when generating world content or geometry procedurally in Unreal Engine: PCG graphs, custom PCG nodes in C++, runtime mesh building, instancing and seeded noise.

MITAuto-check passedGame Development

Install Ue Procedural Generation

skills CLI
$ npx skills add quodsoler/unreal-engine-skills --skill ue-procedural-generation -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install quodsoler/unreal-engine-skills ue-procedural-generation --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/ue-procedural-generation .claude/skills/ue-procedural-generation && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
ue-procedural-generation
GitHub stars
362
Token cost
~7.6k tokens
SKILL.md length
1,662 words
Files
3 (incl. references)
Skills in repo
31
Repo updated
First seen
Licence
MIT

At a glance

A skill your agent uses when generating world content or geometry procedurally in Unreal Engine: PCG graphs, custom PCG nodes in C++, runtime mesh building, instancing and seeded noise.

  • Generating world content
  • SKILL.md covers Context, PCG setup, PCG component and runtime… and Point data, plus 9 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md
  • Geometry procedurally in Unreal Engine: PCG graphs

What it does

Ue Procedural Generation is an agent skill from quodsoler/unreal-engine-skills. 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'…

Its SKILL.md is about 7.6k tokens, which your agent loads only when the skill is triggered. The skill folder holds 3 other files, including reference files (for example `references/pcg-node-reference.md` and `references/procedural-mesh-patterns.md`).

It sits in Game Development. It works with C++ and Unreal Engine. The repository describes itself as: Unreal Engine C++ skills for AI coding agents. 27 skills covering gameplay, rendering, networking, animation, and more. Works with Claude Code, Cursor, Windsurf, and any agent… The licence is MIT.

When your agent uses it

  • Generating world content
  • Geometry procedurally in Unreal Engine: PCG graphs
  • Custom PCG nodes in C++
  • Runtime mesh building

Example prompts

  • “UPCGComponent”
  • “UPCGSettings”
  • “IPCGElement”
  • “/ue-procedural-generation”

What it can do on your machine

Read from SKILL.md and the folder at commit f3742d7. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are cpp, csharp and json).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Ue Procedural Generation loads about 7.6k tokens when it runs, and up to ~24k if it reads all its reference files. Until then it costs about 181 tokens; SKILL.md has 1,662 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~181
When it runs · the whole SKILL.md, loaded when a task matches
~7.6k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~24k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from quodsoler/unreal-engine-skills at commit f3742d7, republished under its MIT licence (© quodsoler). 1,662 words, ~7,598 tokens.

Download SKILL.mdSave it as .claude/skills/ue-procedural-generation/SKILL.md (or your agent's skills folder). This skill also uses 2 other files; get the full folder from GitHub.
name
ue-procedural-generation
description
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.
metadata.version
2.0.0
metadata.engine
5.8

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 wiringPCG setup
Driving generation from an actor, runtime generation, partitioningPCG component and runtime generation
Reading/writing points inside a graphPoint data
Writing a new PCG node in C++Custom PCG node in C++
Which node does X, pin labels, node settings fieldsPCG node reference
Building triangles at runtimeProceduralMeshComponent
Boolean ops, primitives, baking to a static meshDynamic Mesh and Geometry Script
Thousands of repeated meshesInstanced static meshes
Roads, rivers, fences, cablesSplines
Heightfields, scatter, reproducible resultsNoise and deterministic random
Marching cubes, BSP dungeons, WFC, Poisson discProcedural mesh patterns

PCG setup

csharp
// MyGame.Build.cs
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "PCG" });
json
{ "Name": "PCG", "Enabled": true }
ClassHeaderRole
UPCGComponentPCGComponent.hActor component that owns a graph and drives generation
UPCGGraphPCGGraph.hGraph asset: nodes, edges, UserParameters
UPCGGraphInstancePCGGraph.hGraph instance with per-instance parameter overrides
UPCGGraphInterfacePCGGraph.hCommon base of graph and graph instance
UPCGSettingsPCGSettings.hNode settings base class
IPCGElementPCGElement.hThe executable half of a node
FPCGContextPCGContext.hPer-execution state: InputData, OutputData, Node, ExecutionSource
UPCGBasePointDataData/PCGBasePointData.hAbstract point collection
UPCGPointArrayDataData/PCGPointArrayData.hStructure-of-arrays point data
UPCGSubsystemSubsystems/PCGSubsystem.hScheduling, partitioning, runtime generation
APCGVolumePCGVolume.hVolume actor carrying a UPCGComponent

PCG component and runtime generation

cpp
#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
}
CallReplicationUse for
Generate(bool bForce) / Cleanup(bool bRemoveComponents)NetMulticast, ReliableServer-driven generation that must appear on clients
GenerateLocal(bool bForce) / CleanupLocal(bool bRemoveComponents)noneClient-side or single-player generation
NotifyPropertiesChangedFromBlueprint()noneMark dirty and conditionally regenerate after editing exposed properties
CancelGeneration()noneAbort an in-flight generation
GetGeneratedGraphOutput()noneRead back the FPCGDataCollection the graph produced

EPCGComponentGenerationTrigger (PCGComponent.h:77): GenerateOnLoad, GenerateOnDemand, GenerateAtRuntime.

Partitioning and runtime generation:

  • SetIsPartitioned(bool) / IsPartitioned() back the bIsComponentPartitioned property; partitioned components dispatch work to local components on a grid.
  • GenerateAtRuntime hands the component to the runtime-gen scheduler. Tune it with SchedulingPolicyClass / SchedulingPolicy (UPCGSchedulingPolicyBase) and bOverrideGenerationRadii + 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_EDITOR only, 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:

cpp
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:

cpp
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:

cpp
#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:

cpp
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.

cpp
// 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; }
};
cpp
// 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:

  • SupportsBasePointDataInputs returning false (the default) makes PCG convert every input to UPCGPointData before your element runs. Return true and use value ranges.
  • IsCacheable must return false if the node spawns actors or components, or reads untracked data.
  • CanExecuteOnlyOnMainThread returning true serializes the node onto the game thread; keep it false unless you touch UWorld or 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 UPCGBlueprintBaseElement and override the Execute(const FPCGDataCollection&, FPCGDataCollection&) BlueprintNativeEvent; seed helpers are GetSeedWithContext(GetContextHandle()) and GetRandomStreamWithContext(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.

csharp
PublicDependencyModuleNames.Add("ProceduralMeshComponent");
cpp
// 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 to false and feed AddCollisionConvexMesh when the mesh must be dynamic.
  • bUseAsyncCooking moves 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).

csharp
PublicDependencyModuleNames.AddRange(new string[] { "GeometryFramework", "GeometryScriptingCore" });
LibraryRepresentative functions
UGeometryScriptLibrary_MeshPrimitiveFunctionsAppendBox, AppendSphereLatLong, AppendSphereBox, AppendCapsule, AppendBoxWithCollision
UGeometryScriptLibrary_MeshBooleanFunctionsApplyMeshBoolean(TargetMesh, TargetTransform, ToolMesh, ToolTransform, Operation, Options, Debug)
UGeometryScriptLibrary_MeshDeformFunctionsApplyPerlinNoiseToMesh2(TargetMesh, Selection, Options, Debug)
UGeometryScriptLibrary_MeshNormalsFunctionsRecomputeNormals(TargetMesh, CalculateOptions, bDeferChangeNotifications, Debug), SetPerFaceNormals
UGeometryScriptLibrary_StaticMeshFunctionsCopyMeshToStaticMesh, 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

UInstancedStaticMeshComponentUHierarchicalInstancedStaticMeshComponent
HeaderComponents/InstancedStaticMeshComponent.hComponents/HierarchicalInstancedStaticMeshComponent.h
Best forSmall, frequently mutated setsLarge, mostly static sets
CullingStart/end cull distanceHierarchical tree plus cull distance
RemovalCheapTriggers a tree rebuild (bAutoRebuildTreeOnInstanceChanges, BuildTreeIfOutdated)
cpp
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.

Show full SKILL.md (633 more words)Show less

Splines

cpp
// 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.

cpp
// 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

cpp
#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 from FPCGContext::GetSeed(). Do not call FMath::Rand.
  • For networked generation, replicate the seed (GameState or spawn parameter) and use Generate(bForce); GenerateLocal never 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 emitUse in 5.8Source
FSimplePCGElementIPCGElementUE_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::SourceComponentFPCGContext::ExecutionSourceUE_DEPRECATED(5.6) in PCGContext.h:122
FPCGContext::GetComponentName()GetExecutionSourceName()UE_DEPRECATED(5.6) in PCGContext.h:207
FPCGContext::StackGetStack()UE_DEPRECATED(5.6) in PCGContext.h:147
UPCGSpatialData::IntersectWith/ProjectOn/UnionWith/Subtract/CopyInternal without a contextoverloads taking FPCGContext*UE_DEPRECATED(5.5) in Data/PCGSpatialData.h:208-280
ToPointData() (no context)ToBasePointData(FPCGContext*) / ToBasePointDataWithContextDeprecatedFunction 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 sizeUE_DEPRECATED(5.8) in PCGComponent.h:872
UPCGGraph::HiGenExponential / GetGridExponential()HiGenGridSizeMultiplier / GetGridSizeMultiplier()UE_DEPRECATED(5.8) in PCGGraph.h:866,872
UPCGGraph::bIsEditorOnlyShouldCook (FPerPlatformBool)UE_DEPRECATED(5.8) in PCGGraph.h:868
UPCGSettings::BP_GetTypeUnionOfIncidentEdgesGetTypeUnionIDOfIncidentEdgesUE_DEPRECATED(5.7) in PCGSettings.h:520
UInstancedStaticMeshComponent::InstanceBodiesGetInstanceBodies()UE_DEPRECATED(5.8) in Components/InstancedStaticMeshComponent.h:524
UInstancedStaticMeshComponent::InitInstanceBodyInstancePhysicsBodiesUE_DEPRECATED(5.8) in Components/InstancedStaticMeshComponent.h:771
ApplyPerlinNoiseToMeshApplyPerlinNoiseToMesh2UE_DEPRECATED(5.7) in GeometryScript/MeshDeformFunctions.h:401
CopyMeshToStaticMesh without bUseSectionMaterialsoverload taking bUseSectionMaterialsUE_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.

  • ue-actor-component-architecture — component construction, registration, attachment and lifecycle for the components created here
  • ue-physics-collision — collision profiles, body setup, complex vs simple collision, traces used to project scatter onto terrain
  • ue-materials-rendering — material instances, PerInstanceCustomData, Nanite and virtual texturing for generated geometry
  • ue-world-level-streaming — World Partition, data layers and HLODs that PCG partitioning and runtime generation build on
  • ue-async-threading — ParallelFor, Async, task graph and thread-safety rules for background mesh and point computation
  • ue-mass-entity — large agent populations, an alternative to instanced components for crowds
  • ue-data-assets-tables — data assets and data tables that drive generation parameters and mesh/prop tables
  • ue-niagara-effects — Niagara systems, user parameters, data interfaces and data channels

© quodsoler, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 2 other files (references) in skills/ue-procedural-generation of quodsoler/unreal-engine-skills.

  • SKILL.md
  • references/pcg-node-reference.md
  • references/procedural-mesh-patterns.md

Open the folder on GitHubat commit f3742d7

Compare with similar skills

Ue Procedural Generation next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Ue Procedural Generation compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Ue Procedural Generation this skillquodsoler/unreal-engine-skills362—~7.6kAutomated safety check: PassMIT
Ue Specifiersfjz13/UnrealSpecifiers1.1k—~1.4kAutomated safety check: PassCustom licence
Unreal Engine MCPEpicGames/unreal-engine-skills-for-claude-code-plugin338—~1.8kAutomated safety check: PassMIT
Unreal Live CodingItalink/UnrealClientProtocol127—~1kAutomated safety check: PassMIT
Unreal Cpp Gameplayukanwat/overtime3871 repos~1.5kAutomated safety check: PassApache-2.0
Unreal Enhanced Inputukanwat/overtime3871 repos~1.5kAutomated safety check: PassApache-2.0

Similar skills

  • Ue Specifiers

    fjz13/UnrealSpecifiers

    A skill your agent uses when writing, editing, reviewing, validating, or explaining Unreal Engine C++ reflected declarations: UCLASS, USTRUCT, UENUM, UINTERFACE, UPROPERTY, UFUNCTION, UPARAM, UMETA…

    1.1k GitHub stars~1.4k tokensUpdated 4 mo ago
    Game DevelopmentAuto-check passed
  • Unreal Engine MCP

    EpicGames/unreal-engine-skills-for-claude-code-plugin

    Drives a live Unreal Editor through the unreal-mcp server: spawn actors, edit Blueprints, materials and sequences, change properties and trigger Live Coding.

    338 GitHub stars~1.8k tokensUpdated 23 days ago
    Game DevelopmentAuto-check passed
  • Unreal Live Coding

    Italink/UnrealClientProtocol

    Trigger Live Coding compilation via UCP. An agent skill from Italink/UnrealClientProtocol.

    127 GitHub stars~1k tokensUpdated 1 mo ago
    Game DevelopmentAuto-check passed
  • Unreal Cpp Gameplay

    ukanwat/overtime

    Write Unreal Engine 5 C++ gameplay code: the UCLASS/UPROPERTY/UFUNCTION reflection macros, the Gameplay Framework (GameMode, Pawn, Character, PlayerController, Actor components), and the module…

    387 GitHub starsUsed in 1 repo~1.5k tokens
    Game DevelopmentAuto-check passed
  • Unreal Enhanced Input

    ukanwat/overtime

    Set up player input in Unreal Engine 5 with Enhanced Input: Input Actions, Input Mapping Contexts, modifiers and triggers, adding the mapping context, and binding actions by ETriggerEvent.

    387 GitHub starsUsed in 1 repo~1.5k tokens
    Game DevelopmentAuto-check passed
  • Unreal Niagara

    ukanwat/overtime

    Create and control VFX in Unreal Engine 5 with Niagara: systems and emitters, modules and the spawn/update stages, exposed User parameters, and spawning or driving effects from Blueprints or C++.

    387 GitHub starsUsed in 1 repo~1.5k tokens
    Game DevelopmentAuto-check passed

More from quodsoler/unreal-engine-skills

All 31 skills in this repo
  • Unreal Actor-Component Architecture

    quodsoler/unreal-engine-skills

    Unreal Engine C++ guidance for AActor and UActorComponent: lifecycle hooks, spawning, component creation and attachment, ticking, interfaces and composition.

    362 GitHub stars~7.5k tokensUpdated 11 days ago
    Auto-check passed
  • Unreal Engine AI and Navigation

    quodsoler/unreal-engine-skills

    Guides Unreal Engine C++ AI work across AI controllers, behavior trees, blackboards, perception, navmesh pathfinding, EQS and Smart Objects.

    362 GitHub stars~8.9k tokensUpdated 11 days ago
    Auto-check passed
  • Unreal Engine Animation System

    quodsoler/unreal-engine-skills

    Reference for writing and debugging Unreal Engine 5.8 animation C++: AnimInstance and proxy, montages, notifies, curves, blend spaces, state machines, linked layers and root motion.

    362 GitHub stars~7k tokensUpdated 11 days ago
    Auto-check passed
  • Unreal Engine Async and Threading

    quodsoler/unreal-engine-skills

    Picks and applies the right Unreal Engine C++ concurrency API, from UE::Tasks and FPipe to ParallelFor, FRunnable, locks and timers, with game-thread safety rules.

    362 GitHub stars~8k tokensUpdated 11 days ago
    Auto-check passed
  • Ue Audio System

    quodsoler/unreal-engine-skills

    A skill your agent uses when playing, mixing or debugging sound in Unreal Engine — one-shot SFX, looping ambience, music, dialogue or procedural audio.

    362 GitHub stars~7.9k tokensUpdated 11 days ago
    Auto-check passed
  • Unreal Blueprint and C++ Interop

    quodsoler/unreal-engine-skills

    Reference for exposing Unreal Engine C++ to Blueprint and calling Blueprint from C++, covering UFUNCTION and UPROPERTY specifiers, latent and async nodes, and interfaces.

    362 GitHub stars~7.8k tokensUpdated 11 days ago
    Auto-check passed

Questions about Ue Procedural Generation

What does Ue Procedural Generation do?

A skill your agent uses when generating world content or geometry procedurally in Unreal Engine: PCG graphs, custom PCG nodes in C++, runtime mesh building, instancing and seeded noise. Ue Procedural Generation is an agent skill from quodsoler/unreal-engine-skills. 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.

When should I use Ue Procedural Generation?

Ue Procedural Generation fits situations like: generating world content; geometry procedurally in Unreal Engine: PCG graphs; custom PCG nodes in C++; runtime mesh building.

How do I install Ue Procedural Generation in Claude Code?

Run `npx skills add quodsoler/unreal-engine-skills --skill ue-procedural-generation -a claude-code`. Or copy the skill folder (skills/ue-procedural-generation in quodsoler/unreal-engine-skills) into .claude/skills/ue-procedural-generation in your project. Claude Code loads it when a task matches its description.

How do I install Ue Procedural Generation in Codex?

Run `npx skills add quodsoler/unreal-engine-skills --skill ue-procedural-generation -a codex`. Or copy the skill folder (skills/ue-procedural-generation in quodsoler/unreal-engine-skills) into .agents/skills/ue-procedural-generation in your project. Codex loads it when a task matches its description.

Can I use Ue Procedural Generation in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add quodsoler/unreal-engine-skills --skill ue-procedural-generation -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ue-procedural-generation, .gemini/skills/ue-procedural-generation, .github/skills/ue-procedural-generation and .opencode/skills/ue-procedural-generation in your project.

What does Ue Procedural Generation need to run?

SKILL.md names no scripts, command-line tools or credentials: Ue Procedural Generation is instructions for the agent only.

Does Ue Procedural Generation access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Ue Procedural Generation safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Ue Procedural Generation use?

Ue Procedural Generation is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Ue Procedural Generation use?

About 7.6k tokens (SKILL.md is roughly 30k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 17k tokens, read only when the agent opens those files.

What are the alternatives to Ue Procedural Generation?

Skills that share tags, products or a category with Ue Procedural Generation: Ue Specifiers (fjz13/UnrealSpecifiers, 1.1k stars), Unreal Engine MCP (EpicGames/unreal-engine-skills-for-claude-code-plugin, 338 stars), Unreal Live Coding (Italink/UnrealClientProtocol, 127 stars) and Unreal Cpp Gameplay (ukanwat/overtime, 387 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Ue Procedural Generation?

quodsoler (a GitHub user) maintains it in quodsoler/unreal-engine-skills, which has 362 GitHub stars. The repository holds 31 skills in this directory. The repository was last updated on September 28, 2026.

Source: quodsoler/unreal-engine-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.