// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "Engine/EngineTypes.h" #include "InteractionComponent.generated.h" /** * Diffuse a chaque changement de cible visee. NewFocus vaut nullptr quand le * joueur ne vise plus rien : c'est le signal pour cacher le prompt a l'ecran. */ DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInteractionFocusChanged, AActor*, NewFocus, AActor*, OldFocus); /** * Diffuse quand le TEXTE du prompt change, ce qui n'est pas la meme chose qu'un * changement de cible : un feu de camp affiche un compte a rebours, et le fait * de balayer d'un emplacement a l'autre ne change pas l'acteur vise. S'abonner * uniquement a OnFocusChanged laisserait ces deux cas figes a l'ecran. */ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteractionPromptChanged, const FText&, NewPrompt); /** * A poser sur le pawn du joueur. Lance regulierement un trace depuis le point * de vue de la camera, garde en memoire l'objet IInteractable vise, et declenche * l'interaction a la demande. * * Le composant ne connait aucun type d'objet concret : il ne parle qu'a * l'interface. Tu peux ajouter des portes, des coffres ou des PNJ sans jamais * revenir toucher a ce fichier. */ UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent)) class SURVIVAL_PROJET_API UInteractionComponent : public UActorComponent { GENERATED_BODY() public: UInteractionComponent(); virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; /** Declenche l'interaction sur l'objet vise, s'il y en a un. */ UFUNCTION(BlueprintCallable, Category = "Interaction") void TryInteract(); /** L'acteur actuellement vise, ou nullptr. */ UFUNCTION(BlueprintPure, Category = "Interaction") AActor* GetFocusedActor() const { return FocusedActor.Get(); } /** * Texte a afficher a l'ecran, vide si rien n'est vise. * * Renvoie une valeur DEJA calculee : le prompt est reconstruit une fois par * trace, pas une fois par lecture. Un widget peut donc l'interroger sans * jamais declencher le moindre travail. */ UFUNCTION(BlueprintPure, Category = "Interaction") const FText& GetFocusedPrompt() const { return CachedPrompt; } /** Branche ton widget la-dessus pour afficher / cacher le prompt. */ UPROPERTY(BlueprintAssignable, Category = "Interaction") FOnInteractionFocusChanged OnFocusChanged; /** Le texte affiche a change. C'est a ca qu'un widget de prompt s'abonne. */ UPROPERTY(BlueprintAssignable, Category = "Interaction") FOnInteractionPromptChanged OnPromptChanged; protected: /** Portee du trace en cm. 250 = un peu plus d'un bras et demi. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction", meta = (ClampMin = "1")) float InteractionRange = 250.f; /** * Rayon de la sphere balayee. Un trace en ligne pure oblige a viser au pixel * pres et rend le jeu penible ; quelques cm de tolerance changent tout. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction", meta = (ClampMin = "0")) float TraceRadius = 8.f; /** * Intervalle entre deux traces, en secondes. Inutile de tracer 120 fois par * seconde pour un affichage que l'oeil lit a 20 Hz. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction", meta = (ClampMin = "0")) float TraceInterval = 0.05f; /** Canal de collision teste. Visibility convient dans la majorite des cas. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction") TEnumAsByte TraceChannel = ECC_Visibility; /** Affiche le trace dans le monde, tres utile quand rien ne repond. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction") bool bDrawDebug = false; private: /** Relance le trace et met a jour FocusedActor. */ void UpdateFocus(); /** Vrai si l'acteur implemente l'interface et accepte l'interaction. */ bool IsValidInteractable(AActor* Actor) const; /** Change la cible en notifiant l'ancienne et la nouvelle. */ void SetFocusedActor(AActor* NewFocus); /** Point de vue reel du joueur, camera comprise. */ bool GetViewPoint(FVector& OutLocation, FRotator& OutRotation) const; /** Reconstruit CachedPrompt et diffuse uniquement si le texte a reellement change. */ void RefreshPrompt(); /** Cible courante. TWeakObjectPtr : si l'objet est detruit, on ne garde pas un pointeur mort. */ TWeakObjectPtr FocusedActor; /** * Vrai si on avait une cible au dernier changement diffuse. * Indispensable : quand la cible se detruit (pickup ramasse), FocusedActor * se vide tout seul, et une simple comparaison de pointeurs ne verrait * jamais la transition "j'avais une cible" -> "je n'en ai plus". */ bool bHasFocusedActor = false; /** * Dernier texte diffuse. Le garder permet deux choses : ne reveiller l'UI * que sur un vrai changement, et servir GetFocusedPrompt() sans recalculer. */ FText CachedPrompt; float TimeSinceLastTrace = 0.f; };