Grid Placement System
Core System
PlacementObjectRegistry

PlacementObjectRegistry

The PlacementObjectRegistry is a component that manages the central database of all placeable objects in your Grid Placement System. It provides flexible configuration options, runtime management, and seamless integration with UI systems.

Overview

This component acts as the single source of truth for all objects that can be placed in your game.
It supports both design-time configuration (through DataTables) and runtime registration (through PlacementHelper), making it perfect for both static object libraries and dynamic content systems.

Key Features

  • Flexible Configuration - DataTables or runtime registration via PlacementHelper
  • Category Organization - Group objects by type, theme, or functionality
  • Event Integration - React to registry changes and placement triggers
  • UI Data Provider - Direct integration with placement UI widgets
  • Auto-Connection - Automatically finds and connects to PlacementSystemComponent
  • Runtime Management - Add/remove objects dynamically during gameplay
  • Multiple DataTable Support - Load from multiple DataTable assets

Setup

Component Installation

  1. Add PlacementObjectRegistry component to your Player Pawn
  2. Add PlacementSystemComponent to the same pawn (for auto-connection)
  3. Configure your preferred registration method in the Details panel

Auto-Connection

The registry automatically connects to the placement system:

// Enable automatic connection (default: true)
bAutoConnectToPlacementSystem = true

Configuration Methods

The registry supports two primary configuration approaches:

Method Priority:

  1. PlacementHelper Registration - Recommended for most projects
  2. DataTables - Best for large datasets and designer workflows

Note: Methods can be combined. PlacementHelper adds to whatever is configured in the DataTables.

Method 1: PlacementHelper Registration (Recommended)

Use PlacementHelper::RegisterObject() for the easiest and most flexible setup.

Usage:

// In your GameMode or PlayerPawn BeginPlay
void AMyGameMode::RegisterAllObjects()
{
    // Initialize the placement system first
    UPlacementHelper::InitializePlacementSystem(GetWorld()->GetFirstPlayerController()->GetPawn());
    
    // Furniture
    UPlacementHelper::RegisterObject(ChairClass, "Office Chair", ChairIcon, "Ergonomic seating", "Furniture");
    UPlacementHelper::RegisterObject(DeskClass, "Work Desk", DeskIcon, "Spacious workspace", "Furniture");
    UPlacementHelper::RegisterObject(BookshelfClass, "Bookshelf", BookshelfIcon, "Storage for books", "Furniture");
    
    // Lighting
    UPlacementHelper::RegisterObject(DeskLampClass, "Desk Lamp", LampIcon, "Adjustable LED lamp", "Lighting");
    UPlacementHelper::RegisterObject(CeilingFanClass, "Ceiling Fan", FanIcon, "Cooling and air circulation", "Lighting");
    
    // Decorations
    UPlacementHelper::RegisterObject(PlantClass, "Office Plant", PlantIcon, "Green decoration", "Decorations");
    UPlacementHelper::RegisterObject(PictureClass, "Wall Picture", PictureIcon, "Decorative artwork", "Decorations");
}

Benefits:

  • Simplest implementation - just function calls
  • No editor configuration required
  • Perfect for conditional registration
  • Easy to organize with helper functions
  • Integrates seamlessly with PlacementHelper workflow

Method 2: DataTables Configuration

Use DataTables for very large object libraries (50+ objects) or when non-programmers need to manage the object database.

Data Tables Configuration
PropertyTypeDescription
Object Data TablesArray<UDataTable*>Multiple DataTables containing FPlaceableObjectDataTable rows

Setup:

  1. Create DataTable assets with FPlaceableObjectDataTable as the row structure
  2. Add rows for each placeable object
  3. Add the DataTables to the Object Data Tables array

Benefits:

  • Excellent for very large datasets (100+ objects)
  • Can be edited by designers/artists without code
  • Supports multiple DataTables for organization
  • Automatic loading on BeginPlay
  • Shareable across multiple projects

When to use: Large object libraries, designer-managed content,
or when you need multiple DataTables for organization.

Configuration Properties

DataTable Registration Settings

PropertyTypeDefaultDescription
Object Data TablesArray<UDataTable*>EmptyConfigure objects using multiple DataTable assets (FPlaceableObjectDataTable structure)

Auto Setup Settings

PropertyTypeDefaultDescription
Auto Connect To Placement SystemBooleantrueAutomatically find and connect to PlacementSystemComponent on the same actor

Runtime State (Read-Only)

PropertyTypeDescription
Registered ObjectsArray<FPlaceableObjectData>The actual object database used by the system (populated from DataTables and runtime registration)

