Skip to content

Instantly share code, notes, and snippets.

@josiest
Last active December 23, 2025 12:52
Show Gist options
  • Select an option

  • Save josiest/789d7db8a63ce5d6ecd0abbb0494b7e1 to your computer and use it in GitHub Desktop.

Select an option

Save josiest/789d7db8a63ce5d6ecd0abbb0494b7e1 to your computer and use it in GitHub Desktop.
Unreal Camera Facing Widget Component
#include "UI/CameraFacingWidgetComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Camera/PlayerCameraManager.h"
#if WITH_EDITOR
#include "Editor.h"
#include "LevelEditor.h"
#include "LevelEditorViewport.h"
#endif
// Public Interface
void UCameraFacingWidgetComponent::OrientToFaceRotation(const FRotator& Rotation)
{
const FQuat TurnAround = FQuat::MakeFromEuler(FVector{0.0f, 0.0f, 180.0f});
SetWorldRotation(FQuat(Rotation) * TurnAround);
}
void UCameraFacingWidgetComponent::OrientToPlayerCamera()
{
// If this widget isn't attached to an object in a level (on the CDO for example) it isn't a valid world context
// object. Camera Manager will be null in those cases, but we only care about orienting widgets placed in the world
// anyway.
if (const APlayerCameraManager * CameraManager = UGameplayStatics::GetPlayerCameraManager(this, 0))
{
OrientToFaceRotation(CameraManager->GetCameraRotation());
}
else if (!GetWorld())
{
UE_LOG(LogTemp, Error, TEXT("Tried to orient %s but it's not placed in a level"), *GetReadableName());
}
}
#if WITH_EDITOR
void UCameraFacingWidgetComponent::OrientToEditorViewport()
{
if (GCurrentLevelEditingViewportClient)
{
OrientToFaceRotation(GCurrentLevelEditingViewportClient->GetViewRotation());
}
else
{
UE_LOG(LogTemp, Error, TEXT("Viewport for %s not available"), *GetReadableName());
}
}
#endif
// Internal Interface
#if WITH_EDITOR
void UCameraFacingWidgetComponent::BindEditorCallbacks()
{
FEditorDelegates::OnEditorCameraMoved.AddUObject(this, &ThisClass::OnCameraEditorMoved);
// When we eject from the player controller while playing in editor, the editor witches from "Play In Editor" to
// "Simulate In Editor" mode. When we repossess the player controller we'll need to re-orient the widgets to the
// player's camera.
FEditorDelegates::OnSwitchBeginPIEAndSIE.AddUObject(this, &ThisClass::OnToggleSIE);
// In addition to orienting widgets when the editor camera moves, we'll want to orient them as soon as the
// editor is ready. This event is exactly what we need: it'll trigger after the editor viewport is initialized.
// Note: FEditorDelegates::OnEditorInitialized won't work because it's triggered before the viewport is created
auto & LevelEditorModule = FModuleManager::Get().GetModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditorModule.OnTabContentChanged().AddUObject(this, &ThisClass::OrientToEditorViewport);
}
void UCameraFacingWidgetComponent::UnbindEditorCallbacks() const
{
FEditorDelegates::OnEditorCameraMoved.RemoveAll(this);
FEditorDelegates::OnSwitchBeginPIEAndSIE.RemoveAll(this);
auto & LevelEditorModule = FModuleManager::Get().GetModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditorModule.OnTabContentChanged().RemoveAll(this);
}
#endif
// UObject Interface
#if WITH_EDITOR
void UCameraFacingWidgetComponent::PostDuplicate(bool bDuplicateForPIE)
{
Super::PostDuplicate(bDuplicateForPIE);
// We don't want to bind editor callbacks if we're PIE in a new window. However, the world context might be invalid
// at this point, which is necessary to check if we're PIE in a new window. So instead, defer binding callbacks for
// PIE duplicates to BeginPlay
if (IsDuplicateForPIE = bDuplicateForPIE; !IsDuplicateForPIE)
{
BindEditorCallbacks();
}
}
#endif
void UCameraFacingWidgetComponent::BeginDestroy()
{
#if WITH_EDITOR
UnbindEditorCallbacks();
#endif
Super::BeginDestroy();
}
// Actor Component Interface
FString UCameraFacingWidgetComponent::GetReadableName() const
{
#if WITH_EDITOR
return Super::GetReadableName() + (IsDuplicateForPIE? " PIE Duplicate" : "");
#else
return Super::GetReadableName();
#endif
}
void UCameraFacingWidgetComponent::BeginPlay()
{
Super::BeginPlay();
OrientToPlayerCamera();
#if WITH_EDITOR
if (IsDuplicateForPIE && !GEditor->WorldIsPIEInNewViewport(GetWorld()))
{
BindEditorCallbacks();
}
#endif
}
void UCameraFacingWidgetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
#if WITH_EDITOR
// If we're PIE in the editor viewport but detached from the player controller, don't orient to the player camera on
// tick: we want the widgets to orient to the editor viewport camera instead.
if (!GEditor->IsSimulatingInEditor() || GEditor->WorldIsPIEInNewViewport(GetWorld()))
{
OrientToPlayerCamera();
}
#else
OrientToPlayerCamera();
#endif
}
// Event Handlers
#if WITH_EDITOR
void UCameraFacingWidgetComponent::OnCameraEditorMoved(const FVector& Location, const FRotator& Rotation,
ELevelViewportType ViewportType, int32 ViewIndex)
{
OrientToFaceRotation(Rotation);
}
void UCameraFacingWidgetComponent::OnToggleSIE(bool IsSimulatingInEditor)
{
if (IsSimulatingInEditor)
{
OrientToEditorViewport();
}
else
{
OrientToPlayerCamera();
}
}
#endif
#pragma once
#include "Components/WidgetComponent.h"
#include "CameraFacingWidgetComponent.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MY_GAME_API UCameraFacingWidgetComponent : public UWidgetComponent
{
GENERATED_BODY()
// Public Interface
public:
/** Orient the widget to face the opposite direction described by Rotation **/
UFUNCTION(BlueprintCallable)
void OrientToFaceRotation(const FRotator& Rotation);
/** Orient the widget to face the player's camera */
UFUNCTION(BlueprintCallable)
void OrientToPlayerCamera();
#if WITH_EDITOR
/** Orient the widget to face the editor viewport camera */
UFUNCTION(BlueprintCallable)
void OrientToEditorViewport();
#endif
// Internal Interface
private:
#if WITH_EDITOR
bool IsDuplicateForPIE = false;
void BindEditorCallbacks();
void UnbindEditorCallbacks() const;
#endif
// UObject Interface
public:
#if WITH_EDITOR
virtual void PostDuplicate(bool bDuplicateForPIE) override;
#endif
virtual void BeginDestroy() override;
// Actor Component Interface
public:
virtual FString GetReadableName() const override;
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
// Event Handlers
private:
#if WITH_EDITOR
void OnCameraEditorMoved(const FVector& Location, const FRotator& Rotation,
ELevelViewportType ViewportType, int32 ViewIndex);
void OnToggleSIE(bool IsSimulatingInEditor);
#endif
};
@josiest
Copy link
Author

josiest commented Dec 22, 2025

Camera Facing Widget Component

A simple widget component extension that orients widgets in world space to face toward the camera. While playing in editor, this means orienting toward the player camera. While not playing in editor, or while simulating in editor, this means orienting toward the editor viewport camera.

Linking

In order to link the code properly, make sure to add "UMG" and "UnrealEd" to the appropriate dependency module lists in your .Build.cs files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment