Files
Unreal_EmberWild/Source/EmberWild/Private/InteractionPromptWidget.cpp
T

137 lines
4.0 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "InteractionPromptWidget.h"
#include "Components/TextBlock.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/LocalPlayer.h"
#include "FpsPlayer.h"
#include "GameFramework/PlayerController.h"
#include "InteractionComponent.h"
#define LOCTEXT_NAMESPACE "InteractionPrompt"
UInteractionPromptWidget::UInteractionPromptWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PromptFormat = LOCTEXT("PromptFormat", "[{0}] {1}");
}
void UInteractionPromptWidget::NativeConstruct()
{
Super::NativeConstruct();
// NativeConstruct est l'equivalent de BeginPlay pour un widget : il est
// appele une fois que le widget est ajoute au viewport.
BindToOwningPawn();
}
void UInteractionPromptWidget::BindToOwningPawn()
{
const APlayerController* OwningController = GetOwningPlayer();
const AFpsPlayer* Player = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
UInteractionComponent* NewComponent = Player ? Player->GetInteractionComponent() : nullptr;
if (BoundComponent.Get() == NewComponent)
{
return;
}
if (BoundComponent.IsValid())
{
BoundComponent->OnPromptChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandlePromptChanged);
}
BoundComponent = NewComponent;
if (BoundComponent.IsValid())
{
// Abonnement au delegue : le widget ne se reveille que quand le texte
// change vraiment. Pas de Tick, pas de polling.
BoundComponent->OnPromptChanged.AddDynamic(this, &UInteractionPromptWidget::HandlePromptChanged);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("InteractionPromptWidget : le pawn possede n'a pas de UInteractionComponent, le prompt restera cache."));
}
Refresh();
}
void UInteractionPromptWidget::NativeDestruct()
{
// Desabonnement systematique. Sans ca, le delegue garderait une reference
// vers un widget detruit -- typiquement au changement de niveau.
if (BoundComponent.IsValid())
{
BoundComponent->OnPromptChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandlePromptChanged);
}
BoundComponent.Reset();
Super::NativeDestruct();
}
void UInteractionPromptWidget::HandlePromptChanged(const FText& NewPrompt)
{
Refresh();
}
void UInteractionPromptWidget::Refresh()
{
const FText Prompt = BoundComponent.IsValid() ? BoundComponent->GetFocusedPrompt() : FText::GetEmpty();
if (Prompt.IsEmpty())
{
// Collapsed et non Hidden : Hidden garde la place reservee dans la
// mise en page, Collapsed la libere completement.
SetVisibility(ESlateVisibility::Collapsed);
return;
}
if (PromptText)
{
// Un prompt non vide est toujours une action realisable : les cibles ne
// renvoient jamais de texte purement informatif, donc la touche a
// toujours sa place devant.
PromptText->SetText(FText::Format(PromptFormat, GetInteractKeyDisplayName(), Prompt));
}
// HitTestInvisible : le prompt s'affiche mais ne capture pas la souris,
// sinon il volerait les clics destines au jeu.
SetVisibility(ESlateVisibility::HitTestInvisible);
}
FText UInteractionPromptWidget::GetInteractKeyDisplayName() const
{
const APlayerController* OwningController = GetOwningPlayer();
const AFpsPlayer* Player = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
if (!Player)
{
return FText::GetEmpty();
}
const UInputAction* Action = Player->GetInteractAction();
if (!Action)
{
return FText::GetEmpty();
}
// On demande au sous-systeme Enhanced Input quelle touche est REELLEMENT
// mappee sur l'action. Si tu changes E en F dans IMC_Default, ou si le
// joueur remappe ses touches un jour, le texte suit tout seul.
if (const UEnhancedInputLocalPlayerSubsystem* Subsystem =
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(OwningController->GetLocalPlayer()))
{
const TArray<FKey> Keys = Subsystem->QueryKeysMappedToAction(Action);
if (Keys.Num() > 0)
{
return Keys[0].GetDisplayName(/*bLongDisplayName=*/false);
}
}
return FText::GetEmpty();
}
#undef LOCTEXT_NAMESPACE