Management Functions

RemoveObject

UFUNCTION(BlueprintCallable, Category = "GPS Management")
static bool RemoveObject(TSubclassOf<AActor> ActorClass);

Removes an object from the registry.

RemoveObjectsByCategory

UFUNCTION(BlueprintCallable, Category = "GPS Management")
static int32 RemoveObjectsByCategory(const FString& Category);

Removes all objects from a specific category.

ClearAllObjects

UFUNCTION(BlueprintCallable, Category = "GPS Management")
static void ClearAllObjects();

Removes all objects from the registry.

Data Access Functions

GetRegisteredObjects

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GPS Data")
static TArray<FPlaceableObjectData> GetRegisteredObjects();

Returns all registered objects. Perfect for UI population.

GetAllCategories

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GPS Data")
static TArray<FString> GetAllCategories();

Returns all unique category names from registered objects.

GetObjectCount

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GPS Data")
static int32 GetObjectCount();

Returns the total number of registered objects.

IsObjectRegistered

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GPS Data")
static bool IsObjectRegistered(TSubclassOf<AActor> ActorClass);

Checks if a specific object class is registered.

Component API (Advanced Use Cases)

For direct component access when you need component-specific functionality:

Core Management Functions

AddObject

void AddObject(
    TSubclassOf<AActor> ActorClass,
    const FString& DisplayName,
    UTexture2D* Icon = nullptr,
    const FString& Description = TEXT(""),
    const FString& Category = TEXT("General")
);

Adds an object directly to the registry component.

RemoveObject

bool RemoveObject(TSubclassOf<AActor> ActorClass);

Removes an object from the registry.

RemoveObjectsByCategory

int32 RemoveObjectsByCategory(const FString& Category);

Removes all objects from a specific category.

State Check Functions

IsObjectRegistered

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Registry Data")
bool IsObjectRegistered(TSubclassOf<AActor> ActorClass) const;

Checks if a specific object class is registered.

Data Access Functions

GetAllObjects

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Registry Data")
TArray<FPlaceableObjectData> GetAllObjects() const;

Returns all registered objects.

GetAllCategories

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Registry Data")
TArray<FString> GetAllCategories() const;

Returns all unique category names.

GetObjectCount

UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Registry Data")
int32 GetObjectCount() const;

Returns the total number of registered objects.

Connection Functions

ConnectToPlacementSystem

void ConnectToPlacementSystem(UPlacementSystemComponent* PlacementSystem);

Manually connect to a specific PlacementSystemComponent (usually automatic).

Events System

OnObjectsRegistryChanged

UPROPERTY(BlueprintAssignable, Category = "Events")
FOnObjectsRegistryChanged OnObjectsRegistryChanged;

Fired whenever the number of registered objects changes.

Parameters:

  • int32 TotalObjects - New total count of objects

Usage:

// Get registry component and bind to events
if (APawn* PlayerPawn = GetWorld()->GetFirstPlayerController()->GetPawn())
{
    if (UPlacementObjectRegistry* Registry = PlayerPawn->FindComponentByClass<UPlacementObjectRegistry>())
    {
        Registry->OnObjectsRegistryChanged.AddDynamic(this, &UMyWidget::OnRegistryChanged);
    }
}
 
void UMyWidget::OnRegistryChanged(int32 TotalObjects)
{
    ObjectCountLabel->SetText(FText::AsNumber(TotalObjects));
    RefreshObjectList();
}

Data Structures

FPlaceableObjectData

USTRUCT(BlueprintType)
struct FPlaceableObjectData
{
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<AActor> ActorClass;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString DisplayName;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UTexture2D* Icon;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString Description;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString Category;
};

FPlaceableObjectDataTable

USTRUCT(BlueprintType)
struct FPlaceableObjectDataTable : public FTableRowBase
{
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<AActor> ActorClass;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString DisplayName;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UTexture2D* Icon;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString Description;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString Category;
};

The DataTable version that automatically converts to FPlaceableObjectData when loaded.

Usage Patterns

Static Object Library (Recommended)

// Use PlacementHelper for clean, organized registration
void AMyGameMode::SetupPlacementObjects()
{
    // Initialize the system first
    UPlacementHelper::InitializePlacementSystem(GetWorld()->GetFirstPlayerController()->GetPawn());
    
    // Register objects by category
    RegisterFurniture();
    RegisterLighting();
    RegisterDecorations();
}
 
