Grid Placement System
Core System
PlaceableObjectInterface

PlaceableObjectInterface

The PlaceableObjectInterface is the core interface that makes objects compatible with the Grid Placement System. Any object that implements this interface can be placed, edited, and managed by the placement system.

Overview

This interface defines the contract between your objects and the Grid Placement System. It specifies where objects can be placed (Floor/Wall/Roof), how they respond to editing events, and provides access to directional components.

Key Benefits

  • Universal Compatibility - Any object can become placeable
  • Flexible Placement - Support for Floor, Wall, and Roof placement
  • Event Integration - React to selection, editing, and confirmation events
  • Blueprint Friendly - Fully implementable in Blueprints
  • Directional Support - Integration with DirectionalArrowComponent
  • Lifecycle Management - Proper object state handling

Interface Structure

EPlacementType Enum

Defines where objects can be placed in your level.

UENUM(BlueprintType)
enum class EPlacementType : uint8
{
    Floor       UMETA(DisplayName = "Floor"),
    Wall        UMETA(DisplayName = "Wall"), 
    Roof        UMETA(DisplayName = "Roof")
};
TypeDescriptionSurface Requirements
FloorObjects placed on horizontal surfacesTagged with "Floor"
WallObjects mounted on vertical surfacesTagged with "Wall"
RoofObjects attached to ceilings/overheadsTagged with "Roof"

Interface Functions

All functions are BlueprintNativeEvent, meaning they can be implemented in either C++ or Blueprints.

Function Reference

Core Placement Functions

GetPlacementClass

UFUNCTION(BlueprintNativeEvent, Category = "Placement")
TSubclassOf<AActor> GetPlacementClass() const;

Returns the class that should be used for placement and editing operations.

Default Implementation: Returns nullptr

Usage:

  • Usually returns this->GetClass() or a specific placement variant
  • Allows for different placement vs runtime classes
  • Used by the placement system for spawning

Example:

// C++ Implementation
TSubclassOf<AActor> AMyChair::GetPlacementClass_Implementation() const
{
    return this->GetClass();
}
 
// Blueprint Implementation
// Return: Self Class (or specific placement class)

GetClassPlacementType

UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Placement")
EPlacementType GetClassPlacementType() const;

MOST IMPORTANT FUNCTION
Defines where this object can be placed, and if not specified that will return EPlacementType:Floor

Usage:

  • Determines which surfaces the object can be placed on
  • Controls which grid is shown when object is selected
  • Affects placement validation and snapping behavior

Examples:

// Floor objects (chairs, tables, plants)
EPlacementType AChair::GetClassPlacementType_Implementation() const
{
    return EPlacementType::Floor;
}
 
// Wall objects (pictures, wall lamps, TVs)
EPlacementType AWallPicture::GetClassPlacementType_Implementation() const
{
    return EPlacementType::Wall;
}
 
// Roof objects (ceiling fans, chandeliers, smoke detectors)
EPlacementType ACeilingFan::GetClassPlacementType_Implementation() const
{
    return EPlacementType::Roof;
}

Editing Lifecycle Functions

OnSelectedForEditing

UFUNCTION(BlueprintNativeEvent, Category = "Placement")
void OnSelectedForEditing();

Called when the object is selected for editing (moving, rotating, etc.).

Default Implementation: Empty (no action)

Usage:

  • Hide/disable non-essential components
  • Change visual appearance to indicate selection
  • Prepare object for manipulation

Example:

void AMyObject::OnSelectedForEditing_Implementation()
{
    // Hide collision that might interfere with placement
    if (UPrimitiveComponent* Collision = GetRootComponent<UPrimitiveComponent>())
    {
        Collision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    }
    
    // Add selection outline or effect
    if (UStaticMeshComponent* Mesh = FindComponentByClass<UStaticMeshComponent>())
    {
        Mesh->SetRenderCustomDepth(true);
    }
    
    // Stop any ongoing animations or effects
    StopAllAnimations();
}

OnEditingCancelled

UFUNCTION(BlueprintNativeEvent, Category = "Placement")
void OnEditingCancelled();

Called when editing is cancelled (user pressed cancel or switched objects).

