// 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 UCraftingComponent; class UInputAction; class UInputMappingContext; class UInteractionComponent; class UInventoryComponent; class UItemDataAsset; class USurvivalStatsComponent; struct FInputActionValue; /** * Ressort amorti à une dimension, partagé par les effets de caméra. * * Pourquoi un ressort plutôt qu'une courbe paramétrée par le temps : un ressort * n'a pas d'animation « en cours » qu'il faudrait annuler pour en jouer une * autre. Une nouvelle impulsion s'ajoute simplement à l'état courant, position * et vitesse restent continues, donc le mouvement est interruptible à n'importe * quel instant sans jamais produire de saut. C'est exactement ce que le crouch * demande quand le joueur martèle la touche. */ struct FCameraSpring { /** Déplacement courant, dans l'unité de ce que le ressort pilote. */ float Offset = 0.f; /** Vitesse courante, par seconde. */ float Velocity = 0.f; /** Impulsion pas encore versée, étalée sur RampTime pour éviter l'à-coup. */ float PendingImpulse = 0.f; void AddImpulse(float Strength) { PendingImpulse += Strength; } /** Remet le ressort au repos, sans transition (respawn, téléportation). */ void Reset() { Offset = 0.f; Velocity = 0.f; PendingImpulse = 0.f; } /** * Intègre le ressort d'un pas de temps et renvoie le nouvel Offset. * * @param Frequency fréquence propre en Hz : vitesse de retour au repos. * @param Damping 1 = retour net sans rebond, en dessous ça oscille. * @param RampTime durée sur laquelle une impulsion est versée (0 = d'un coup). * @param MaxOffset garde-fou d'amplitude. */ float Update(float Step, float Frequency, float Damping, float RampTime, float MaxOffset); }; /** * 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; virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const 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 = "Crafting") UCraftingComponent* GetCraftingComponent() const { return CraftingComponent; } 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); /** * Meme chose, depuis un inventaire quelconque : celui d'un coffre ouvert, * typiquement. * * C'est le PAWN qui pose au sol et pas le composant, pour la meme raison * que le surplus de fabrication : un UInventoryComponent ne sait pas * engendrer un acteur, et un coffre qui deposerait lui-meme laisserait le * butin dans sa propre caisse. L'objet part donc toujours devant les yeux * du joueur, quel que soit le contenant d'ou il sort. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") bool DropFromInventory(UInventoryComponent* Inventory, 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); /** * Recopie les reglages du joueur dans le pawn. * * Appelee au BeginPlay ET a chaque fois que USurvivalUserSettings diffuse : * apres un respawn le pawn est un NOUVEL acteur, une valeur poussee une * seule fois serait perdue avec le cadavre. UFUNCTION obligatoire, c'est ce * qui permet le AddDynamic sur le delegue. */ UFUNCTION() void ApplyUserSettings(); /** * Pose la transform de la camera juste avant que la vue soit lue. * * Le gestionnaire de camera passe par ici pour construire le point de vue : * c'est le seul endroit ou l'on est certain de lire la position de l'acteur * et la demi-hauteur de la capsule au MEME instant. Le Tick du pawn, lui, * n'a aucune dependance de tick vis-a-vis du CharacterMovement (verifie dans * Character.cpp : seul le Mesh en recoit une), donc la frame ou la capsule * change de taille pouvait etre calculee avec l'ancienne demi-hauteur -- * soit un decalage de 28 cm sur une frame, a chaque accroupissement. * Bonus : le regard n'a plus une frame de retard sur la souris. */ virtual void CalcCamera(float DeltaTime, FMinimalViewInfo& OutResult) override; protected: // Called when the game starts or when spawned virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) 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 FirstPersonCamera; /** Détecte et déclenche les interactions avec le monde. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Interaction") TObjectPtr 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 InventoryComponent; /** * Fabrication. Sur le pawn comme l'inventaire : c'est ce corps qui fabrique, * et il lit l'inventaire du meme acteur. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Crafting") TObjectPtr CraftingComponent; /** Vie, faim, soif. Sur le pawn : c'est ce corps qui a faim. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Survie") TObjectPtr SurvivalStats; // ------------------------------------------------------------------ // Enhanced Input : assets à assigner dans le Blueprint // ------------------------------------------------------------------ /** Contexte ajouté au joueur au BeginPlay (IMC_Default). */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr DefaultMappingContext; /** Déplacement, valeur Axis2D (X = droite/gauche, Y = avant/arrière). */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr MoveAction; /** Regard souris / stick droit, valeur Axis2D. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr LookAction; /** Saut, valeur Digital (bool). */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr JumpAction; /** Sprint, valeur Digital (bool) — maintenu. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr SprintAction; /** Accroupi, valeur Digital (bool) — maintenu. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr CrouchAction; /** Interaction, valeur Digital (bool). */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input") TObjectPtr 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 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 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. * * ATTENTION : ces trois valeurs, comme BaseFOV, sont ECRASEES au BeginPlay * par USurvivalUserSettings. Elles ne servent plus que de repli si l'objet * de réglages est absent ; pour changer la sensibilité, passe par le menu. */ 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 bascule. Positif = le regard plonge. * * Le tilt n'est plus une courbe indexée sur l'avancement de la transition * mais un ressort relancé à chaque changement d'état : c'est ce qui permet * de s'accroupir et de se relever coup sur coup sans que la caméra saute. * L'impulsion est normalisée pour que le pic tombe bien sur cette valeur. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi") float CrouchTiltPitch = 3.f; /** * Roulis (degrés) au pic de la bascule : bascule sur le côté. * A 0 par defaut, le mouvement reste purement vertical. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi") float CrouchTiltRoll = 0.f; /** * Fréquence du ressort de bascule (Hz). Le pic est atteint après environ * 1/(2*PI*f) secondes : 0.9 Hz place le sommet vers 0.18 s, soit à peu près * au milieu d'une transition de 0.4 s. Plus haut = plus sec et plus court. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0.1")) float CrouchTiltFrequency = 0.9f; /** Amortissement de la bascule. 1 = aucun rebond ; en dessous, la tête oscille. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0.05", ClampMax = "2.0")) float CrouchTiltDamping = 1.f; /** * Durée (s) sur laquelle l'impulsion de bascule est versée dans le ressort. * A 0 la rotation démarre à pleine vitesse angulaire en une frame, ce qui * se lit comme un claquement ; quelques centièmes suffisent à l'adoucir. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Accroupi", meta = (ClampMin = "0")) float CrouchTiltRampTime = 0.05f; // ------------------------------------------------------------------ // 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(); /** * Relachement de la touche de sprint / d'accroupissement. * * Separes de StopSprint et StopCrouch, qui restent des ordres inconditionnels : * en mode bascule le relachement ne doit rien faire, alors que la mort ou * l'ouverture de l'inventaire doivent bel et bien couper le sprint. */ void HandleSprintReleased(); void HandleCrouchReleased(); /** * 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: // ------------------------------------------------------------------ // Réseau // // Jeter et utiliser engendrent ou détruisent des choses dans le monde : // ce sont des actions d'autorité. Le routage vit dans les fonctions // publiques elles-mêmes, pour que ni l'UI ni l'input n'aient à savoir // s'ils tournent chez l'hôte ou chez un client. // ------------------------------------------------------------------ UFUNCTION(Server, Reliable) void Server_DropFromInventory(UInventoryComponent* Inventory, int32 SlotIndex, int32 Quantity); /** * Le slot est passé explicitement : SelectedHotbarIndex n'est PAS répliqué, * c'est une intention purement locale. Le serveur ne peut donc pas deviner * ce que le joueur avait en main. */ UFUNCTION(Server, Reliable) void Server_UseItem(int32 SlotIndex); /** L'utilisation réelle, côté autorité. */ void PerformUseItem(int32 SlotIndex); /** * Point d'entrée unique du sprint : applique en local ET prévient le serveur. * * Sans le second, le serveur croirait le client à `WalkSpeed` et corrigerait * sa position à chaque pas — c'est le rubber-banding classique, et il ne se * voit que sur une machine distante, jamais chez l'hôte. */ void SetSprinting(bool bNewSprinting); UFUNCTION(Server, Reliable) void Server_SetSprinting(bool bNewSprinting); /** La vitesse suit l'état, y compris sur les pawns des autres joueurs. */ UFUNCTION() void OnRep_IsSprinting(); void ApplySprintSpeed(); /** Fait avancer toutes les animations de caméra (intégration uniquement). */ void UpdateCameraEffects(float DeltaTime); /** * Recopie l'état des animations dans la transform de la caméra. * Séparée de l'intégration : appelée à la fois en fin de Tick et depuis * CalcCamera, elle doit rester idempotente et sans notion de temps. */ void ApplyCameraTransform(); /** Relance la bascule de tête. Direction : +1 on s'accroupit, -1 on se relève. */ void AddCrouchTilt(float Direction); /** Branche sur OnDied du composant de survie. */ UFUNCTION() void HandleDeath(); /** * Le resultat d'une fabrication n'est pas entre dans l'inventaire : on le * pose aux pieds du joueur plutot que de le laisser disparaitre. * * C'est le pawn qui fait ca et non UCraftingComponent : le composant n'a pas * a savoir comment on engendre un objet dans le monde, et un etabli qui * porterait le meme composant deposerait son surplus sur sa propre table. */ UFUNCTION() void HandleCraftOverflow(UItemDataAsset* Item, int32 Quantity, int32 RemainingUses); /** 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; /** Déplacement vertical de la caméra (cm) : sauts, atterrissages, explosions. */ FCameraSpring PunchSpring; /** * Bascule de tête pendant l'accroupissement, en unités normalisées : * son pic vaut 1, ce sont CrouchTiltPitch et CrouchTiltRoll qui le * convertissent en degrés. */ FCameraSpring CrouchTiltSpring; /** Vitesse verticale mémorisée pendant la chute, pour calibrer l'atterrissage. */ float LastFallSpeed = 0.f; /** Répliqué pour que les autres joueurs voient la bonne vitesse et la bonne anim. */ UPROPERTY(ReplicatedUsing = OnRep_IsSprinting) bool bIsSprinting = false; /** Évite de spammer le log si IA_Look est mal configurée. */ bool bLoggedLookTypeWarning = false; // ------------------------------------------------------------------ // Copies locales des réglages joueur, rafraîchies par ApplyUserSettings // ------------------------------------------------------------------ /** 0 = caméra parfaitement fixe, 1 = amplitude d'origine. */ float CameraShakeMultiplier = 1.f; bool bSprintToggleMode = false; bool bCrouchToggleMode = false; };