ed7c7fd991
Boucle de jeu complète : récolte, inventaire, consommation, stats de survie, mort et respawn. Gameplay en C++, Blueprints réservés au câblage d'assets. Assets binaires (.uasset, .umap, textures, audio) suivis via Git LFS. Binaries/, Intermediate/, Saved/ et DerivedDataCache/ sont ignorés : régénérés au build, ils pèsent 3 Go pour 1,3 Go de contenu utile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
134 lines
3.8 KiB
C++
134 lines
3.8 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->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<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
|