// 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(OwningController->GetPawn()) : nullptr; UInteractionComponent* NewComponent = Player ? Player->GetInteractionComponent() : nullptr; if (BoundComponent.Get() == NewComponent) { return; } if (BoundComponent.IsValid()) { BoundComponent->OnFocusChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandleFocusChanged); } BoundComponent = NewComponent; if (BoundComponent.IsValid()) { // Abonnement au delegue : le widget ne se reveille que quand la cible // change vraiment. Pas de Tick, pas de polling. BoundComponent->OnFocusChanged.AddDynamic(this, &UInteractionPromptWidget::HandleFocusChanged); } 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->OnFocusChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandleFocusChanged); } BoundComponent.Reset(); Super::NativeDestruct(); } void UInteractionPromptWidget::HandleFocusChanged(AActor* NewFocus, AActor* OldFocus) { 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) { 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(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(OwningController->GetLocalPlayer())) { const TArray Keys = Subsystem->QueryKeysMappedToAction(Action); if (Keys.Num() > 0) { return Keys[0].GetDisplayName(/*bLongDisplayName=*/false); } } return FText::GetEmpty(); } #undef LOCTEXT_NAMESPACE