Import initial du projet Survival (UE 5.8)

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>
This commit is contained in:
2026-07-28 11:29:07 +02:00
commit ed7c7fd991
2626 changed files with 14833 additions and 0 deletions
@@ -0,0 +1,85 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ConfirmDialogWidget.generated.h"
class UButton;
class UTextBlock;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConfirmDialogConfirmed);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConfirmDialogCancelled);
/**
* Boite de dialogue "es-tu sur ?", volontairement generique.
*
* Elle ne sait pas ce qu'elle confirme : elle pose la question et previent via
* ses delegues. C'est l'appelant qui decide de l'action. Ainsi la meme boite
* servira pour quitter, effacer une sauvegarde ou abandonner une partie.
*
* Classe Abstract : on cree un WBP_ConfirmDialog qui en herite.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UConfirmDialogWidget : public UUserWidget
{
GENERATED_BODY()
public:
UConfirmDialogWidget(const FObjectInitializer& ObjectInitializer);
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override;
UPROPERTY(BlueprintAssignable, Category = "Dialogue")
FOnConfirmDialogConfirmed OnConfirmed;
UPROPERTY(BlueprintAssignable, Category = "Dialogue")
FOnConfirmDialogCancelled OnCancelled;
/**
* Affiche la boite et lui donne le focus clavier.
*
* Passe par ici et jamais par SetVisibility directement : sans le focus,
* la boite ne recevrait aucune touche et Echap resterait sans effet.
*/
UFUNCTION(BlueprintCallable, Category = "Dialogue")
void Open(const FText& InMessage);
/** Referme la boite. Ne previent personne : l'appelant sait ce qu'il fait. */
UFUNCTION(BlueprintCallable, Category = "Dialogue")
void Close();
protected:
/** Echap equivaut a cliquer Annuler. Decoche pour une question incontournable. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Dialogue")
bool bCancelOnEscape = true;
/**
* Entree equivaut a cliquer Confirmer.
*
* Desactive par defaut, volontairement : sur une question destructrice, un
* joueur qui martele Entree validerait sans avoir lu.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Dialogue")
bool bConfirmOnEnter = false;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> ConfirmButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> CancelButton;
/** Optionnel : une boite dont le texte est fige dans le Blueprint reste valide. */
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UTextBlock> MessageText;
private:
UFUNCTION()
void HandleConfirmClicked();
UFUNCTION()
void HandleCancelClicked();
};
+395
View File
@@ -0,0 +1,395 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "FpsPlayer.generated.h"
class APickupItem;
class UCameraComponent;
class UInputAction;
class UInputMappingContext;
class UInteractionComponent;
class UInventoryComponent;
class USurvivalStatsComponent;
struct FInputActionValue;
/**
* Personnage joueur en vue première personne.
* Les assets d'input (Input Mapping Context + Input Actions) se branchent
* dans le Blueprint enfant (BP_FpsPlayer), catégorie "Input".
*/
UCLASS()
class SURVIVAL_PROJET_API AFpsPlayer : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AFpsPlayer();
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
/** Vrai tant que le joueur sprinte (utile pour l'anim ou la stamina plus tard). */
UFUNCTION(BlueprintPure, Category = "Mouvement")
bool IsSprinting() const { return bIsSprinting; }
UFUNCTION(BlueprintPure, Category = "Camera")
UCameraComponent* GetFirstPersonCamera() const { return FirstPersonCamera; }
UFUNCTION(BlueprintPure, Category = "Interaction")
UInteractionComponent* GetInteractionComponent() const { return InteractionComponent; }
UFUNCTION(BlueprintPure, Category = "Inventaire")
UInventoryComponent* GetInventoryComponent() const { return InventoryComponent; }
UFUNCTION(BlueprintPure, Category = "Survie")
USurvivalStatsComponent* GetSurvivalStats() const { return SurvivalStats; }
/** Sert a l'UI pour afficher la vraie touche mappee, plutot qu'un "E" ecrit en dur. */
UFUNCTION(BlueprintPure, Category = "Input")
const UInputAction* GetInteractAction() const { return InteractAction; }
/**
* Coupe les etats maintenus avant de desactiver les inputs du pawn.
*
* Sprint et saut sont declenches par Started et arretes par Completed.
* Si on desactive l'input pendant que la touche est enfoncee, le Completed
* n'a plus personne pour l'ecouter et l'etat reste bloque : on rouvrirait
* le jeu en sprintant sans toucher a Maj.
*/
UFUNCTION(BlueprintCallable, Category = "Input")
void ClearTransientInputStates();
/**
* Retire des objets d'un slot et les fait apparaitre au sol devant le joueur.
* @param Quantity nombre d'exemplaires ; passe un grand nombre pour tout jeter.
* @return true si quelque chose est reellement sorti de l'inventaire.
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
bool DropSlot(int32 SlotIndex, int32 Quantity);
/** Vide l'inventaire entier en un tas autour du personnage. */
UFUNCTION(BlueprintCallable, Category = "Inventaire")
void DropAllItems();
UFUNCTION(BlueprintPure, Category = "Survie")
bool IsDead() const { return bIsDead; }
/**
* Secoue la caméra verticalement. Strength est la vitesse initiale en cm/s
* donnée à la caméra ; positif = vers le haut, négatif = vers le bas.
* Réutilisable pour une explosion, un recul d'arme, un coup reçu.
*/
UFUNCTION(BlueprintCallable, Category = "Camera")
void AddCameraPunch(float Strength);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Appelés par le CharacterMovement quand la capsule change de taille (accroupi)
virtual void OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
virtual void OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust) override;
// Réception au sol et décollage
virtual void Landed(const FHitResult& Hit) override;
virtual void OnJumped_Implementation() override;
// ------------------------------------------------------------------
// Composants
// ------------------------------------------------------------------
/** Caméra placée à hauteur des yeux, suit la rotation du controller. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
TObjectPtr<UCameraComponent> FirstPersonCamera;
/** Détecte et déclenche les interactions avec le monde. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Interaction")
TObjectPtr<UInteractionComponent> InteractionComponent;
/**
* Inventaire du joueur. Sur le pawn et non sur le controller :
* un cadavre qui garde son butin fait partie du gameplay de survie.
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Inventaire")
TObjectPtr<UInventoryComponent> InventoryComponent;
/** Vie, faim, soif. Sur le pawn : c'est ce corps qui a faim. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Survie")
TObjectPtr<USurvivalStatsComponent> SurvivalStats;
// ------------------------------------------------------------------
// Enhanced Input : assets à assigner dans le Blueprint
// ------------------------------------------------------------------
/** Contexte ajouté au joueur au BeginPlay (IMC_Default). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputMappingContext> DefaultMappingContext;
/** Déplacement, valeur Axis2D (X = droite/gauche, Y = avant/arrière). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> MoveAction;
/** Regard souris / stick droit, valeur Axis2D. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> LookAction;
/** Saut, valeur Digital (bool). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> JumpAction;
/** Sprint, valeur Digital (bool) — maintenu. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> SprintAction;
/** Accroupi, valeur Digital (bool) — maintenu. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> CrouchAction;
/** Interaction, valeur Digital (bool). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> InteractAction;
/**
* Utilise l'objet actif de la barre rapide, valeur Digital (bool).
* A mapper sur le clic gauche.
*
* Bindee sur le PAWN volontairement : quand l'inventaire est ouvert, les
* inputs du pawn sont coupes, donc le clic gauche sert au glisser-deposer
* de l'UI sans qu'on ait le moindre test a ecrire.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> UseItemAction;
// ------------------------------------------------------------------
// Réglages : mouvement
// ------------------------------------------------------------------
/** Vitesse de marche normale (cm/s). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mouvement", meta = (ClampMin = "0"))
float WalkSpeed = 400.f;
/** Vitesse en sprint (cm/s). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mouvement", meta = (ClampMin = "0"))
float SprintSpeed = 650.f;
/** Vitesse accroupi (cm/s). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mouvement", meta = (ClampMin = "0"))
float CrouchSpeed = 200.f;
// ------------------------------------------------------------------
// Réglages : objets jetés au sol
// ------------------------------------------------------------------
/**
* Classe engendrée quand on jette un objet. Assigne ton BP_Pickup_Test :
* son ItemData est écrasé au spawn, un seul Blueprint suffit pour tout.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Inventaire|Drop")
TSubclassOf<APickupItem> DroppedPickupClass;
/** Distance devant les yeux où l'objet est déposé (cm). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire|Drop", meta = (ClampMin = "10"))
float DropDistance = 120.f;
/** Hauteur au-dessus du sol trouvé, pour éviter que l'objet s'y encastre. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire|Drop")
float DropGroundOffset = 5.f;
// ------------------------------------------------------------------
// Réglages : regard
// ------------------------------------------------------------------
/**
* Sensibilité horizontale : degrés de rotation par unité de souris.
* 1.0 = brut (beaucoup trop rapide), ~0.4 est un bon point de départ.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.01"))
float LookSensitivityX = 0.4f;
/** Sensibilité verticale. Souvent réglée un peu plus basse que l'horizontale. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.01"))
float LookSensitivityY = 0.4f;
/** Inverse l'axe vertical de la souris. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
bool bInvertLookY = false;
/**
* Hauteur de l'oeil AU-DESSUS DES PIEDS, debout (cm).
* 160 cm correspond a un personnage d'environ 1m76.
* Mesure depuis le sol et non depuis la capsule : c'est ce qui permet
* d'annuler le saut instantane de la capsule quand elle retrecit.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
float EyeHeightStanding = 160.f;
/** Hauteur de l'oeil au-dessus des pieds, accroupi (cm). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
float EyeHeightCrouched = 104.f;
// ------------------------------------------------------------------
// Réglages : animation d'accroupissement
// ------------------------------------------------------------------
/** Durée en secondes de la transition debout <-> accroupi. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0.01"))
float CrouchTransitionDuration = 0.4f;
/** Tangage (degrés) au pic de la transition. Positif = le regard plonge. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi")
float CrouchTiltPitch = 3.f;
/**
* Roulis (degrés) au pic de la transition : bascule sur le côté.
* A 0 par defaut, le mouvement reste purement vertical.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi")
float CrouchTiltRoll = 0.f;
// ------------------------------------------------------------------
// Réglages : impacts caméra (saut / atterrissage)
// ------------------------------------------------------------------
/**
* Fréquence du ressort de caméra en Hz : à quelle vitesse elle revient
* en place après un choc. Plus haut = plus sec.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0.1"))
float PunchFrequency = 3.f;
/**
* Amortissement du ressort. 1.0 = retour net sans rebond,
* 0.5 = un rebond visible, en dessous ça oscille longtemps.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0.05", ClampMax = "2.0"))
float PunchDamping = 0.85f;
/**
* Durée (s) sur laquelle une impulsion est injectée dans le ressort.
* A 0 elle est appliquée d'un coup : la caméra part à pleine vitesse en
* une frame, ce qui se lit comme un a-coup. Quelques centiemes suffisent
* a rendre l'entree en mouvement progressive.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
float PunchImpulseRampTime = 0.06f;
/** Degrés de tangage ajoutés par cm de déplacement vertical de la caméra. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact")
float PunchPitchPerCm = 0.35f;
/** Amplitude maximale du déplacement vertical (cm), garde-fou. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
float PunchMaxOffset = 50.f;
/**
* Impulsion (cm/s) à l'atterrissage, quand l'impact est au maximum.
* Ordre de grandeur : le deplacement en cm de la camera vaut environ
* cette valeur x 0.018 (avec les reglages de ressort par defaut).
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
float LandPunchStrength = 2000.f;
/** En dessous de cette vitesse de chute (cm/s), aucun effet à l'atterrissage. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "0"))
float LandPunchMinFallSpeed = 150.f;
/**
* Vitesse de chute (cm/s) à partir de laquelle l'effet est au maximum.
* Un saut sur place retombe a peu pres a JumpZVelocity (450), donc cette
* valeur doit rester proche pour qu'un saut normal se sente deja bien.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact", meta = (ClampMin = "1"))
float LandPunchMaxFallSpeed = 900.f;
/**
* Impulsion (cm/s) au décollage du saut. Positif = la caméra s'enfonce
* un instant (compression des jambes) ; mets une valeur négative pour
* qu'elle parte vers le haut à la place.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Impact")
float JumpPunchStrength = 550.f;
// ------------------------------------------------------------------
// Réglages : champ de vision
// ------------------------------------------------------------------
/** FOV au repos, en degrés. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|FOV", meta = (ClampMin = "30", ClampMax = "150"))
float BaseFOV = 90.f;
/** Degrés de FOV ajoutés en sprint. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|FOV")
float SprintFOVOffset = 8.f;
/** Vitesse de transition du FOV. Plus haut = plus brusque. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|FOV", meta = (ClampMin = "0.1"))
float FOVInterpSpeed = 8.f;
// ------------------------------------------------------------------
// Handlers d'input
// ------------------------------------------------------------------
void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void StartSprint();
void StopSprint();
void StartCrouch();
void StopCrouch();
void Interact();
/**
* Clic gauche : agit avec l'objet actif de la barre rapide.
* Aujourd'hui seuls les consommables font quelque chose ; c'est ici que
* viendront les outils et les armes.
*/
void UseSelectedItem();
private:
/** Fait avancer toutes les animations de caméra et les applique. */
void UpdateCameraEffects(float DeltaTime);
/** Branche sur OnDied du composant de survie. */
UFUNCTION()
void HandleDeath();
/** Fait apparaitre un pickup deja configure a un endroit donne. */
APickupItem* SpawnPickup(const struct FInventorySlot& Slot, const FVector& Location);
/** Descend une position jusqu'au sol, pour ne pas laisser d'objet en l'air. */
FVector PlaceOnGround(const FVector& Start) const;
bool bIsDead = false;
/** Progression de l'animation d'accroupissement : 0 = debout, 1 = accroupi. */
float CrouchAlpha = 0.f;
/** Valeur visée par CrouchAlpha. */
float CrouchTargetAlpha = 0.f;
/** +1 quand on s'accroupit, -1 quand on se relève : inverse le sens du tilt. */
float CrouchTiltDirection = 1.f;
/** Décalage vertical courant de la caméra (cm), état du ressort d'impact. */
float PunchOffset = 0.f;
/** Vitesse du ressort d'impact (cm/s). */
float PunchVelocity = 0.f;
/** Impulsion restant à injecter dans le ressort, étalée sur PunchImpulseRampTime. */
float PendingPunch = 0.f;
/** Vitesse verticale mémorisée pendant la chute, pour calibrer l'atterrissage. */
float LastFallSpeed = 0.f;
bool bIsSprinting = false;
/** Évite de spammer le log si IA_Look est mal configurée. */
bool bLoggedLookTypeWarning = false;
};
@@ -0,0 +1,169 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "FpsPlayerController.generated.h"
class UHotbarWidget;
class UInputAction;
class UInventoryComponent;
class UInteractionPromptWidget;
class UInventoryWidget;
class UScreenFadeComponent;
class USurvivalStatsWidget;
class UUserWidget;
struct FInputActionValue;
/**
* Controller du joueur. C'est ici que vit l'UI, et pas dans le personnage :
* le controller survit a la mort du pawn, alors que le personnage non.
* Si tu creais le HUD dans AFpsPlayer, il disparaitrait a chaque respawn.
*/
UCLASS()
class SURVIVAL_PROJET_API AFpsPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AFpsPlayerController();
/** Ouvre ou ferme l'inventaire. */
UFUNCTION(BlueprintCallable, Category = "UI")
void ToggleInventory();
UFUNCTION(BlueprintPure, Category = "UI")
bool IsInventoryOpen() const { return bInventoryOpen; }
protected:
virtual void BeginPlay() override;
virtual void OnPossess(APawn* InPawn) override;
virtual void SetupInputComponent() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
// ------------------------------------------------------------------
// Fondus
//
// La mécanique elle-même vit dans UScreenFadeComponent, partagé avec le
// menu principal. Ici on ne garde que les durées propres au jeu.
// La couleur et le WBP_Fade se règlent sur le composant.
// ------------------------------------------------------------------
/** Le voile. Assigne-lui ton WBP_Fade dans BP_FpsPlayerController. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UI|Fondu")
TObjectPtr<UScreenFadeComponent> ScreenFade;
/** Décoche pour démarrer sans fondu, pratique pendant les tests. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu")
bool bFadeInOnStart = true;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
float StartupFadeDuration = 1.5f;
/** Durée du fondu au noir quand le joueur meurt. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
float DeathFadeDuration = 1.f;
/**
* Temps total entre la mort et la réapparition.
* Doit rester supérieur à DeathFadeDuration, sinon le joueur réapparaît
* avant que l'écran soit entièrement noir.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
float RespawnDelay = 3.f;
/**
* Ouverture de l'inventaire, valeur Digital (bool).
* L'input d'interface vit sur le controller et non sur le pawn : ouvrir
* son sac doit rester possible meme sans corps a piloter.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> ToggleInventoryAction;
/**
* Jette l'objet survole, valeur Digital (bool). Ctrl enfonce = un seul
* exemplaire au lieu de la pile entiere.
* Sur le controller aussi : le pawn a ses inputs coupes quand l'inventaire
* est ouvert, donc un binding sur le pawn ne repondrait jamais.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> DropItemAction;
/**
* Selection directe d'un slot de la barre rapide, valeur Axis1D.
*
* Une seule action pour toutes les touches : dans IMC_Default, mappe les
* touches 1 a 6 sur cette action et donne a chacune un modificateur
* Scalar different (1, 2, 3...). Le handler lit la valeur et en deduit
* le slot. Six actions separees seraient six assets a maintenir.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> HotbarSelectAction;
/** Molette de souris pour defiler dans la barre, valeur Axis1D. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> HotbarScrollAction;
/**
* Viseur, affiche en permanence. Widget separe du prompt : ce dernier se
* met en Collapsed des qu'on ne vise plus rien, et emporterait le viseur
* avec lui s'ils partageaient le meme widget.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
TSubclassOf<UUserWidget> CrosshairClass;
/** A assigner dans BP_FpsPlayerController : ton WBP_InteractionPrompt. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
TSubclassOf<UInteractionPromptWidget> InteractionPromptClass;
/** Ton WBP_Inventory. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
TSubclassOf<UInventoryWidget> InventoryClass;
/** Ton WBP_Hotbar, affiche en permanence. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
TSubclassOf<UHotbarWidget> HotbarClass;
/** Ton WBP_SurvivalStats : les trois jauges. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
TSubclassOf<USurvivalStatsWidget> SurvivalStatsClass;
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
TObjectPtr<UUserWidget> Crosshair;
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
TObjectPtr<UInteractionPromptWidget> InteractionPrompt;
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
TObjectPtr<UInventoryWidget> InventoryWidget;
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
TObjectPtr<UHotbarWidget> Hotbar;
UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
TObjectPtr<USurvivalStatsWidget> SurvivalStatsWidget;
private:
void CreateHudWidgets();
/** Jette la pile survolee dans la grille, si l'inventaire est ouvert. */
void DropHoveredItem();
void HandleHotbarSelect(const FInputActionValue& Value);
void HandleHotbarScroll(const FInputActionValue& Value);
/** Raccourci vers l'inventaire du pawn possede. */
UInventoryComponent* GetPawnInventory() const;
/** Branche sur OnDied du pawn possede. */
UFUNCTION()
void HandlePawnDied();
/** Detruit le cadavre et demande un nouveau pawn au GameMode. */
void RespawnPlayer();
FTimerHandle RespawnTimerHandle;
bool bInventoryOpen = false;
};
@@ -0,0 +1,66 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "HotbarWidget.generated.h"
class UHorizontalBox;
class UInventoryComponent;
class UInventorySlotWidget;
/**
* Barre rapide en bas de l'ecran, affichee en permanence.
*
* Elle ne possede aucune donnee : elle montre les N premiers slots de
* l'inventaire. Deplacer un objet de la grille vers la barre, c'est
* simplement le deplacer vers un slot d'index bas -- le glisser-deposer
* existant s'en occupe deja sans aucun code supplementaire.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UHotbarWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
/** Remet a jour contenu et selection. */
UFUNCTION(BlueprintCallable, Category = "Inventaire")
void Refresh();
/** (Re)branche sur l'inventaire du pawn possede. Voir UInventoryWidget. */
void BindToOwningPawn();
protected:
/** A nommer exactement "SlotContainer" dans le Widget Blueprint. */
UPROPERTY(meta = (BindWidget))
TObjectPtr<UHorizontalBox> SlotContainer;
/** Le meme WBP_InventorySlot que la grille : une seule apparence a maintenir. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire")
TSubclassOf<UInventorySlotWidget> SlotWidgetClass;
/** Espacement entre les cases. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire", meta = (ClampMin = "0"))
float SlotPadding = 4.f;
private:
UFUNCTION()
void HandleInventoryChanged();
UFUNCTION()
void HandleSelectionChanged(int32 NewIndex);
void RebuildSlots(int32 DesiredCount);
UInventoryComponent* ResolveInventory() const;
TWeakObjectPtr<UInventoryComponent> BoundInventory;
/** UPROPERTY obligatoire : sans ca le GC collecterait les cases. */
UPROPERTY(Transient)
TArray<TObjectPtr<UInventorySlotWidget>> SlotWidgets;
};
@@ -0,0 +1,71 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Interactable.generated.h"
/**
* Classe technique requise par Unreal. On ne la manipule jamais directement,
* elle sert uniquement au systeme de reflection : c'est elle qu'on passe a
* Implements<UInteractable>() et a Execute_XXX().
*/
UINTERFACE(MinimalAPI, BlueprintType)
class UInteractable : public UInterface
{
GENERATED_BODY()
};
/**
* A implementer par tout ce avec quoi le joueur peut interagir : objets a
* ramasser, portes, coffres, etablis, plantes a recolter.
*
* Les fonctions sont des BlueprintNativeEvent : une classe C++ surcharge
* XXX_Implementation(), un Blueprint surcharge l'evenement du meme nom.
* Les deux fonctionnent, et l'appelant n'a pas a savoir lequel des deux
* il a en face -- c'est tout l'interet de passer par une interface.
*
* IMPORTANT : pour appeler ces fonctions depuis l'exterieur, utilise
* IInteractable::Execute_Interact(Actor, Interactor) et jamais
* Actor->Interact() directement. Seul le wrapper Execute_ sait router
* l'appel vers une implementation Blueprint.
*/
class SURVIVAL_PROJET_API IInteractable
{
GENERATED_BODY()
public:
/**
* Texte affiche au joueur quand il vise l'objet ("Ramasser", "Ouvrir"...).
* Renvoie du FText et non du FString : c'est le type localisable d'Unreal,
* indispensable si tu traduis le jeu un jour.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
FText GetInteractionPrompt() const;
virtual FText GetInteractionPrompt_Implementation() const { return FText::GetEmpty(); }
/**
* Permet de refuser l'interaction selon le contexte : porte verrouillee,
* inventaire plein, competence manquante. Renvoyer false empeche l'objet
* d'etre meme selectionne par le composant d'interaction.
*/
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
bool CanInteract(AActor* Interactor) const;
virtual bool CanInteract_Implementation(AActor* Interactor) const { return true; }
/** L'interaction elle-meme. Interactor est le pawn du joueur. */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void Interact(AActor* Interactor);
virtual void Interact_Implementation(AActor* Interactor) {}
/** Le joueur commence a viser l'objet : surbrillance, son, animation. */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void OnBeginFocus();
virtual void OnBeginFocus_Implementation() {}
/** Le joueur ne vise plus l'objet. */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void OnEndFocus();
virtual void OnEndFocus_Implementation() {}
};
@@ -0,0 +1,103 @@
// 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);
/**
* 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. */
UFUNCTION(BlueprintPure, Category = "Interaction")
FText GetFocusedPrompt() const;
/** Branche ton widget la-dessus pour afficher / cacher le prompt. */
UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnInteractionFocusChanged OnFocusChanged;
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<ECollisionChannel> 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;
/** Cible courante. TWeakObjectPtr : si l'objet est detruit, on ne garde pas un pointeur mort. */
TWeakObjectPtr<AActor> 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;
float TimeSinceLastTrace = 0.f;
};
@@ -0,0 +1,69 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "InteractionPromptWidget.generated.h"
class UInteractionComponent;
class UTextBlock;
/**
* Affiche "Appuyez sur E pour Ramasser Objet" quand le joueur vise quelque
* chose d'interactif, et se cache sinon.
*
* La classe est Abstract : on ne l'instancie jamais directement, on cree un
* Widget Blueprint (WBP_InteractionPrompt) qui en herite. Le C++ porte la
* logique, le Blueprint porte la mise en page. C'est la separation standard
* en UMG, l'equivalent d'un prefab d'UI avec son script en Unity.
*
* Le widget ne fait AUCUN travail par frame : il s'abonne au delegue
* OnFocusChanged du composant d'interaction et ne se met a jour que lorsque
* la cible change reellement.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UInteractionPromptWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
/** (Re)branche sur le composant d'interaction du pawn possede. */
void BindToOwningPawn();
protected:
/**
* meta = (BindWidget) : Unreal relie automatiquement ce pointeur au widget
* nomme exactement "PromptText" dans le Widget Blueprint enfant. Si tu
* oublies de le creer ou que tu le renommes, le Blueprint refuse de
* compiler avec un message clair -- pas de crash silencieux au runtime.
*/
UPROPERTY(meta = (BindWidget))
TObjectPtr<UTextBlock> PromptText;
/**
* Texte affiche. {0} sera remplace par la touche, {1} par l'action.
* Editable dans le Widget Blueprint pour ajuster la formulation.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
FText PromptFormat;
UInteractionPromptWidget(const FObjectInitializer& ObjectInitializer);
private:
/** Appele par le composant d'interaction a chaque changement de cible. */
UFUNCTION()
void HandleFocusChanged(AActor* NewFocus, AActor* OldFocus);
/** Recalcule le texte et la visibilite. */
void Refresh();
/** Nom lisible de la touche actuellement mappee sur l'action Interagir. */
FText GetInteractKeyDisplayName() const;
/** Le composant auquel on est abonne, pour pouvoir se desabonner proprement. */
TWeakObjectPtr<UInteractionComponent> BoundComponent;
};
@@ -0,0 +1,230 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "InventoryComponent.generated.h"
class UItemDataAsset;
/**
* Contenu d'un emplacement. Item nul = slot vide.
* Le tableau de slots a toujours exactement la taille de l'inventaire :
* l'index dans le tableau EST la position dans la grille, ce qui rend
* l'affichage et le futur drag & drop triviaux.
*/
USTRUCT(BlueprintType)
struct FInventorySlot
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire")
TObjectPtr<UItemDataAsset> Item = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire")
int32 Quantity = 0;
/**
* Charges restantes de l'exemplaire courant, pour les objets a usages
* multiples. Etat par instance, d'ou sa place ici et pas sur le DataAsset :
* une canette entamee ne doit pas entamer toutes les canettes du jeu.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire")
int32 RemainingUses = 0;
bool IsEmpty() const { return Item == nullptr || Quantity <= 0; }
void Clear()
{
Item = nullptr;
Quantity = 0;
RemainingUses = 0;
}
};
/** Diffuse a chaque modification du contenu ou de la taille. L'UI s'y abonne. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryChanged);
/** Diffuse quand le joueur change de slot actif dans la barre rapide. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSelectedHotbarSlotChanged, int32, NewIndex);
/**
* Inventaire en grille a slots, facon Raft / Valheim.
*
* Le nombre de slots est dynamique : BaseSlotCount fixe le minimum, et
* BonusSlotCount permet a un sac equipe de l'agrandir. Retirer le sac
* refuse si les objets ne rentrent plus, plutot que de les faire
* disparaitre en silence.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
public:
UInventoryComponent();
virtual void InitializeComponent() override;
/**
* Ajoute des objets, en remplissant d'abord les piles entamees.
* @param RemainingUses charges de l'exemplaire ajoute ; -1 signifie "plein".
* Sert a rendre son etat a un objet ramasse au sol.
* @return le nombre d'exemplaires qui N'ONT PAS pu rentrer (0 = tout est passe).
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
int32 AddItem(UItemDataAsset* Item, int32 Quantity = 1, int32 RemainingUses = -1);
/**
* Consomme une utilisation du slot.
* Retire un exemplaire quand il ne reste plus de charge.
* @return true si quelque chose a ete consomme.
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
bool ConsumeUse(int32 SlotIndex);
/**
* Retire des objets, en vidant d'abord les piles les plus petites.
* @return le nombre d'exemplaires reellement retires.
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
int32 RemoveItem(UItemDataAsset* Item, int32 Quantity = 1);
/** Combien d'exemplaires de cet objet, toutes piles confondues. */
UFUNCTION(BlueprintPure, Category = "Inventaire")
int32 GetItemCount(const UItemDataAsset* Item) const;
/** Combien d'exemplaires pourraient encore rentrer, sans rien modifier. */
UFUNCTION(BlueprintPure, Category = "Inventaire")
int32 GetRoomFor(const UItemDataAsset* Item) const;
/**
* Un seul tableau, deux conteneurs distincts :
* index 0 .. HotbarSlotCount-1 -> barre rapide
* index HotbarSlotCount .. fin -> grille d'inventaire (+ bonus de sac)
*
* Un objet range dans la barre n'apparait PAS dans la grille, et
* inversement. Ils partagent le meme tableau uniquement pour que le
* glisser-deposer entre les deux ne demande aucune conversion d'index.
*/
UFUNCTION(BlueprintPure, Category = "Inventaire")
int32 GetTotalSlotCount() const { return FMath::Max(0, HotbarSlotCount + BackpackSlotCount + BonusSlotCount); }
/** Premier index affiche par la grille, juste apres la barre rapide. */
UFUNCTION(BlueprintPure, Category = "Inventaire")
int32 GetBackpackStartIndex() const { return GetHotbarSlotCount(); }
UFUNCTION(BlueprintPure, Category = "Inventaire")
int32 GetUsedSlotCount() const;
UFUNCTION(BlueprintPure, Category = "Inventaire")
const TArray<FInventorySlot>& GetSlots() const { return Slots; }
/**
* Deplace la pile entiere d'un slot vers un autre.
* - cible vide -> la pile entiere part
* - meme objet -> fusion jusqu'a MaxStackSize, le reste demeure a la source
* - objet different -> echange des deux slots
* @return true si quelque chose a change.
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
bool MoveItem(int32 FromIndex, int32 ToIndex);
/**
* Deplace seulement une partie d'une pile.
*
* Un transfert partiel vers un slot occupe par un objet DIFFERENT est
* refuse : il n'existe aucune facon sensee d'echanger la moitie d'une pile
* contre autre chose. Seul un deplacement total peut provoquer un echange.
*
* @return true si quelque chose a change.
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
bool MoveItemQuantity(int32 FromIndex, int32 ToIndex, int32 Quantity);
/** Index du premier slot libre, ou INDEX_NONE si l'inventaire est plein. */
UFUNCTION(BlueprintPure, Category = "Inventaire")
int32 FindFirstEmptySlot() const;
/**
* Retire jusqu'a Quantity exemplaires d'un slot precis et renvoie ce qui a
* ete retire. Sert au drop au sol et, plus tard, au transfert vers un coffre.
*
* Different de RemoveItem() qui pioche par type sans se soucier du slot :
* ici le joueur a designe une case, on ne touche qu'a celle-la.
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
bool TakeFromSlot(int32 SlotIndex, int32 Quantity, FInventorySlot& OutTaken);
/**
* Change le bonus de slots apporte par l'equipement (sac).
* @return false si reduire la taille laisserait des objets dehors ;
* dans ce cas rien n'est modifie et le sac ne doit pas etre retire.
*/
UFUNCTION(BlueprintCallable, Category = "Inventaire")
bool SetBonusSlotCount(int32 NewBonusSlots);
UPROPERTY(BlueprintAssignable, Category = "Inventaire")
FOnInventoryChanged OnInventoryChanged;
// ------------------------------------------------------------------
// Barre rapide
// ------------------------------------------------------------------
//
// La barre rapide n'est PAS un jeu de slots separe : ce sont les
// HotbarSlotCount premiers slots de l'inventaire, simplement affiches
// deux fois. Consequence directe : le glisser-deposer entre la barre et
// la grille fonctionne sans une ligne de code de plus, puisque ce sont
// les memes index dans le meme tableau.
/** Change le slot actif. Un index hors de la barre est ignore. */
UFUNCTION(BlueprintCallable, Category = "Inventaire|Barre rapide")
void SelectHotbarSlot(int32 Index);
/** Avance ou recule dans la barre, en boucle. */
UFUNCTION(BlueprintCallable, Category = "Inventaire|Barre rapide")
void CycleHotbarSelection(int32 Delta);
UFUNCTION(BlueprintPure, Category = "Inventaire|Barre rapide")
int32 GetSelectedHotbarIndex() const { return SelectedHotbarIndex; }
UFUNCTION(BlueprintPure, Category = "Inventaire|Barre rapide")
int32 GetHotbarSlotCount() const { return FMath::Min(HotbarSlotCount, Slots.Num()); }
/** Contenu du slot actif. C'est ce que le joueur "a en main". */
UFUNCTION(BlueprintPure, Category = "Inventaire|Barre rapide")
FInventorySlot GetSelectedSlot() const;
UPROPERTY(BlueprintAssignable, Category = "Inventaire|Barre rapide")
FOnSelectedHotbarSlotChanged OnSelectedHotbarSlotChanged;
protected:
/** Cases affichees dans la grille d'inventaire. N'inclut PAS la barre rapide. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire", meta = (ClampMin = "0"))
int32 BackpackSlotCount = 20;
/** Slots supplementaires apportes par l'equipement (sac). Passe par SetBonusSlotCount(). */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Inventaire")
int32 BonusSlotCount = 0;
/**
* Cases de la barre rapide, independantes de celles de la grille.
* Elles occupent les premiers index du tableau.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire|Barre rapide", meta = (ClampMin = "0"))
int32 HotbarSlotCount = 10;
/** Toujours dimensionne a GetTotalSlotCount(). */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Inventaire")
TArray<FInventorySlot> Slots;
private:
/** Redimensionne le tableau si la taille cible a change. */
void EnsureSlotCount();
/** Regroupe les slots occupes au debut du tableau. */
void CompactSlots();
int32 SelectedHotbarIndex = 0;
};
@@ -0,0 +1,36 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/DragDropOperation.h"
#include "InventoryDragDropOperation.generated.h"
class UInventoryComponent;
/**
* Ce qui voyage entre la case qu'on saisit et celle ou on lache.
*
* On transporte un INDEX et non le contenu du slot : si l'inventaire change
* pendant le glisser (ramassage automatique, script), la cible relira l'etat
* reel au moment du drop au lieu d'appliquer une donnee perimee.
*/
UCLASS()
class SURVIVAL_PROJET_API UInventoryDragDropOperation : public UDragDropOperation
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, Category = "Inventaire")
int32 SourceSlotIndex = INDEX_NONE;
/**
* Combien d'exemplaires on deplace. Egal a la pile entiere pour un
* glisser normal, la moitie avec Maj, un seul avec Ctrl.
*/
UPROPERTY(BlueprintReadOnly, Category = "Inventaire")
int32 Quantity = 0;
UPROPERTY(BlueprintReadOnly, Category = "Inventaire")
TWeakObjectPtr<UInventoryComponent> SourceInventory;
};
@@ -0,0 +1,114 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "InventoryComponent.h"
#include "InventorySlotWidget.generated.h"
class UImage;
class UInventoryWidget;
class UProgressBar;
class UTextBlock;
/**
* Une case de la grille d'inventaire : une icone et une quantite.
*
* Volontairement passive pour l'affichage : elle ne va jamais chercher
* l'information elle-meme, c'est la grille parente qui lui pousse son contenu
* via SetSlot(). Elle est en revanche autonome pour le glisser-deposer, parce
* que c'est elle qui recoit les evenements souris.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UInventorySlotWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
/** Appele une fois a la creation : dit a la case qui elle represente. */
void InitSlot(UInventoryComponent* InInventory, int32 InSlotIndex, UInventoryWidget* InOwningGrid);
/** Affiche le contenu d'un slot, ou se vide si le slot est vide. */
UFUNCTION(BlueprintCallable, Category = "Inventaire")
void SetSlot(const FInventorySlot& InSlot);
/**
* Transforme cette instance en fantome de glisser : le fond de case
* disparait pour ne laisser flotter que l'objet lui-meme.
*/
void ConfigureAsDragVisual();
/** Affiche ou masque le cadre de selection (barre rapide). */
UFUNCTION(BlueprintCallable, Category = "Inventaire")
void SetSelected(bool bSelected);
protected:
// --- Glisser-deposer ---
virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual void NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation) override;
virtual bool NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override;
// --- Survol, pour que la touche "jeter" sache sur quoi agir ---
virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
/** A nommer exactement "ItemIcon" dans le Widget Blueprint. */
UPROPERTY(meta = (BindWidget))
TObjectPtr<UImage> ItemIcon;
/** A nommer exactement "QuantityText". */
UPROPERTY(meta = (BindWidget))
TObjectPtr<UTextBlock> QuantityText;
/**
* Fond de case. BindWidgetOptional et non BindWidget : le Blueprint peut
* s'en passer sans casser la compilation, c'est purement decoratif.
* Nomme-le "SlotBackground" pour qu'il soit masque pendant le glisser.
*/
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UWidget> SlotBackground;
/**
* Cadre affiche quand la case est le slot actif de la barre rapide.
* Optionnel : la grille d'inventaire ne s'en sert jamais.
*/
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UWidget> SelectionBorder;
/**
* Petite barre d'usure en bas de la case, pour les objets a usages
* multiples. Masquee automatiquement sur tout le reste.
* A nommer "UsesBar" dans le Widget Blueprint.
*/
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UProgressBar> UsesBar;
/** Opacite du fantome qui suit le curseur pendant le glisser. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire", meta = (ClampMin = "0.05", ClampMax = "1.0"))
float DragVisualOpacity = 0.85f;
/** Opacite de la case d'origine tant que le glisser est en cours. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float DraggedSlotOpacity = 0.3f;
private:
/** Rebranche l'opacite normale, que le glisser ait abouti ou non. */
UFUNCTION()
void HandleDragFinished(UDragDropOperation* Operation);
TWeakObjectPtr<UInventoryComponent> OwningInventory;
/** La grille parente, pour lui signaler le survol. */
TWeakObjectPtr<UInventoryWidget> OwningGrid;
int32 SlotIndex = INDEX_NONE;
/** Dernier contenu recu, pour construire le fantome de glisser. */
FInventorySlot CachedSlot;
/** Vrai si cette instance est un fantome et non une vraie case de la grille. */
bool bIsDragVisual = false;
};
@@ -0,0 +1,85 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "InventoryWidget.generated.h"
class UInventoryComponent;
class UInventorySlotWidget;
class UUniformGridPanel;
/**
* Grille d'inventaire. Cree ses cases dynamiquement a partir du nombre de
* slots du composant, et se remet a jour uniquement quand l'inventaire change.
*
* Le nombre de cases n'est PAS fige : quand un sac ajoutera des slots, la
* grille s'agrandira toute seule au prochain rafraichissement.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UInventoryWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
/** Reconstruit si necessaire, puis pousse le contenu dans chaque case. */
UFUNCTION(BlueprintCallable, Category = "Inventaire")
void Refresh();
/** Index de la case sous le curseur, ou INDEX_NONE. */
UFUNCTION(BlueprintPure, Category = "Inventaire")
int32 GetHoveredSlotIndex() const { return HoveredSlotIndex; }
/** Appele par les cases quand le curseur entre ou sort. */
void NotifySlotHovered(int32 Index, bool bHovered);
/**
* (Re)branche le widget sur l'inventaire du pawn actuellement possede.
* Indispensable apres un respawn : le nouveau pawn a un nouveau composant,
* et l'ancien abonnement pointerait vers un objet detruit.
*/
void BindToOwningPawn();
protected:
/** A nommer exactement "SlotGrid" dans le Widget Blueprint. */
UPROPERTY(meta = (BindWidget))
TObjectPtr<UUniformGridPanel> SlotGrid;
/** Le widget de case a instancier : ton WBP_InventorySlot. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire")
TSubclassOf<UInventorySlotWidget> SlotWidgetClass;
/** Nombre de cases par ligne. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire", meta = (ClampMin = "1"))
int32 ColumnCount = 6;
protected:
/**
* Absorbe les depots qui ne visent aucune case. Sans ca, lacher un objet
* sur le fond du panneau declencherait OnDragCancelled -- et si un jour on
* y branche le drop au sol, le joueur jetterait son butin par accident.
*/
virtual bool NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) override;
private:
UFUNCTION()
void HandleInventoryChanged();
/** Cree ou supprime des cases pour coller au nombre de slots. */
void RebuildGrid(int32 DesiredCount);
/** Retrouve l'inventaire du pawn possede. */
UInventoryComponent* ResolveInventory() const;
TWeakObjectPtr<UInventoryComponent> BoundInventory;
int32 HoveredSlotIndex = INDEX_NONE;
/** UPROPERTY obligatoire : sans ca le GC collecterait les cases. */
UPROPERTY(Transient)
TArray<TObjectPtr<UInventorySlotWidget>> SlotWidgets;
};
@@ -0,0 +1,104 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ItemDataAsset.generated.h"
class UStaticMesh;
class UTexture2D;
/**
* Definition d'un TYPE d'objet : "Bois", "Pomme", "Corde".
* Un asset par type, cree dans le Content Browser, edite a la main.
*
* C'est l'equivalent exact d'un ScriptableObject Unity : une donnee partagee
* que tout le monde reference et que personne ne duplique. Cent buches dans
* le monde pointent toutes vers le meme UItemDataAsset "Bois".
*
* Regle a ne jamais enfreindre : ceci decrit ce qu'est un objet, jamais son
* etat. La quantite, la durabilite, l'usure appartiennent a l'inventaire ou
* a l'acteur, pas ici -- sinon ramasser une pomme modifierait toutes les
* pommes du jeu.
*/
UCLASS(BlueprintType)
class SURVIVAL_PROJET_API UItemDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
/** Identifiant stable, utilise par la sauvegarde et les recettes de craft. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
FName ItemId;
/** Nom affiche au joueur. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
FText DisplayName;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item", meta = (MultiLine = "true"))
FText Description;
/** Icone affichee dans la grille d'inventaire. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
TObjectPtr<UTexture2D> Icon;
/** Mesh utilise quand l'objet est pose au sol. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
TObjectPtr<UStaticMesh> WorldMesh;
/**
* Nombre maximum d'exemplaires par slot.
* 1 = ne s'empile pas (outils, armes), 20 = ressource classique.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item", meta = (ClampMin = "1"))
int32 MaxStackSize = 1;
/**
* Nombre d'utilisations avant que l'objet soit consomme.
* 1 = objet a usage unique. 3 = une canette qu'on boit en trois gorgees.
*
* Les valeurs de restauration sont appliquees A CHAQUE utilisation, pas
* reparties : une canette a 3 usages et 20 de soif rend 20 par gorgee.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item", meta = (ClampMin = "1"))
int32 MaxUses = 1;
/**
* Taille de pile reellement applicable.
*
* Un objet a charges ne s'empile jamais : deux canettes entamees a des
* niveaux differents ne peuvent pas partager un slot, il faudrait stocker
* deux compteurs dans une seule case. La regle est centralisee ici pour
* qu'aucun appelant ne puisse l'oublier.
*/
UFUNCTION(BlueprintPure, Category = "Item")
int32 GetEffectiveMaxStack() const { return MaxUses > 1 ? 1 : FMath::Max(1, MaxStackSize); }
/**
* Slots ajoutes a l'inventaire quand cet objet est equipe comme sac.
* Laisse a 0 pour tout ce qui n'est pas un contenant.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Sac", meta = (ClampMin = "0"))
int32 BonusInventorySlots = 0;
// ------------------------------------------------------------------
// Consommable
// ------------------------------------------------------------------
/** Coche pour qu'un clic gauche consomme l'objet et applique ses effets. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consommable")
bool bIsConsumable = false;
/** Faim rendue a la consommation. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consommable", meta = (EditCondition = "bIsConsumable"))
float HungerRestore = 0.f;
/** Soif rendue. Une valeur negative deshydrate -- utile pour l'eau salee. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consommable", meta = (EditCondition = "bIsConsumable"))
float ThirstRestore = 0.f;
/** Vie rendue. Negative pour un aliment avarie ou toxique. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consommable", meta = (EditCondition = "bIsConsumable"))
float HealthRestore = 0.f;
};
@@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MainMenuGameMode.generated.h"
/**
* GameMode de la map de menu.
*
* Son role principal est NEGATIF : empecher AFpsPlayer de spawner. La map de
* menu etant une copie de la map de jeu, sans ce GameMode Override dans ses
* World Settings, le joueur apparaitrait derriere le menu avec son HUD, ses
* jauges et son inventaire.
*
* Divergence Unity a garder en tete : ici le GameMode est une propriete DE LA
* MAP, pas un objet qu'on pose dedans. Il se choisit dans World Settings.
*/
UCLASS()
class SURVIVAL_PROJET_API AMainMenuGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AMainMenuGameMode();
};
@@ -0,0 +1,116 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MainMenuPlayerController.generated.h"
class AMenuCameraSpot;
class UConfirmDialogWidget;
class UMainMenuWidget;
class UScreenFadeComponent;
/**
* Ce qu'on fait une fois l'ecran devenu noir.
*
* Quitter n'y figure pas : la sortie est seche, sans fondu, une fois la
* confirmation obtenue.
*/
enum class EMenuPendingAction : uint8
{
None,
StartGame
};
/**
* Controller du menu principal. Il ne possede aucun pawn : il pose simplement
* sa vue sur une AMenuCameraSpot tiree au hasard et affiche le menu par-dessus.
*
* Comme dans le jeu, l'UI vit sur le controller et pas sur un acteur du monde.
*/
UCLASS()
class SURVIVAL_PROJET_API AMainMenuPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AMainMenuPlayerController();
/** Fondu au noir, puis chargement de la map de jeu. */
UFUNCTION(BlueprintCallable, Category = "Menu")
void StartSoloGame();
/** Ouvre la demande de confirmation. Ne quitte rien par elle-meme. */
UFUNCTION(BlueprintCallable, Category = "Menu")
void QuitGame();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/**
* Ta map de jeu.
*
* TSoftObjectPtr et non une reference dure : une reference dure ferait
* charger la map de jeu EN MEME TEMPS que le menu, alors qu'on ne la veut
* qu'au clic sur Jouer. C'est l'equivalent d'une Addressable en Unity, par
* opposition a un champ public qui tire tout l'asset avec lui.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
TSoftObjectPtr<UWorld> GameLevel;
/** Ton WBP_MainMenu. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
TSubclassOf<UMainMenuWidget> MainMenuClass;
/**
* Ton WBP_ConfirmDialog. Si tu le laisses vide, le bouton Quitter ferme le
* jeu directement -- pratique pour tester, mais laisse-le assigne.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
TSubclassOf<UConfirmDialogWidget> QuitDialogClass;
/** La question posee avant de fermer le jeu. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu")
FText QuitDialogMessage;
/** Fondu d'arrivee sur le menu, au lancement du jeu. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu", meta = (ClampMin = "0.1"))
float OpeningFadeDuration = 1.5f;
/** Fondu de depart, quand on quitte le menu pour la partie. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Menu", meta = (ClampMin = "0.1"))
float LeaveFadeDuration = 1.f;
/** Le fondu, partage avec AFpsPlayerController. Assigne-lui ton WBP_Fade. */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Menu")
TObjectPtr<UScreenFadeComponent> ScreenFade;
UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
TObjectPtr<UMainMenuWidget> MainMenu;
UPROPERTY(Transient, BlueprintReadOnly, Category = "Menu")
TObjectPtr<UConfirmDialogWidget> QuitDialog;
private:
/** Tire un angle de vue au hasard, en evitant celui du dernier passage. */
AMenuCameraSpot* PickCameraSpot();
/** Le fondu vient de se terminer : on execute l'action mise de cote. */
UFUNCTION()
void HandleFadeFinished();
/** Oui : on ferme le jeu, immediatement. */
UFUNCTION()
void HandleQuitConfirmed();
/** Non : on referme la boite et on rend la main au menu. */
UFUNCTION()
void HandleQuitCancelled();
/** Ferme le jeu sans rien demander. */
void CloseGame();
EMenuPendingAction PendingAction = EMenuPendingAction::None;
};
@@ -0,0 +1,55 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MainMenuWidget.generated.h"
class UButton;
/**
* Le menu principal : quelques boutons par-dessus la vue du monde.
*
* Classe Abstract comme les autres widgets du projet : on cree un
* WBP_MainMenu qui en herite, le C++ porte la logique et le Blueprint la mise
* en page.
*
* Le widget ne decide de rien, il appelle AMainMenuPlayerController. Le jour ou
* un bouton devra faire une action reseau, elle sera deja du bon cote : un
* widget n'existe que sur la machine du joueur local, le serveur ne le voit pas.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UMainMenuWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
protected:
/** Lance la partie solo. Nomme exactement "PlayButton" dans le WBP. */
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> PlayButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> QuitButton;
/**
* Coop a venir. BindWidgetOptional et non BindWidget : tu peux dessiner ton
* WBP_MainMenu sans eux aujourd'hui, le Blueprint compilera quand meme.
* S'ils existent, ils sont grises automatiquement.
*/
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UButton> HostButton;
UPROPERTY(meta = (BindWidgetOptional))
TObjectPtr<UButton> JoinButton;
private:
UFUNCTION()
void HandlePlayClicked();
UFUNCTION()
void HandleQuitClicked();
};
@@ -0,0 +1,33 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraActor.h"
#include "MenuCameraSpot.generated.h"
/**
* Un angle de vue possible pour le fond du menu principal.
*
* Pose-en autant que tu veux dans la map de menu : le controller en tire une au
* hasard a chaque lancement.
*
* Une classe dediee plutot qu'un tag "MenuCamera" sur une ACameraActor -- le
* reflexe Unity : la recherche est typee au lieu d'etre une chaine de
* caracteres, et la classe a un endroit ou porter ses propres reglages.
*/
UCLASS()
class SURVIVAL_PROJET_API AMenuCameraSpot : public ACameraActor
{
GENERATED_BODY()
public:
AMenuCameraSpot();
/**
* Decoche pour retirer cet angle du tirage sans supprimer l'acteur.
* Pratique pour mettre un cadrage de cote le temps de le retravailler.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Menu")
bool bEnabled = true;
};
@@ -0,0 +1,90 @@
// 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 UItemDataAsset;
class USphereComponent;
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 SURVIVAL_PROJET_API APickupItem : public AActor, public IInteractable
{
GENERATED_BODY()
public:
APickupItem();
/**
* 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.
*/
UFUNCTION(BlueprintCallable, Category = "Item")
void InitPickup(UItemDataAsset* InItemData, int32 InQuantity, int32 InRemainingUses = -1);
// --- IInteractable ---
virtual FText GetInteractionPrompt_Implementation() 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<UStaticMeshComponent> 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 une sphere dediee, le pickup fonctionne quel que soit le visuel.
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item")
TObjectPtr<USphereComponent> InteractionSphere;
/** Quel objet ce pickup represente. A assigner sur chaque instance. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
TObjectPtr<UItemDataAsset> ItemData;
/** Combien d'exemplaires ce tas contient. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item", meta = (ClampMin = "1"))
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, Category = "Item")
int32 RemainingUses = -1;
/**
* 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;
/** Active le Custom Depth quand l'objet est vise. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
bool bHighlightOnFocus = true;
};
@@ -0,0 +1,90 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ScreenFadeComponent.generated.h"
class UUserWidget;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnScreenFadeFinished);
/**
* Fondu plein ecran, pose sur un PlayerController.
*
* Un composant et non du code dans le controller : le fondu sert au jeu (mort,
* respawn) comme au menu principal, et deux implementations du meme voile
* finiraient par diverger.
*
* Le voile est un widget UMG et non le fondu camera du moteur : StartCameraFade
* ne teinte que le rendu de la SCENE et laisse tout le HUD visible par-dessus.
* On garde quand meme StartCameraFade en parallele, uniquement pour le fondu
* AUDIO, que le voile ne sait pas faire.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UScreenFadeComponent : public UActorComponent
{
GENERATED_BODY()
public:
UScreenFadeComponent();
/**
* Se declenche a la fin de CHAQUE fondu, entrant comme sortant.
* A celui qui ecoute de savoir ce qu'il attendait.
*/
UPROPERTY(BlueprintAssignable, Category = "Fondu")
FOnScreenFadeFinished OnFadeFinished;
/** Du noir vers le jeu. */
UFUNCTION(BlueprintCallable, Category = "Fondu")
void FadeIn(float Duration);
/** Du jeu vers le noir, ou l'ecran reste jusqu'au prochain FadeIn. */
UFUNCTION(BlueprintCallable, Category = "Fondu")
void FadeOut(float Duration);
/** Couvre l'ecran immediatement, sans animation ni delegue. */
UFUNCTION(BlueprintCallable, Category = "Fondu")
void SnapToBlack();
UFUNCTION(BlueprintPure, Category = "Fondu")
bool IsFading() const { return bFading; }
protected:
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/** Ton WBP_Fade : un simple rectangle plein ecran, sans logique. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Fondu")
TSubclassOf<UUserWidget> FadeWidgetClass;
/** Noir classique. Le blanc donne un reveil brutal. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Fondu")
FLinearColor FadeColor = FLinearColor::Black;
private:
/** Cree le voile au premier besoin, et pas avant : il depend du controller. */
void EnsureWidget();
/** 1 = ecran entierement couvert, 0 = voile invisible. */
void SetCoverage(float Coverage);
APlayerController* GetOwningController() const;
UPROPERTY(Transient)
TObjectPtr<UUserWidget> FadeWidget;
/** 0 = ecran couvert, 1 = jeu entierement visible. */
float Alpha = 1.f;
/** Valeur visee par Alpha : 0 pour partir au noir, 1 pour revenir. */
float Target = 1.f;
/** Duree du fondu en cours, distincte des reglages qui restent constants. */
float CurrentDuration = 1.f;
bool bFading = false;
};
@@ -0,0 +1,44 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "SurvivalGameInstance.generated.h"
/**
* Le seul objet qui survit aux changements de map.
*
* C'est l'equivalent le plus proche d'un singleton DontDestroyOnLoad d'Unity :
* il naît au lancement du jeu et meurt a sa fermeture. Tout le reste --
* GameMode, GameState, PlayerController, pawns, widgets -- est detruit et
* recree a chaque OpenLevel.
*
* Y mettre : les reglages, la sauvegarde en cours, et plus tard l'hebergement
* et le rejoindre-une-partie. Surtout pas d'etat de gameplay, qui appartient au
* monde et doit disparaitre avec lui.
*/
UCLASS()
class SURVIVAL_PROJET_API USurvivalGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
/** Nom de la derniere camera de menu utilisee. NAME_None au premier lancement. */
UFUNCTION(BlueprintPure, Category = "Menu")
FName GetLastMenuCameraName() const { return LastMenuCameraName; }
UFUNCTION(BlueprintCallable, Category = "Menu")
void SetLastMenuCameraName(FName SpotName) { LastMenuCameraName = SpotName; }
private:
/**
* Stocke un NOM et pas un index : les index changent des qu'on ajoute ou
* supprime une camera dans la map, et on retomberait sur autre chose.
*
* Ne sert qu'a eviter de retirer deux fois de suite le meme angle quand le
* joueur revient au menu -- d'ou le Transient, ca n'a pas a etre sauvegarde.
*/
UPROPERTY(Transient)
FName LastMenuCameraName = NAME_None;
};
@@ -0,0 +1,28 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "SurvivalGameState.generated.h"
/**
* L'etat du monde que TOUT LE MONDE doit connaitre.
*
* Vide pour l'instant, pose des maintenant parce que la rebrancher plus tard
* suppose de retoucher le GameMode et tous les Blueprints qui en derivent.
*
* Le piege a retenir pour la coop : AGameMode n'existe QUE sur le serveur.
* Sur un client, GetGameMode() renvoie nullptr, toujours -- ce n'est pas un bug
* mais le design du framework. Le GameMode arbitre, il ne se montre pas.
* Le GameState, lui, est replique a tout le monde : c'est le seul endroit ou
* un client peut lire l'etat de la partie.
*
* Y mettra : l'heure du jour, le numero de jour, la seed du monde, l'etat des
* ressources partagees. Pas les stats d'un joueur, qui vont sur son PlayerState.
*/
UCLASS()
class SURVIVAL_PROJET_API ASurvivalGameState : public AGameStateBase
{
GENERATED_BODY()
};
@@ -0,0 +1,23 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "SurvivalPlayerState.generated.h"
/**
* Ce que les AUTRES joueurs savent de toi.
*
* Vide pour l'instant. Y mettra : pseudo, couleur, temps de survie, nombre de
* morts -- tout ce qu'un coequipier doit pouvoir lire sur ton compte.
*
* La regle de partage avec le pawn est simple : le PlayerState survit a la mort
* du pawn, le pawn non. L'inventaire et les jauges restent donc sur le pawn,
* puisque la regle du jeu veut qu'on perde tout au sol en mourant.
*/
UCLASS()
class SURVIVAL_PROJET_API ASurvivalPlayerState : public APlayerState
{
GENERATED_BODY()
};
@@ -0,0 +1,116 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SurvivalStatsComponent.generated.h"
/** Diffuse a chaque changement de vie, faim ou soif. L'UI s'y abonne. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalStatsChanged);
/** Diffuse une seule fois, quand la vie atteint zero. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalDeath);
/**
* Vie, faim et soif du personnage.
*
* Les valeurs descendent sur un timer a basse frequence et non dans Tick :
* une jauge qui perd trois points par minute n'a aucune raison d'etre
* recalculee 120 fois par seconde.
*
* Les vitesses sont exprimees PAR MINUTE, parce que c'est l'unite dans
* laquelle on raisonne quand on equilibre un jeu de survie -- "cent points
* de faim en trente minutes" se lit, "0.055 par seconde" non.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API USurvivalStatsComponent : public UActorComponent
{
GENERATED_BODY()
public:
USurvivalStatsComponent();
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
/** Rend de la vie, de la faim et de la soif. Les valeurs sont bornees au max. */
UFUNCTION(BlueprintCallable, Category = "Survie")
void ApplyRestore(float HealthAmount, float HungerAmount, float ThirstAmount);
UFUNCTION(BlueprintCallable, Category = "Survie")
void ApplyDamage(float Amount);
UFUNCTION(BlueprintPure, Category = "Survie")
float GetHealth() const { return Health; }
UFUNCTION(BlueprintPure, Category = "Survie")
float GetHunger() const { return Hunger; }
UFUNCTION(BlueprintPure, Category = "Survie")
float GetThirst() const { return Thirst; }
/** Ratios 0-1, directement utilisables par une ProgressBar. */
UFUNCTION(BlueprintPure, Category = "Survie")
float GetHealthPercent() const { return MaxHealth > 0.f ? Health / MaxHealth : 0.f; }
UFUNCTION(BlueprintPure, Category = "Survie")
float GetHungerPercent() const { return MaxHunger > 0.f ? Hunger / MaxHunger : 0.f; }
UFUNCTION(BlueprintPure, Category = "Survie")
float GetThirstPercent() const { return MaxThirst > 0.f ? Thirst / MaxThirst : 0.f; }
UFUNCTION(BlueprintPure, Category = "Survie")
bool IsDead() const { return bDead; }
UPROPERTY(BlueprintAssignable, Category = "Survie")
FOnSurvivalStatsChanged OnStatsChanged;
UPROPERTY(BlueprintAssignable, Category = "Survie")
FOnSurvivalDeath OnDied;
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie|Vie", meta = (ClampMin = "1"))
float MaxHealth = 100.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie|Faim", meta = (ClampMin = "1"))
float MaxHunger = 100.f;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie|Soif", meta = (ClampMin = "1"))
float MaxThirst = 100.f;
/** Points de faim perdus par minute. 3.3 vide une jauge pleine en ~30 min. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Faim", meta = (ClampMin = "0"))
float HungerDecayPerMinute = 3.3f;
/** La soif descend plus vite que la faim : c'est le besoin le plus pressant. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Soif", meta = (ClampMin = "0"))
float ThirstDecayPerMinute = 5.f;
/** Vie perdue par minute quand la faim est a zero. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Vie", meta = (ClampMin = "0"))
float StarvationDamagePerMinute = 6.f;
/** Vie perdue par minute quand la soif est a zero. Cumulable avec la faim. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Vie", meta = (ClampMin = "0"))
float DehydrationDamagePerMinute = 9.f;
/**
* Secondes entre deux mises a jour. Une seconde suffit largement et reste
* assez fin pour que les barres bougent de facon fluide a l'oeil.
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie", meta = (ClampMin = "0.05"))
float UpdateInterval = 1.f;
private:
/** Appelee par le timer : fait descendre les jauges et applique les degats. */
void UpdateStats();
FTimerHandle UpdateTimerHandle;
float Health = 0.f;
float Hunger = 0.f;
float Thirst = 0.f;
bool bDead = false;
};
@@ -0,0 +1,73 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "SurvivalStatsWidget.generated.h"
class UProgressBar;
class USurvivalStatsComponent;
/**
* Les trois jauges de survie : vie, faim, soif.
*
* Aucun Tick : le widget s'abonne a OnStatsChanged, qui ne se declenche
* qu'une fois par seconde ou lors d'une consommation.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API USurvivalStatsWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
UFUNCTION(BlueprintCallable, Category = "Survie")
void Refresh();
/** (Re)branche sur les stats du pawn possede. Voir UInventoryWidget. */
void BindToOwningPawn();
protected:
/** A nommer exactement "HealthBar", "HungerBar" et "ThirstBar". */
UPROPERTY(meta = (BindWidget))
TObjectPtr<UProgressBar> HealthBar;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UProgressBar> HungerBar;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UProgressBar> ThirstBar;
/** En dessous de ce ratio, la barre passe en couleur d'alerte. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie", meta = (ClampMin = "0", ClampMax = "1"))
float CriticalThreshold = 0.2f;
/** Couleur d'alerte, commune aux trois jauges. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie")
FLinearColor CriticalColor = FLinearColor(0.9f, 0.1f, 0.1f, 1.f);
private:
UFUNCTION()
void HandleStatsChanged();
USurvivalStatsComponent* ResolveStats() const;
/** Applique le remplissage et la couleur d'une barre. */
void ApplyBar(UProgressBar* Bar, float Percent, const FLinearColor& BaseColor) const;
TWeakObjectPtr<USurvivalStatsComponent> BoundStats;
/**
* Couleurs d'origine relevees au NativeConstruct.
*
* On les capture depuis les widgets eux-memes plutot que de les redeclarer
* en C++ : la palette reste entierement dans le Designer, et le code ne
* fait que basculer vers l'alerte puis revenir.
*/
FLinearColor HealthBaseColor = FLinearColor::White;
FLinearColor HungerBaseColor = FLinearColor::White;
FLinearColor ThirstBaseColor = FLinearColor::White;
};