Last active
December 23, 2025 12:52
-
-
Save josiest/789d7db8a63ce5d6ecd0abbb0494b7e1 to your computer and use it in GitHub Desktop.
Unreal Camera Facing Widget Component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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 | |
| }; |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.csfiles.