3D Model Generation
Generate AI-powered 3D models in Unreal Engine using natural language prompts
3D Model Generation
The PlayKit SDK enables AI-powered 3D model generation using natural language prompts. Generate game-ready 3D models with textures and materials directly from text descriptions.
Overview
The UPlayKit3DClient component provides:
- Text-to-3D Generation: Create 3D models from natural language descriptions
- Automatic Polling: Task status is automatically monitored with progress updates
- Progress Tracking: Real-time progress percentages and status changes
- Quality Control: Configurable texture and geometry quality
- PBR Materials: Optional physically-based rendering material generation
Important: Generated model URLs expire after 5 minutes. Download them immediately upon completion.
Adding the Component
Blueprint
- Select your Actor in the editor
- Click "Add Component"
- Search for "PlayKit 3D Client"
- Add to your Actor
C++
#include "Client/PlayKit3DClient.h"
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PlayKit")
UPlayKit3DClient* ThreeDClient;
AMyActor()
{
ThreeDClient = CreateDefaultSubobject<UPlayKit3DClient>(TEXT("3DClient"));
}
};Configuration Properties
Configure the component's default settings in the Details panel:
| Property | Type | Default | Description |
|---|---|---|---|
| Model Name | String | "tripo-3d" | AI model to use for generation |
| Model Version | String | "v2.5-20250123" | Specific model version |
| Default Texture Quality | Enum | Standard | Texture detail level (Standard/Detailed) |
| Default Geometry Quality | Enum | Standard | Mesh detail level (Standard/Detailed) |
| bDefault PBR | Boolean | true | Enable PBR materials by default |
| Default Face Limit | Integer | 50000 | Maximum polygon count (0 = server default) |
| bDefault Auto Size | Boolean | false | Automatically normalize model size |
Basic Generation
Simple Generation
void AMyActor::GenerateSimpleModel()
{
// Bind event handlers
ThreeDClient->OnCompleted.AddDynamic(this, &AMyActor::OnModelGenerated);
ThreeDClient->OnProgress.AddDynamic(this, &AMyActor::OnProgressUpdate);
ThreeDClient->OnError.AddDynamic(this, &AMyActor::OnGenerationError);
// Start generation
ThreeDClient->Generate3D(TEXT("A medieval castle with stone walls and towers"));
}
void AMyActor::OnModelGenerated(FPlayKit3DResponse Response)
{
if (Response.bSuccess)
{
FString ModelURL = Response.Task.Output.ModelUrl;
UE_LOG(LogTemp, Log, TEXT("Model URL: %s"), *ModelURL);
// CRITICAL: Download immediately (5-minute expiration)
DownloadModelFromURL(ModelURL);
}
}
void AMyActor::OnProgressUpdate(const FString& TaskId, int32 Progress)
{
UE_LOG(LogTemp, Log, TEXT("Generation progress: %d%%"), Progress);
// Update progress bar UI
}
void AMyActor::OnGenerationError(const FString& ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Generation failed [%s]: %s"), *ErrorCode, *ErrorMessage);
}Blueprint
Event BeginPlay
�?3D Client �?Generate 3D
├── Prompt: "A fantasy sword with glowing runes"
└── On Completed �?Download Model (Model URL)Advanced Generation
With Negative Prompt
ThreeDClient->Generate3DWithNegative(
TEXT("A futuristic spaceship"),
TEXT("low quality, blurry, broken geometry") // Things to avoid
);Full Configuration
void AMyActor::GenerateAdvancedModel()
{
FPlayKit3DConfig Config;
Config.Prompt = TEXT("A detailed dragon statue");
Config.NegativePrompt = TEXT("low poly, simple, cartoonish");
Config.bTexture = true;
Config.bPBR = true;
Config.TextureQuality = EPlayKit3DQuality::Detailed;
Config.GeometryQuality = EPlayKit3DQuality::Detailed;
Config.FaceLimit = 100000; // Higher polygon count
Config.bAutoSize = true; // Normalize size
Config.TextureSeed = 12345; // For reproducible results
ThreeDClient->Generate3DAdvanced(Config);
}Event Callbacks
The component provides four event types:
OnProgress
Fired during polling with current progress percentage:
UFUNCTION()
void OnProgressUpdate(const FString& TaskId, int32 Progress)
{
// Update loading UI
ProgressBar->SetPercent(Progress / 100.0f);
StatusText->SetText(FText::Format(
LOCTEXT("GeneratingProgress", "Generating: {0}%"),
FText::AsNumber(Progress)
));
}OnStatusChanged
Fired when task status changes (Queued �?Running �?Success):
UFUNCTION()
void OnStatusChanged(const FString& TaskId,
EPlayKit3DTaskStatus OldStatus,
EPlayKit3DTaskStatus NewStatus)
{
switch (NewStatus)
{
case EPlayKit3DTaskStatus::Queued:
StatusText->SetText(LOCTEXT("StatusQueued", "Queued..."));
break;
case EPlayKit3DTaskStatus::Running:
StatusText->SetText(LOCTEXT("StatusRunning", "Generating..."));
break;
case EPlayKit3DTaskStatus::Success:
StatusText->SetText(LOCTEXT("StatusSuccess", "Complete!"));
break;
}
}OnCompleted
Fired when generation succeeds:
UFUNCTION()
void OnModelGenerated(FPlayKit3DResponse Response)
{
if (Response.bSuccess)
{
// Main model file (GLB format)
FString ModelURL = Response.Task.Output.ModelUrl;
// Optional PBR model (if enabled)
FString PBRModelURL = Response.Task.Output.PBRModelUrl;
// Preview image
FString PreviewImageURL = Response.Task.Output.RenderedImageUrl;
// Timestamps
FDateTime GeneratedAt = Response.Task.Output.GeneratedAt;
int64 CreatedTimestamp = Response.Task.CreatedAt;
int64 CompletedTimestamp = Response.Task.CompletedAt;
// Download before URLs expire!
DownloadModel(ModelURL);
}
}OnError
Fired when generation fails:
UFUNCTION()
void OnGenerationError(const FString& ErrorCode, const FString& ErrorMessage)
{
if (ErrorCode == TEXT("REQUEST_IN_PROGRESS"))
{
// Task already running
ShowMessage(TEXT("Please wait for current generation to complete"));
}
else if (ErrorCode == TEXT("PLAYER_INSUFFICIENT_CREDIT"))
{
// Not enough credits
ShowRechargeDialog();
}
else
{
// Other errors
UE_LOG(LogTemp, Error, TEXT("Error [%s]: %s"), *ErrorCode, *ErrorMessage);
}
}Task Management
Check Status
// Check if generation is running
if (ThreeDClient->IsProcessing())
{
UE_LOG(LogTemp, Log, TEXT("Generation in progress"));
}
// Get current task info
FString TaskId = ThreeDClient->GetCurrentTaskId();
EPlayKit3DTaskStatus Status = ThreeDClient->GetCurrentStatus();
int32 Progress = ThreeDClient->GetCurrentProgress();Cancel Task
void AMyActor::CancelGeneration()
{
ThreeDClient->CancelTask();
// Polling stops, events cleared
}Manual Status Query
// Query specific task by ID (normally automatic)
ThreeDClient->QueryTaskStatus(TEXT("task-id-here"));Quality Settings
Texture Quality
- Standard: Faster generation, lower detail
- Detailed: Slower generation, higher detail
Config.TextureQuality = EPlayKit3DQuality::Detailed;Geometry Quality
Controls mesh complexity:
Config.GeometryQuality = EPlayKit3DQuality::Detailed;
Config.FaceLimit = 100000; // Maximum polygon countPBR Materials
Enable physically-based rendering materials:
Config.bPBR = true; // Generates separate PBR model URLDownloading Models
Critical: Model URLs expire after 5 minutes. Download immediately upon receiving OnCompleted event.
HTTP Download Example
void AMyActor::DownloadModelFromURL(const FString& URL)
{
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(URL);
Request->SetVerb(TEXT("GET"));
Request->OnProcessRequestComplete().BindUObject(
this, &AMyActor::HandleModelDownload);
Request->ProcessRequest();
}
void AMyActor::HandleModelDownload(
FHttpRequestPtr Request,
FHttpResponsePtr Response,
bool bWasSuccessful)
{
if (bWasSuccessful && Response.IsValid())
{
TArray<uint8> ModelData = Response->GetContent();
// Save to disk
FString SavePath = FPaths::ProjectSavedDir() / TEXT("Models") / TEXT("generated.glb");
FFileHelper::SaveArrayToFile(ModelData, *SavePath);
// Import to Unreal (requires GLB importer plugin)
ImportGLBModel(SavePath);
}
}Save to Content Browser
void AMyActor::SaveToContentBrowser(const TArray<uint8>& ModelData, const FString& AssetName)
{
FString PackagePath = TEXT("/Game/GeneratedModels/") + AssetName;
UPackage* Package = CreatePackage(*PackagePath);
// Create asset from GLB data
// (Implementation depends on your GLB import pipeline)
}Prompt Engineering
Effective Prompts
// Good: Specific and detailed
TEXT("A low-poly treasure chest with metal hinges and lock, game-ready asset")
// Good: Style specification
TEXT("Stylized cartoon tree with rounded shapes, mobile game aesthetic")
// Good: Technical requirements
TEXT("Modular brick wall section, tileable, PBR textures, game asset")Best Practices
- Be Specific: Include material types, style, purpose
- Set Expectations: "low-poly", "high-detail", "game-ready"
- Define Style: "realistic", "stylized", "cartoon", "sci-fi"
- Mention Use Case: "character prop", "environment asset", "weapon"
Negative Prompts
Use negative prompts to avoid unwanted features:
Config.NegativePrompt = TEXT("high poly, photorealistic, complex geometry");Error Handling
Common Errors
| Error Code | Cause | Solution |
|---|---|---|
REQUEST_IN_PROGRESS | Task already running | Wait for completion or cancel |
INVALID_PROMPT | Empty prompt | Provide valid description |
PLAYER_INSUFFICIENT_CREDIT | Not enough credits | Purchase credits |
GENERATION_FAILED | Server error | Retry with different prompt |
CONTENT_BANNED | Violates content policy | Modify prompt |
NETWORK_ERROR | Connection failed | Check internet connection |
Retry Logic
void AMyActor::GenerateWithRetry(const FString& Prompt, int32 MaxRetries)
{
ThreeDClient->OnError.AddDynamic(this, &AMyActor::HandleErrorWithRetry);
ThreeDClient->Generate3D(Prompt);
}
void AMyActor::HandleErrorWithRetry(const FString& ErrorCode, const FString& ErrorMessage)
{
if (RetryCount < MaxRetries && ErrorCode == TEXT("NETWORK_ERROR"))
{
RetryCount++;
FTimerHandle RetryTimer;
GetWorld()->GetTimerManager().SetTimer(
RetryTimer,
[this]() { ThreeDClient->Generate3D(LastPrompt); },
2.0f, // Wait 2 seconds
false
);
}
}Performance Considerations
Memory Management
// Component automatically cleans up on EndPlay
// Timers and HTTP requests are automatically cancelled
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
// No manual cleanup needed
}Concurrent Requests
Only one generation task per component instance:
// Check before starting
if (!ThreeDClient->IsProcessing())
{
ThreeDClient->Generate3D(Prompt);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Task already in progress"));
}Polling Efficiency
The component automatically:
- Adjusts polling interval based on server recommendations
- Retries on network failures
- Stops polling on terminal states
Complete Example
UCLASS()
class AModelGeneratorActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
UPlayKit3DClient* ThreeDClient;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString PromptText;
AModelGeneratorActor()
{
ThreeDClient = CreateDefaultSubobject<UPlayKit3DClient>(TEXT("3DClient"));
// Configure defaults
ThreeDClient->ModelName = TEXT("tripo-3d");
ThreeDClient->DefaultTextureQuality = EPlayKit3DQuality::Detailed;
ThreeDClient->DefaultFaceLimit = 75000;
}
protected:
virtual void BeginPlay() override
{
Super::BeginPlay();
// Bind events
ThreeDClient->OnCompleted.AddDynamic(this, &AModelGeneratorActor::OnCompleted);
ThreeDClient->OnProgress.AddDynamic(this, &AModelGeneratorActor::OnProgress);
ThreeDClient->OnStatusChanged.AddDynamic(this, &AModelGeneratorActor::OnStatusChanged);
ThreeDClient->OnError.AddDynamic(this, &AModelGeneratorActor::OnError);
}
public:
UFUNCTION(BlueprintCallable, Category = "PlayKit")
void StartGeneration()
{
if (PromptText.IsEmpty())
{
UE_LOG(LogTemp, Warning, TEXT("Prompt is empty"));
return;
}
ThreeDClient->Generate3D(PromptText);
}
private:
UFUNCTION()
void OnCompleted(FPlayKit3DResponse Response)
{
if (Response.bSuccess)
{
DownloadAndImport(Response.Task.Output.ModelUrl);
}
}
UFUNCTION()
void OnProgress(const FString& TaskId, int32 Progress)
{
UE_LOG(LogTemp, Log, TEXT("Progress: %d%%"), Progress);
}
UFUNCTION()
void OnStatusChanged(const FString& TaskId,
EPlayKit3DTaskStatus OldStatus,
EPlayKit3DTaskStatus NewStatus)
{
// Update UI
}
UFUNCTION()
void OnError(const FString& ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Error: %s"), *ErrorMessage);
}
void DownloadAndImport(const FString& URL)
{
// Download implementation
}
};Blueprint Nodes Reference
| Node | Category | Description |
|---|---|---|
| Generate 3D | PlayKit|3D | Start generation with simple prompt |
| Generate 3D (With Negative) | PlayKit|3D | Generate with negative prompt |
| Generate 3D (Advanced) | PlayKit|3D | Generate with full configuration |
| Cancel Task | PlayKit|3D | Stop current generation |
| Is Processing | PlayKit|3D | Check if task is running |
| Get Current Task ID | PlayKit|3D | Get active task identifier |
| Get Current Status | PlayKit|3D | Get current task status |
| Get Current Progress | PlayKit|3D | Get progress percentage |
Next Steps
- Learn about Image Generation for 2D textures
- Explore NPC Conversations for AI characters
- See API Reference for complete documentation