Grid Placement System
Library Components
PlacementCameraController

PlacementCameraController

The PlacementCameraController is a specialized library component that manages camera behavior during placement mode. It provides intelligent camera setup detection, temporary SpringArm creation for First-Person projects, and comprehensive camera controls including zoom, movement, and constraint systems.

Overview

PlacementCameraController automatically adapts to both Third-Person and First-Person camera setups, providing a consistent placement experience regardless of the project's camera architecture. For FPP projects, it temporarily creates a SpringArm component during placement mode and seamlessly restores the original setup when exiting.

Key Features

  • Automatic Setup Detection - Detects TPP vs FPP camera configurations
  • Temporary SpringArm Creation - Creates SpringArm for FPP projects during placement
  • Camera Movement Controls - WASD movement with configurable speed
  • Zoom System - Smooth zoom with distance constraints
  • Vertical Movement - Q/E keys for up/down camera movement
  • Pitch Constraints - Prevents camera flipping with configurable limits
  • Collision Management - Can disable camera collision during placement
  • State Restoration - Perfectly restores original camera setup

Core Methods

Initialization

Initialize

void Initialize(USpringArmComponent* SpringArm, UCameraComponent* Camera);

Initializes the camera controller with existing camera components.

Parameters:

  • SpringArm - Existing SpringArm component (null for FPP setups)
  • Camera - The camera component to control

Detection Logic:

  • If SpringArm exists: Third-Person setup detected
  • If SpringArm is null: First-Person setup detected

State Management

StoreCurrentState

void StoreCurrentState();

Stores the current camera/SpringArm state for later restoration.

Stored Values:

  • SpringArm length and rotation
  • SpringArm location and target offset
  • Collision settings and probe size

RestoreStoredState

void RestoreStoredState();

Restores the camera to its original state.

For TPP: Restores SpringArm properties For FPP: Destroys temporary SpringArm and restores original camera attachment

PrepareForPlacementMode

void PrepareForPlacementMode();

Prepares the camera system for placement mode.

For TPP: Stores current state For FPP: Creates temporary SpringArm for placement controls

First-Person Support

DetectFPPSetup

bool DetectFPPSetup(UCameraComponent* Camera) const;

Detects if the camera is in a First-Person configuration.

Detection Criteria:

  • Camera is not attached to a SpringArm component
  • Camera is directly attached to character or other scene component

CreateTemporarySpringArmForPlacement

void CreateTemporarySpringArmForPlacement();

Creates a temporary SpringArm for FPP projects during placement mode.

Process:

  1. Stores original camera parent and transform
  2. Creates new SpringArm component with placement-optimized settings
  3. Attaches SpringArm to original camera parent
  4. Reattaches camera to temporary SpringArm
  5. Stores initial state for placement mode

DestroyTemporarySpringArm

void DestroyTemporarySpringArm();

Removes temporary SpringArm and restores original FPP setup.

Movement Controls

HandleZoom

void HandleZoom(const FInputActionValue& Value);

Handles camera zoom input (mouse wheel).

Parameters:

  • Value - Scroll wheel input value

Behavior:

  • Adjusts SpringArm TargetArmLength
  • Respects min/max zoom distance limits
  • Smooth scaling based on zoom speed

HandleMove

void HandleMove(const FInputActionValue& Value, UWorld* World);

Handles camera movement input (WASD).

Parameters:

  • Value - 2D movement vector from input
  • World - World context for delta time

Movement Logic:

  • Forward/backward based on camera forward vector (Z ignored)
  • Left/right based on camera right vector (Z ignored)
  • Uses SpringArm TargetOffset for persistent positioning

HandlePlacementVertical

void HandlePlacementVertical(const FInputActionValue& Value, UWorld* World);

Handles vertical camera movement (Q/E keys).

Parameters:

  • Value - Vertical input value
  • World - World context for delta time

Constraints:

  • Respects VerticalMinOffsetZ and VerticalMaxOffsetZ limits
  • Smooth movement based on camera movement speed

Constraint System

ConstrainCameraPitch

void ConstrainCameraPitch(APlayerController* PC);

Constrains camera pitch to prevent flipping.

Parameters:

  • PC - Player controller to constrain

Constraint Logic:

  • Clamps pitch between MinCameraPitch and MaxCameraPitch
  • Handles 360° pitch wrap-around correctly
  • Only applies corrections when pitch exceeds limits

Collision Management

DisableCameraCollision

void DisableCameraCollision();

Disables camera collision during placement mode.

Effects:

  • Sets bDoCollisionTest = false
  • Sets ProbeSize = 0.0f

RestoreCameraCollision

void RestoreCameraCollision();

Restores original camera collision settings.

Settings Configuration

Camera Movement Settings

void SetZoomSpeed(float Speed);
void SetMinZoomDistance(float Distance);
void SetMaxZoomDistance(float Distance);
void SetCameraMovementSpeed(float Speed);

Collision Settings

void SetDisableCollisionInPlacementMode(bool bDisable);

Constraint Settings