void AMyGameMode::RegisterFurniture()
{
    UPlacementHelper::RegisterObject(ChairClass, "Office Chair", ChairIcon, "Comfortable seating", "Furniture");
    UPlacementHelper::RegisterObject(DeskClass, "Work Desk", DeskIcon, "Spacious workspace", "Furniture");
    UPlacementHelper::RegisterObject(BookshelfClass, "Bookshelf", BookshelfIcon, "Storage for books", "Furniture");
}
 
void AMyGameMode::RegisterLighting()
{
    UPlacementHelper::RegisterObject(DeskLampClass, "Desk Lamp", LampIcon, "Adjustable LED lamp", "Lighting");
    UPlacementHelper::RegisterObject(CeilingFanClass, "Ceiling Fan", FanIcon, "Cooling and air circulation", "Lighting");
}
 
void AMyGameMode::RegisterDecorations()
{
    UPlacementHelper::RegisterObject(PlantClass, "Office Plant", PlantIcon, "Green decoration", "Decorations");
    UPlacementHelper::RegisterObject(PictureClass, "Wall Picture", PictureIcon, "Decorative artwork", "Decorations");
}

Dynamic Content System

void AContentManager::LoadFurniturePack()
{
    // Load object classes from asset bundle
    TArray<TSubclassOf<AActor>> FurnitureClasses = LoadFurnitureAssets();
    
    // Use PlacementHelper for dynamic registration
    for (TSubclassOf<AActor> FurnitureClass : FurnitureClasses)
    {
        FString Name = GetLocalizedName(FurnitureClass);
        UTexture2D* Icon = GetObjectIcon(FurnitureClass);
        
        UPlacementHelper::RegisterObject(FurnitureClass, Name, Icon, "", "DLC Furniture");
    }
}
 
void AContentManager::UnloadFurniturePack()
{
    // Remove DLC objects by category
    UPlacementHelper::RemoveObjectsByCategory("DLC Furniture");
}

Category-Based UI System

void UBuildingWidget::CreateCategoryTabs()
{
    // Use PlacementHelper for easy access
    TArray<FString> Categories = UPlacementHelper::GetAllCategories();
    
    for (const FString& Category : Categories)
    {
        UButton* CategoryTab = CreateWidget<UButton>(this, CategoryTabClass);
        CategoryTab->GetChildAt(0)->SetText(FText::FromString(Category));
        CategoryTab->OnClicked.AddDynamic(this, &UBuildingWidget::ShowCategory);
        
        CategoryContainer->AddChild(CategoryTab);
    }
}
 
void UBuildingWidget::ShowCategory(const FString& CategoryName)
{
    ObjectGrid->ClearChildren();
    
    // Use PlacementHelper for data access
    TArray<FPlaceableObjectData> AllObjects = UPlacementHelper::GetRegisteredObjects();
    
    // Filter by category
    for (const FPlaceableObjectData& ObjectData : AllObjects)
    {
        if (ObjectData.Category == CategoryName)
        {
            UObjectButton* ObjectButton = CreateWidget<UObjectButton>(this, ObjectButtonClass);
            ObjectButton->Setup(ObjectData);
            ObjectButton->OnObjectClicked.AddDynamic(this, &UBuildingWidget::OnObjectSelected);
            
            ObjectGrid->AddChild(ObjectButton);
        }
    }
}
 
void UBuildingWidget::OnObjectSelected(TSubclassOf<AActor> ActorClass)
{
    // Use PlacementHelper for placement
    UPlacementHelper::StartPlacing(ActorClass);
    SetVisibility(ESlateVisibility::Hidden);
}

DataTable Configuration

Creating Multiple PlaceableObjects DataTables

  1. Create DataTable Assets:

    • Right-click in Content Browser → "Miscellaneous" → "Data Table"
    • Select FPlaceableObjectDataTable as Row Structure
    • Create separate tables for organization:
      • "DT_Furniture" - for furniture objects
      • "DT_Lighting" - for lighting objects
      • "DT_Decorations" - for decorative objects
  2. Configure DataTable Rows:

    DT_Furniture:
    Row Name: "chair_office"
    ActorClass: /Game/Furniture/BP_OfficeChair.BP_OfficeChair_C
    DisplayName: "Office Chair"
    Icon: /Game/UI/Icons/T_ChairIcon.T_ChairIcon
    Description: "Ergonomic office seating with adjustable height"
    Category: "Office Furniture"
    
    DT_Lighting:
    Row Name: "desk_lamp"
    ActorClass: /Game/Lighting/BP_DeskLamp.BP_DeskLamp_C
    DisplayName: "Desk Lamp"
    Icon: /Game/UI/Icons/T_LampIcon.T_LampIcon
    Description: "Adjustable LED desk lighting"
    Category: "Lighting"
  3. Assign to Registry:

    • Add all DataTables to the "Object Data Tables" array
    • The registry will automatically load all objects from all tables on BeginPlay