Default Implementation: Empty (no action)

Usage:

  • Restore object to its original state
  • Re-enable disabled components
  • Clean up any editing-specific changes

Example:

void AMyObject::OnEditingCancelled_Implementation()
{
    // Restore collision
    if (UPrimitiveComponent* Collision = GetRootComponent<UPrimitiveComponent>())
    {
        Collision->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    }
    
    // Remove selection effects
    if (UStaticMeshComponent* Mesh = FindComponentByClass<UStaticMeshComponent>())
    {
        Mesh->SetRenderCustomDepth(false);
    }
    
    // Resume normal behavior
    ResumeNormalBehavior();
}

OnEditingConfirmed

UFUNCTION(BlueprintNativeEvent, Category = "Placement")
void OnEditingConfirmed();

Called when editing is confirmed (object placement/movement finalized).

Default Implementation: Empty (no action)

Usage:

  • Finalize object position and state
  • Save new position/rotation if needed
  • Perform any post-placement setup

Example:

void AMyObject::OnEditingConfirmed_Implementation()
{
    // Restore collision
    if (UPrimitiveComponent* Collision = GetRootComponent<UPrimitiveComponent>())
    {
        Collision->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    }
    
    // Remove selection effects
    if (UStaticMeshComponent* Mesh = FindComponentByClass<UStaticMeshComponent>())
    {
        Mesh->SetRenderCustomDepth(false);
    }
    
    // Trigger placement confirmation effects
    PlayPlacementConfirmEffect();
    
    // Save state if needed
    SaveCurrentTransform();
}

Directional Support

GetArrowComponent

UFUNCTION(BlueprintNativeEvent, Category = "Direction")
UDirectionalArrowComponent* GetArrowComponent() const;
RemainingDirectionalArrow

Returns the DirectionalArrowComponent for this object (if any).

Default Implementation: Returns nullptr

Usage:

  • Provides access to the object's directional arrow
  • Used by placement system for visual direction feedback
  • Return nullptr if object doesn't need directional indication

Example:

UDirectionalArrowComponent* AMyChair::GetArrowComponent_Implementation() const
{
    return DirectionalArrowComponent; // Your component reference
}
 
// For objects that don't need direction arrows
UDirectionalArrowComponent* ACube::GetArrowComponent_Implementation() const
{
    return nullptr; // No directional arrow needed
}

Implementation Guide

C++ Implementation

1. Header File Setup

// MyPlaceableObject.h
#include "Interface/PlaceableObjectInterface.h"
#include "Components/DirectionalArrowComponent.h"
 
UCLASS()
class MYGAME_API AMyPlaceableObject : public AActor, public IPlaceableObjectInterface
{
    GENERATED_BODY()
 
public:
    AMyPlaceableObject();
 
protected:
    // DirectionalArrowComponent for direction indication
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    UDirectionalArrowComponent* DirectionalArrow;
 
    // Placement type for this object
    UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Placement")
    EPlacementType PlacementType = EPlacementType::Floor;
 
public:
    // IPlaceableObjectInterface implementation
    virtual TSubclassOf<AActor> GetPlacementClass_Implementation() const override;
    virtual EPlacementType GetClassPlacementType_Implementation() const override;
    virtual void OnSelectedForEditing_Implementation() override;
    virtual void OnEditingCancelled_Implementation() override;
    virtual void OnEditingConfirmed_Implementation() override;
    virtual UDirectionalArrowComponent* GetArrowComponent_Implementation() const override;
};

2. Implementation File

// MyPlaceableObject.cpp
#include "MyPlaceableObject.h"
 
AMyPlaceableObject::AMyPlaceableObject()
{
    PrimaryActorTick.bCanEverTick = false;
 
    // Create root component
    USceneComponent* Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
    SetRootComponent(Root);
 
    // Create directional arrow component
    DirectionalArrow = CreateDefaultSubobject<UDirectionalArrowComponent>(TEXT("DirectionalArrow"));
    DirectionalArrow->SetupAttachment(RootComponent);
}
 
TSubclassOf<AActor> AMyPlaceableObject::GetPlacementClass_Implementation() const
{
    return this->GetClass();
}
 
