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")
};
Type | Description | Surface Requirements |
---|---|---|
Floor | Objects placed on horizontal surfaces | Tagged with "Floor" |
Wall | Objects mounted on vertical surfaces | Tagged with "Wall" |
Roof | Objects attached to ceilings/overheads | Tagged 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;

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
- Open your placeable object Blueprint
- Go to Class Settings

- In "Interfaces" section, click "Add"

- 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
- 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
- Editing Lifecycle:
- Always restore state in
OnEditingCancelled()
- Disable physics/collision during editing
- Clean up visual effects when editing ends
- Always restore state in
- 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
- 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.