// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Interactable.h" #include "PickupItem.generated.h" class UBoxComponent; class UItemDataAsset; class UStaticMeshComponent; /** * Un objet pose dans le monde. Il ne DECRIT pas l'objet, il ne fait que * pointer vers son UItemDataAsset et porter une quantite. * * Consequence pratique : pour creer un nouveau type de ramassable, tu n'as * plus besoin d'un Blueprint par objet. Un seul BP_Pickup suffit, tu changes * juste l'ItemData sur chaque instance posee dans le niveau. */ UCLASS() class EMBERWILD_API APickupItem : public AActor, public IInteractable { GENERATED_BODY() public: APickupItem(); virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; /** * Configure un pickup engendre a l'execution (objet jete au sol). * A appeler entre SpawnActorDeferred() et FinishSpawning() pour que * OnConstruction ait deja les bonnes donnees et pose le bon mesh. * * A n'appeler que sur le SERVEUR : c'est lui qui engendre les acteurs du * monde, les clients recoivent l'acteur deja configure par replication. */ UFUNCTION(BlueprintCallable, Category = "Item") void InitPickup(UItemDataAsset* InItemData, int32 InQuantity, int32 InRemainingUses = -1); // --- IInteractable --- virtual FText GetInteractionPrompt_Implementation(AActor* Interactor) const override; virtual bool CanInteract_Implementation(AActor* Interactor) const override; virtual void Interact_Implementation(AActor* Interactor) override; virtual void OnBeginFocus_Implementation() override; virtual void OnEndFocus_Implementation() override; protected: /** * Sans garde WITH_EDITOR : on veut aussi que le mesh se pose correctement * sur les pickups engendres a l'execution quand on jette un objet. */ virtual void OnConstruction(const FTransform& Transform) override; /** Visuel uniquement, sans collision. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item") TObjectPtr Mesh; /** * Volume que le trace d'interaction vient toucher. * * Volontairement separe du mesh : beaucoup de meshes d'asset packs, en * particulier la vegetation, n'ont aucune geometrie de collision. Avec un * volume dedie, le pickup fonctionne quel que soit le visuel. * * Une BOITE et non une sphere, contrairement a la premiere version : la * sphere englobante d'un objet allonge a pour rayon sa DEMI-DIAGONALE, ce qui * est le pire cas geometrique possible. Une branche de 120x20x15 donnait une * sphere de 62 cm de rayon, soit vingt-sept fois le volume de l'objet. Le * symptome n'etait pas cosmetique : deux pickups poses cote a cote voyaient * leurs spheres se chevaucher, et le trace retenait le premier volume touche * plutot que l'objet reellement vise. * * Elle epouse toute seule le mesh pose (voir FitInteractionBox) : un seul * BP_Pickup sert des centaines d'objets, une taille fixe serait forcement * fausse pour la plupart. * * Contrairement a celle du coffre, elle ne bloque QUE le canal Visibility : * un objet au sol se traverse, il ne barre pas le passage. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item") TObjectPtr InteractionBox; /** * Quel objet ce pickup represente. A assigner sur chaque instance. * * ReplicatedUsing et non simplement Replicated : chez un client, l'acteur * est construit AVANT que ses proprietes arrivent. OnConstruction s'execute * donc avec ItemData a nullptr et ne pose aucun mesh -- l'objet resterait * invisible pour tout le monde sauf l'hote. C'est OnRep qui repose le visuel * au moment ou la donnee arrive vraiment. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, ReplicatedUsing = OnRep_ItemData, Category = "Item") TObjectPtr ItemData; /** * Combien d'exemplaires ce tas contient. * Replique parce que le prompt l'affiche ("Ramasser Branche x3") et qu'un * ramassage partiel, inventaire plein, le fait diminuer sous les yeux de tous. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Item") int32 Quantity = 1; /** * Charges restantes pour un objet a usages multiples. * -1 = plein, ce qui est le cas de tout objet pose a la main dans le niveau. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "Item") int32 RemainingUses = -1; /** Repose le mesh quand la donnee de l'objet arrive chez un client. */ UFUNCTION() void OnRep_ItemData(); /** * Applique le visuel decrit par l'ItemData. Partage par OnConstruction et * OnRep_ItemData : deux copies finiraient par diverger, et le symptome * serait un objet correct dans l'editeur mais nu en jeu. */ void ApplyItemVisuals(); /** * Cale la boite d'interaction sur le mesh actuellement pose. * * Elle lit le mesh du COMPOSANT et pas celui de l'ItemData : le cas * bUseMeshFromItemData decoche -- un mesh impose a la main sur une instance -- * a autant besoin d'une boite a sa taille que les autres. */ void FitInteractionBox(); /** * Reprend automatiquement le WorldMesh de l'ItemData dans l'editeur. * Decoche pour imposer un mesh different sur une instance precise. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item") bool bUseMeshFromItemData = true; /** * La boite d'interaction epouse toute seule le mesh pose. * * Coche par defaut : changer l'ItemData d'une instance doit suffire, sans * avoir a retailler un volume a la main derriere. Decoche-le pour un objet * qu'on veut volontairement ramassable de plus loin, ou de plus pres. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Interaction") bool bAutoFitInteractionBox = true; /** * Marge ajoutee sur chaque axe, en cm. * * Additive et surtout pas multiplicative : c'est tout l'interet du passage a * une boite. Multiplier la demi-epaisseur d'une branche -- 7 cm -- par 1.2 * ajoute un centimetre et demi, autant dire rien, alors que le meme facteur * rallonge son grand axe de 12 cm. La tolerance de visee ne depend pas de la * taille de l'objet, elle se compte en centimetres. * * Garde-la petite : le trace d'interaction est deja un balayage spherique * tolerant, gonfler la boite ferait surtout voler la cible aux voisins. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Interaction", meta = (EditCondition = "bAutoFitInteractionBox", ClampMin = "0", Units = "cm")) float InteractionBoxPadding = 4.f; /** * Demi-dimension plancher, appliquee axe par axe, en cm. * * Une baie ou un clou donneraient une boite de deux centimetres, impossible * a viser meme avec la tolerance du balayage. Axe par axe et non sur le * volume entier : une branche a besoin qu'on epaississe son epaisseur, pas * qu'on rallonge sa longueur. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Interaction", meta = (EditCondition = "bAutoFitInteractionBox", ClampMin = "0", Units = "cm")) float MinInteractionExtent = 10.f; /** Active le Custom Depth quand l'objet est vise. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item") bool bHighlightOnFocus = true; };