void SetEnableCameraPitchConstraint(bool bEnable);
void SetMinCameraPitch(float Pitch);
void SetMaxCameraPitch(float Pitch);
void SetVerticalOffsetLimits(float MinZ, float MaxZ);

Camera Setup Detection

Third-Person Detection

// TPP Setup: SpringArm component exists
SpringArmComponent = SpringArm;
bIsFPPSetup = false;
UE_LOG(LogTemp, Log, TEXT("PlacementCameraController: TPP setup detected"));

First-Person Detection

// FPP Setup: No SpringArm, camera directly attached
bIsFPPSetup = DetectFPPSetup(Camera);
SpringArmComponent = nullptr;
 
if (bIsFPPSetup)
{
    UE_LOG(LogTemp, Log, TEXT("PlacementCameraController: FPP setup detected - Spring Arm will be created when entering placement mode"));
}

Temporary SpringArm System

Configuration for Placement Mode

// Temporary SpringArm settings optimized for placement
SpringArmComponent->TargetArmLength = 400.0f;
SpringArmComponent->bUsePawnControlRotation = true;
SpringArmComponent->bInheritPitch = true;
SpringArmComponent->bInheritYaw = true;
SpringArmComponent->bInheritRoll = false;
SpringArmComponent->bDoCollisionTest = true;
SpringArmComponent->ProbeSize = 12.0f;
SpringArmComponent->TargetOffset = FVector::ZeroVector;

State Preservation

// Store original FPP state
OriginalCameraParent = CameraComponent->GetAttachParent();
OriginalCameraTransform = CameraComponent->GetRelativeTransform();

Integration with PlacementSystemComponent

Automatic Configuration

// In PlacementSystemComponent::ConfigureComponents()
if (PlacementCameraController)
{
    PlacementCameraController->SetZoomSpeed(ZoomSpeed);
    PlacementCameraController->SetMinZoomDistance(MinZoomDistance);
    PlacementCameraController->SetMaxZoomDistance(MaxZoomDistance);
    PlacementCameraController->SetCameraMovementSpeed(CameraMovementSpeed);
    PlacementCameraController->SetDisableCollisionInPlacementMode(bDisableCollisionInPlacementMode);
    PlacementCameraController->SetEnableCameraPitchConstraint(bEnableCameraPitchConstraint);
    PlacementCameraController->SetMinCameraPitch(MinCameraPitch);
    PlacementCameraController->SetMaxCameraPitch(MaxCameraPitch);
    PlacementCameraController->SetVerticalOffsetLimits(VerticalMinOffsetZ, VerticalMaxOffsetZ);
}

Mode Transitions

// Entering placement mode
if (PlacementCameraController)
{
    PlacementCameraController->PrepareForPlacementMode();
    PlacementCameraController->DisableCameraCollision();
}
 
// Exiting placement mode
if (PlacementCameraController)
{
    PlacementCameraController->RestoreStoredState();
}

Input Handling

// In PlacementSystemComponent input handlers
if (PlacementCameraController)
{
    PlacementCameraController->HandleZoom(Value);
    PlacementCameraController->HandleMove(Value, GetWorld());
    PlacementCameraController->HandlePlacementVertical(Value, GetWorld());
    PlacementCameraController->ConstrainCameraPitch(PC);
}

State Storage Structure

FPlacementSystemState

USTRUCT(BlueprintType)
struct FPlacementSystemState
{
    GENERATED_BODY()
 
    float SpringArmLength = 0.0f;
    FRotator SpringArmRotation = FRotator::ZeroRotator;
    FVector SpringArmLocation = FVector::ZeroVector;
    FVector TargetOffset = FVector::ZeroVector;
    bool bWasCollisionEnabled = true;
    float OriginalProbeSize = 12.0f;
};

Usage Examples

Basic Setup Detection

// Initialize with automatic detection
PlacementCameraController->Initialize(ExistingSpringArm, CameraComponent);
 
// Check detected setup
if (PlacementCameraController->HasValidSpringArm())
{
    UE_LOG(LogTemp, Log, TEXT("Third-Person setup ready"));
}
else
{
    UE_LOG(LogTemp, Log, TEXT("First-Person setup - temporary SpringArm will be created"));
}

Manual Camera Control

// Programmatic camera adjustment
void SetCameraDistance(float Distance)
{
    if (PlacementCameraController->HasValidSpringArm())
    {
        // Clamp to configured limits
        float ClampedDistance = FMath::Clamp(Distance, MinZoomDistance, MaxZoomDistance);
        
        // Apply through zoom system
        FInputActionValue ZoomValue;
        float ZoomDelta = (ClampedDistance - CurrentDistance) / ZoomSpeed;
        ZoomValue.Set<float>(ZoomDelta);
        
        PlacementCameraController->HandleZoom(ZoomValue);
    }
}

Custom Movement Implementation

// Custom movement with validation
void MoveCamera(FVector2D MovementInput)
{
    if (!PlacementCameraController) return;
    
    // Validate movement input
    if (MovementInput.SizeSquared() <= 0.0f) return;
    
    // Create input action value
    FInputActionValue MoveValue;
    MoveValue.Set<FVector2D>(MovementInput);
    
    // Apply movement
    PlacementCameraController->HandleMove(MoveValue, GetWorld());
}