DataTable Loading Process

The registry automatically:

  1. Loads all rows from all assigned DataTables on BeginPlay
  2. Converts FPlaceableObjectDataTable to FPlaceableObjectData
  3. Checks for duplicate ActorClasses and skips them
  4. Fires OnObjectsRegistryChanged event when loading is complete

Performance Considerations

Optimization Tips

Use Multiple Small DataTables:

// Better: Organize by logical groups
Object Data Tables:
- DT_CoreFurniture (20 objects)
- DT_ElectronicDevices (15 objects)  
- DT_Decorations (25 objects)
 
// Instead of: One massive table
- DT_AllObjects (500+ objects)

Error Handling and Validation

Registry Validation

bool ValidateRegistry()
{
    TArray<FPlaceableObjectData> Objects = UPlacementHelper::GetRegisteredObjects();
    
    for (const FPlaceableObjectData& ObjectData : Objects)
    {
        if (!ObjectData.ActorClass)
        {
            UE_LOG(LogTemp, Error, TEXT("Object '%s' has null ActorClass"), *ObjectData.DisplayName);
            return false;
        }
        
        if (!ObjectData.ActorClass->ImplementsInterface(UPlaceableObjectInterface::StaticClass()))
        {
            UE_LOG(LogTemp, Error, TEXT("Object '%s' doesn't implement IPlaceableObjectInterface"), *ObjectData.DisplayName);
            return false;
        }
    }
    
    return true;
}

Safe Registration

void SafeRegisterObject(TSubclassOf<AActor> ActorClass, const FString& DisplayName)
{
    if (!ActorClass)
    {
        UE_LOG(LogTemp, Warning, TEXT("Cannot register null ActorClass"));
        return;
    }
    
    // Check if already registered
    if (UPlacementHelper::IsObjectRegistered(ActorClass))
    {
        UE_LOG(LogTemp, Warning, TEXT("ActorClass already registered: %s"), *DisplayName);
        return;
    }
    
    // Register using PlacementHelper
    UPlacementHelper::RegisterObject(ActorClass, DisplayName);
}

Troubleshooting

Common Issues

"No objects showing in UI"

  • Ensure UPlacementHelper::InitializePlacementSystem() was called first
  • Check that objects are registered via PlacementHelper or DataTables are assigned
  • Verify UPlacementHelper::GetRegisteredObjects() returns data
  • Ensure UI is calling methods after BeginPlay

"Objects not placing"

  • Verify objects implement IPlaceableObjectInterface
  • Check that UPlacementHelper::StartPlacing() returns true
  • Ensure PlacementSystemComponent is on the same actor

"Auto-connection failed"

  • Verify both PlacementObjectRegistry and PlacementSystemComponent are on the same Actor
  • Check that bAutoConnectToPlacementSystem is true
  • Try calling UPlacementHelper::InitializePlacementSystem() manually

"DataTables not loading"

  • Verify DataTable row structure is FPlaceableObjectDataTable
  • Check for null ActorClass entries in DataTable rows
  • Ensure DataTables are assigned to "Object Data Tables" array
  • Look for error logs about duplicate ActorClasses

"Categories not showing correctly"

  • Check for typos in category names (case-sensitive)
  • Use UPlacementHelper::GetAllCategories() to debug available categories
  • Verify objects actually have category assignments

Debug Commands

// Registry debugging using PlacementHelper
void DebugRegistry()
{
    UE_LOG(LogTemp, Log, TEXT("=== Registry Debug ==="));
    UE_LOG(LogTemp, Log, TEXT("Total Objects: %d"), UPlacementHelper::GetObjectCount());
    
    TArray<FString> Categories = UPlacementHelper::GetAllCategories();
    UE_LOG(LogTemp, Log, TEXT("Categories: %d"), Categories.Num());
    
    for (const FString& Category : Categories)
    {
        int32 CategoryCount = 0;
        TArray<FPlaceableObjectData> AllObjects = UPlacementHelper::GetRegisteredObjects();
        
        for (const FPlaceableObjectData& ObjectData : AllObjects)
        {
            if (ObjectData.Category == Category)
            {
                CategoryCount++;
            }
        }
        UE_LOG(LogTemp, Log, TEXT("  %s: %d objects"), *Category, CategoryCount);
    }
}

The PlacementObjectRegistry provides a robust foundation for managing your placeable objects with flexibility for both simple static configurations through PlacementHelper and complex DataTable-based content systems.