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:
- Stores original camera parent and transform
- Creates new SpringArm component with placement-optimized settings
- Attaches SpringArm to original camera parent
- Reattaches camera to temporary SpringArm
- 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 inputWorld
- 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 valueWorld
- 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.