Error Handling

Validation Methods

bool ValidateCameraSetup()
{
    if (!PlacementCameraController)
    {
        UE_LOG(LogTemp, Error, TEXT("PlacementCameraController is null"));
        return false;
    }
    
    if (!CameraComponent)
    {
        UE_LOG(LogTemp, Error, TEXT("CameraComponent is null"));
        return false;
    }
    
    // For FPP, SpringArm will be created automatically
    // For TPP, SpringArm should exist
    if (!PlacementCameraController->HasValidSpringArm() && !bIsFPPSetup)
    {
        UE_LOG(LogTemp, Warning, TEXT("No SpringArm found for TPP setup"));
        return false;
    }
    
    return true;
}

Safe State Restoration

void SafeRestoreCamera()
{
    if (PlacementCameraController)
    {
        try
        {
            PlacementCameraController->RestoreStoredState();
            UE_LOG(LogTemp, Log, TEXT("Camera state restored successfully"));
        }
        catch (...)
        {
            UE_LOG(LogTemp, Error, TEXT("Failed to restore camera state"));
            
            // Fallback: Try to restore basic functionality
            if (bIsFPPSetup)
            {
                PlacementCameraController->DestroyTemporarySpringArm();
            }
        }
    }
}

Performance Considerations

Efficient Updates

// Only update when camera is actively moving
void TickCameraController(float DeltaTime)
{
    // Skip expensive operations when not needed
    if (!bCameraMovementActive) return;
    
    // Batch constraint checks
    if (bEnableCameraPitchConstraint && GetWorld()->GetTimeSeconds() - LastConstraintCheck > 0.1f)
    {
        PlacementCameraController->ConstrainCameraPitch(PlayerController);
        LastConstraintCheck = GetWorld()->GetTimeSeconds();
    }
}

Memory Management

// Proper cleanup for temporary SpringArm
void CleanupTemporaryComponents()
{
    if (bCreatedTemporarySpringArm && SpringArmComponent)
    {
        // Restore camera first
        if (CameraComponent && OriginalCameraParent)
        {
            CameraComponent->AttachToComponent(OriginalCameraParent, 
                FAttachmentTransformRules::KeepWorldTransform);
            CameraComponent->SetRelativeTransform(OriginalCameraTransform);
        }
        
        // Then destroy SpringArm
        SpringArmComponent->DestroyComponent();
        SpringArmComponent = nullptr;
        bCreatedTemporarySpringArm = false;
    }
}

Troubleshooting

Common Issues

"Camera not responding to input"

  • Verify PlacementCameraController is initialized
  • Check that input actions are properly bound
  • Ensure camera setup detection completed successfully

"FPP camera stuck after placement mode"

  • Check that RestoreStoredState() is called when exiting placement
  • Verify original camera parent and transform are stored correctly
  • Ensure temporary SpringArm is properly destroyed

"Camera constraints not working"

  • Verify bEnableCameraPitchConstraint is true
  • Check that MinCameraPitch and MaxCameraPitch are set correctly
  • Ensure ConstrainCameraPitch is called regularly (in tick)

"Zoom limits not respected"

  • Check MinZoomDistance and MaxZoomDistance settings
  • Verify zoom speed is reasonable (not zero or negative)
  • Ensure SpringArm component exists and is valid

Debug Commands

// Debug camera state
void DebugCameraController()
{
    if (!PlacementCameraController) return;
    
    UE_LOG(LogTemp, Log, TEXT("=== Camera Controller Debug ==="));
    UE_LOG(LogTemp, Log, TEXT("Has Valid SpringArm: %s"), 
           PlacementCameraController->HasValidSpringArm() ? TEXT("Yes") : TEXT("No"));
    
    if (SpringArmComponent)
    {
        UE_LOG(LogTemp, Log, TEXT("SpringArm Length: %.1f"), SpringArmComponent->TargetArmLength);
        UE_LOG(LogTemp, Log, TEXT("SpringArm Offset: %s"), *SpringArmComponent->TargetOffset.ToString());
        UE_LOG(LogTemp, Log, TEXT("Collision Enabled: %s"), 
               SpringArmComponent->bDoCollisionTest ? TEXT("Yes") : TEXT("No"));
    }
    
    if (CameraComponent)
    {
        UE_LOG(LogTemp, Log, TEXT("Camera Location: %s"), *CameraComponent->GetComponentLocation().ToString());
        UE_LOG(LogTemp, Log, TEXT("Camera Rotation: %s"), *CameraComponent->GetComponentRotation().ToString());
    }
    
    UE_LOG(LogTemp, Log, TEXT("FPP Setup: %s"), bIsFPPSetup ? TEXT("Yes") : TEXT("No"));
    UE_LOG(LogTemp, Log, TEXT("Temporary SpringArm Created: %s"), 
           bCreatedTemporarySpringArm ? TEXT("Yes") : TEXT("No"));
}

PlacementCameraController provides intelligent camera management that seamlessly adapts to any camera setup while providing comprehensive placement controls and reliable state restoration.