EPlacementType AMyPlaceableObject::GetClassPlacementType_Implementation() const
{
    return PlacementType; // Use the configured placement type
}
 
void AMyPlaceableObject::OnSelectedForEditing_Implementation()
{
    // Disable collision during editing
    if (UPrimitiveComponent* RootPrim = Cast<UPrimitiveComponent>(GetRootComponent()))
    {
        RootPrim->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    }
}
 
void AMyPlaceableObject::OnEditingCancelled_Implementation()
{
    // Restore collision
    if (UPrimitiveComponent* RootPrim = Cast<UPrimitiveComponent>(GetRootComponent()))
    {
        RootPrim->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    }
}
 
void AMyPlaceableObject::OnEditingConfirmed_Implementation()
{
    // Restore collision and finalize placement
    if (UPrimitiveComponent* RootPrim = Cast<UPrimitiveComponent>(GetRootComponent()))
    {
        RootPrim->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    }
}
 
UDirectionalArrowComponent* AMyPlaceableObject::GetArrowComponent_Implementation() const
{
    return DirectionalArrow;
}

Blueprint Implementation

1. Class Settings

  1. Open your placeable object Blueprint
  2. Go to Class Settings
InterfaceClassSettings
  1. In "Interfaces" section, click "Add"
Implement PlaceableObjectInterface
  1. Select "Placeable Object Interface"

2. Implement Interface Functions

Get Class Placement Type:

Event: Get Class Placement Type
Return: Floor (or Wall/Roof depending on your object)

Get Placement Class:

Event: Get Placement Class  
Return: Get Class (returns this Blueprint's class)

Get Arrow Component:

Event: Get Arrow Component
Return: [Reference to your DirectionalArrowComponent]
// Or return None if no arrow needed

Below are optional lifecycle events you can implement to manage object state during editing. Implement these as needed for your specific object behavior.

On Selected For Editing:

Event: On Selected For Editing
Actions:
- Set Collision Enabled (Root Component, No Collision)
- Set Render Custom Depth (Mesh Component, True)

On Editing Cancelled:

Event: On Editing Cancelled  
Actions:
- Set Collision Enabled (Root Component, Query and Physics)
- Set Render Custom Depth (Mesh Component, False)

On Editing Confirmed:

Event: On Editing Confirmed
Actions:
- Set Collision Enabled (Root Component, Query and Physics)  
- Set Render Custom Depth (Mesh Component, False)
- [Any placement confirmation logic]

Troubleshooting

Common Issues

"Object doesn't appear in placement system"

  • Verify the class implements IPlaceableObjectInterface
  • Check that GetPlacementClass() returns a valid class
  • Ensure object is registered with PlacementHelper in Registry

"Object places on wrong surface type"

  • Check GetClassPlacementType() returns correct value
  • Verify level surfaces are tagged correctly ("Floor", "Wall", "Roof")
  • Ensure placement type matches available surfaces

"Arrow doesn't show during placement"

  • Verify GetArrowComponent() returns valid component reference
  • Check that DirectionalArrowComponent is properly configured
  • Ensure arrow is positioned correctly relative to object

"Object behavior doesn't work during editing"

  • Check that editing lifecycle functions are implemented
  • Verify collision and physics state management
  • Test that state is properly restored on cancel/confirm

Implementation Guidelines

  1. Placement Type Consistency:
    • Choose placement type based on object's natural usage
    • Floor: Furniture, plants, standalone items
    • Wall: Mounted items, decorations, picture
    • Roof: Hanging items, Lamps, ceiling fans

  1. Editing Lifecycle:
    • Always restore state in OnEditingCancelled()
    • Disable physics/collision during editing
    • Clean up visual effects when editing ends

  1. Directional Arrows:
    • Use arrows for objects with clear orientation (chairs, TVs)
    • Skip arrows for symmetric objects (tables, spheres)
    • Position arrows beyond object bounds for visibility

  1. Performance:
    • Keep editing lifecycle functions lightweight
    • Avoid heavy operations in interface functions
    • Cache computed values when possible

The PlaceableObjectInterface is the foundation that makes objects compatible with the Grid Placement System, providing a clean contract for placement behavior and lifecycle management.