(Feat) Add Animation Fps Charater

This commit is contained in:
2026-08-13 22:07:08 +02:00
parent 176beab9fc
commit cca54a5e0d
157 changed files with 850 additions and 148 deletions
@@ -3,13 +3,13 @@
using UnrealBuildTool;
using System.Collections.Generic;
public class Survival_projetTarget : TargetRules
public class EmberWildTarget : TargetRules
{
public Survival_projetTarget(TargetInfo Target) : base(Target)
public EmberWildTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V7;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_8;
ExtraModuleNames.Add("Survival_projet");
ExtraModuleNames.Add("EmberWild");
}
}
@@ -2,9 +2,9 @@
using UnrealBuildTool;
public class Survival_projet : ModuleRules
public class EmberWild : ModuleRules
{
public Survival_projet(ReadOnlyTargetRules Target) : base(Target)
public EmberWild(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
+6
View File
@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "EmberWild.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, EmberWild, "EmberWild" );
@@ -0,0 +1,43 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AnimNotify_ItemAction.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/World.h"
#include "FpsPlayer.h"
void UAnimNotify_ItemAction::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference)
{
Super::Notify(MeshComp, Animation, EventReference);
if (!MeshComp)
{
return;
}
// L'editeur d'animation joue les notifies dans sa fenetre de previsualisation,
// sur un mesh de preview qui n'appartient a aucun pawn. Le Cast ci-dessous
// suffirait a s'en proteger, mais le monde de preview n'est pas un monde de
// jeu et rien de ce qui suit n'y aurait de sens : on s'arrete plus tot.
const UWorld* World = MeshComp->GetWorld();
if (!World || !World->IsGameWorld())
{
return;
}
// Le montage tourne sur les bras ou sur le corps, deux composants du pawn :
// dans les deux cas le proprietaire du mesh EST le personnage.
if (AFpsPlayer* Player = Cast<AFpsPlayer>(MeshComp->GetOwner()))
{
Player->TriggerHeldItemAction();
}
}
FString UAnimNotify_ItemAction::GetNotifyName_Implementation() const
{
// Ce qui s'affiche sur le marqueur dans la timeline du montage. Sans cette
// surcharge le moteur y ecrirait le nom de la classe, prefixe compris.
return TEXT("Item Action");
}
@@ -58,6 +58,7 @@ void UFpsArmsAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
bIsSprinting = Player->IsSprinting();
bIsFalling = Movement->IsFalling();
LandingImpact = Player->GetLandingImpact();
bIsCrouching = Movement->IsCrouching();
VerticalSpeed = Movement->Velocity.Z;
@@ -249,6 +249,11 @@ void AFpsPlayer::Tick(float DeltaTime)
if (GetCharacterMovement()->IsFalling())
{
LastFallSpeed = GetCharacterMovement()->Velocity.Z;
// Et on efface la reception precedente des la premiere image de vol :
// c'est ce qui permet aux transitions de la machine a etats de se
// contenter de comparer l'enum, sans verifier bIsFalling par-dessus.
LandingImpact = ELandingImpact::None;
}
UpdateCameraEffects(DeltaTime);
@@ -499,17 +504,55 @@ void AFpsPlayer::Landed(const FHitResult& Hit)
FallSpeed, LandPunchMinFallSpeed);
}
// Les degats decident de tout : c'est la meme valeur qui fait perdre de la
// vie et qui fait encaisser les bras. Une reception lourde est, par
// definition, une chute qui a fait mal.
const float Damage = ComputeFallDamage(FallSpeed);
// Pas de garde d'autorite ici : USurvivalStatsComponent::ApplyDamage la pose
// deja, et la dupliquer donnerait deux endroits ou la regle peut changer.
// Chez un client l'appel ne fait donc rien -- sa vie descend par OnRep_Stats.
if (Damage > 0.f && SurvivalStats)
{
SurvivalStats->ApplyDamage(Damage);
}
// Le geste des bras suit le meme seuil bas que la camera : une marche
// d'escalier ne doit ni secouer la vue ni faire encaisser le personnage.
if (FallSpeed > LandPunchMinFallSpeed)
// Sous ce seuil l'enum reste a None, et la machine a etats repart droit de
// la chute vers la locomotion sans jouer de reception.
if (FallSpeed <= LandPunchMinFallSpeed)
{
UAnimMontage* Montage = (FallSpeed >= HardLandingFallSpeed) ? HardLandMontage : LandMontage;
PlayArmsMontage(Montage);
LandingImpact = ELandingImpact::None;
}
else
{
LandingImpact = (Damage > 0.f) ? ELandingImpact::Hard : ELandingImpact::Soft;
}
LastFallSpeed = 0.f;
}
float AFpsPlayer::ComputeFallDamage(float FallSpeed) const
{
// Comparaison STRICTE : a vitesse nulle comme a toute vitesse sous le seuil,
// la chute ne coute rien. Avec un >=, un FallDamageMinSpeed mis a zero en
// PIE ferait perdre de la vie a chaque pas.
if (FallSpeed <= FallDamageMinSpeed)
{
return 0.f;
}
// Lineaire en VITESSE et pas en energie : la vitesse est ce que le joueur
// percoit -- le temps qu'il passe a tomber -- alors qu'un modele en v²
// rendrait les dix derniers metres brutalement mortels sans que rien a
// l'ecran ne l'ait annonce.
return FMath::GetMappedRangeValueClamped(
FVector2D(FallDamageMinSpeed, FMath::Max(FallDamageMaxSpeed, FallDamageMinSpeed + 1.f)),
FVector2D(FallDamageMin, FallDamageMax),
FallSpeed);
}
void AFpsPlayer::OnJumped_Implementation()
{
Super::OnJumped_Implementation();
@@ -692,18 +735,67 @@ void AFpsPlayer::HandleSprintReleased()
void AFpsPlayer::UseSelectedItem()
{
const int32 SelectedIndex = InventoryComponent->GetSelectedHotbarIndex();
// La case est figee ICI, a l'appui, et relue a l'impact : voir
// PendingUseSlotIndex.
PendingUseSlotIndex = InventoryComponent->GetSelectedHotbarIndex();
// Le geste part AVANT le routage reseau, et sans rien attendre de lui : il
// n'existe que sur cette machine (bras en OnlyOwnerSee) et doit repondre au
// clic dans la frame. Le faire transiter par le serveur ajouterait un
// aller-retour a la seule partie du systeme que le joueur ressent
// immediatement.
//
// Consequence assumee : le geste se joue meme si le serveur refuse ensuite
// l'action. Sur un coup de pioche c'est invisible ; pour un consommable qui
// n'existe plus, c'est OnRep_Slots qui remet l'affichage d'aplomb.
const EUseMontageResult Result = HeldItemComponent
? HeldItemComponent->PlayUseMontage()
: EUseMontageResult::NoMontage;
switch (Result)
{
case EUseMontageResult::Started:
// L'effet appartient desormais au montage : il partira de son marqueur,
// au moment ou la hache touche. Rien a faire de plus a l'appui.
return;
case EUseMontageResult::AlreadyPlaying:
// Marteler le bouton ne relance pas le bras ; il ne doit pas davantage
// declencher l'effet, sinon un clic maintenu recolterait a chaque image
// pendant que le geste, lui, n'en joue qu'un.
return;
default:
break;
}
// Aucun montage a jouer : personne n'enverra de marqueur, l'effet part tout
// de suite. C'est le comportement d'avant, conserve pour les objets sans
// animation -- la plupart des consommables aujourd'hui.
TriggerHeldItemAction();
}
void AFpsPlayer::TriggerHeldItemAction()
{
// Le montage d'usage ne tourne aujourd'hui que sur les bras du joueur local,
// donc cette garde ne rejette rien. Elle est la pour le jour ou un montage
// sera joue sur le CORPS, visible des quatre machines : sans elle, chaque
// coup de hache partirait en autant d'exemplaires qu'il y a de joueurs.
if (!IsLocallyControlled() || !InventoryComponent)
{
return;
}
// L'hote agit tout de suite, le client demande. On envoie meme si le slot
// parait vide chez nous : c'est le serveur qui fait foi, et son inventaire
// peut avoir une frame d'avance sur le notre.
if (!HasAuthority())
{
Server_UseItem(SelectedIndex);
Server_UseItem(PendingUseSlotIndex);
return;
}
PerformUseItem(SelectedIndex);
PerformUseItem(PendingUseSlotIndex);
}
void AFpsPlayer::Server_UseItem_Implementation(int32 SlotIndex)
@@ -1028,6 +1120,15 @@ void AFpsPlayer::OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdj
CrouchTargetAlpha = 1.f;
AddCrouchTilt(1.f);
// Le geste des bras part d'ici et pas d'une transition d'AnimBP : c'est le
// seul endroit qui soit certain que la capsule change VRAIMENT de taille.
// Le CharacterMovement peut refuser un accroupissement, et une machine a
// etats qui suivrait la touche jouerait alors un geste sans rien derriere.
if (HeldItemComponent)
{
HeldItemComponent->PlayCrouchMontage(/*bEntering=*/true);
}
}
void AFpsPlayer::OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
@@ -1036,4 +1137,11 @@ void AFpsPlayer::OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjus
CrouchTargetAlpha = 0.f;
AddCrouchTilt(-1.f);
// Symetrique exact d'OnStartCrouch : se relever peut etre refuse par un
// plafond bas, et c'est le moteur qui tranche, pas la touche.
if (HeldItemComponent)
{
HeldItemComponent->PlayCrouchMontage(/*bEntering=*/false);
}
}
@@ -123,6 +123,13 @@ void UHeldItemComponent::RefreshFromInventory()
const FInventorySlot Selected = Inventory->GetSelectedSlot();
SetHeldItem(Selected.IsEmpty() ? nullptr : Selected.Item.Get());
// Reposee meme quand SetHeldItem est sortie tot faute de changement : chez un
// client, les premiers rafraichissements tombent avant que le controller soit
// la, donc avant qu'on ait le droit de toucher aux bras. Sans ce rattrapage,
// un joueur qui reapparait avec sa pioche deja selectionnee garderait les
// cycles mains nues jusqu'a ce qu'il change de case.
UpdateArmsAnimLayer();
}
void UHeldItemComponent::SetHeldItem(UItemDataAsset* NewItem)
@@ -134,6 +141,11 @@ void UHeldItemComponent::SetHeldItem(UItemDataAsset* NewItem)
HeldItem = NewItem;
ApplyHeldVisuals();
// La couche AVANT le montage : le geste de sortie doit se superposer aux
// cycles du nouvel outil, pas a ceux qu'on vient de quitter. L'inverse se
// voit sur la premiere image, la main partant de la mauvaise pose.
UpdateArmsAnimLayer();
PlayEquipMontage();
// En local d'abord, pour que l'objet apparaisse sous les doigts sans attendre
@@ -145,29 +157,141 @@ void UHeldItemComponent::SetHeldItem(UItemDataAsset* NewItem)
}
}
void UHeldItemComponent::UpdateArmsAnimLayer()
{
AFpsPlayer* Player = GetOwningPlayer();
if (!Player || !Player->IsLocallyControlled())
{
return;
}
USkeletalMeshComponent* Arms = Player->GetFirstPersonArms();
if (!Arms)
{
return;
}
// Le mesh REELLEMENT monte fait foi, pas le seul HeldItem : un objet dont on
// a oublie de cocher bShowInHand ferait sinon jouer les cycles de la pioche
// sur une main vide.
const TSubclassOf<UAnimInstance> DesiredLayer =
(bItemVisibleInHand && HeldItem) ? HeldItem->ArmsAnimLayer : nullptr;
if (DesiredLayer == LinkedArmsLayer)
{
return;
}
// Delier AVANT de lier, toujours. LinkAnimClassLayers ajoute une couche sans
// retirer la precedente : enchainer hache puis pioche laisserait les deux en
// place, et la hache l'emporterait sur toute fonction que la pioche
// n'implemente pas. Aucun message ne le signale.
if (LinkedArmsLayer)
{
Arms->UnlinkAnimClassLayers(LinkedArmsLayer);
}
if (DesiredLayer)
{
Arms->LinkAnimClassLayers(DesiredLayer);
}
LinkedArmsLayer = DesiredLayer;
}
void UHeldItemComponent::PlayEquipMontage()
{
// Ranger un objet ne joue rien : le pack ne livre que des gestes de sortie
// d'outil, et un desequipement sans animation ne se remarque pas -- l'objet
// suivant arrive dans la foulee avec le sien.
if (!HeldItem || !HeldItem->EquipMontage)
if (!HeldItem)
{
return;
}
PlayLocalArmsMontage(HeldItem->EquipMontage, /*bSkipIfAlreadyPlaying=*/false);
}
EUseMontageResult UHeldItemComponent::PlayUseMontage()
{
// Le test porte sur l'OBJET et pas sur le mesh monte : une pomme n'apparait
// pas dans la main (bShowInHand faux) mais elle occupe bien la case active,
// et la manger ne doit pas declencher un coup de poing.
UAnimMontage* const Montage = HeldItem ? HeldItem->UseMontage.Get() : UnarmedUseMontage.Get();
if (!Montage)
{
return EUseMontageResult::NoMontage;
}
// Le geste deja en cours est distingue AVANT l'appel, parce que
// PlayLocalArmsMontage rend false dans les deux cas et que l'appelant, lui,
// doit les separer : l'un enverra son marqueur d'impact, l'autre jamais.
const AFpsPlayer* Player = GetOwningPlayer();
const USkeletalMeshComponent* Arms = Player ? Player->GetFirstPersonArms() : nullptr;
const UAnimInstance* ArmsAnim = Arms ? Arms->GetAnimInstance() : nullptr;
if (ArmsAnim && ArmsAnim->Montage_IsPlaying(Montage))
{
return EUseMontageResult::AlreadyPlaying;
}
// Chez un pawn distant les bras ne jouent rien (voir PlayLocalArmsMontage) :
// aucun marqueur n'en sortira, d'ou NoMontage plutot qu'un etat a part. La
// garde IsLocallyControlled() du pawn reste seule juge de ce qui part sur
// le reseau.
return PlayLocalArmsMontage(Montage, /*bSkipIfAlreadyPlaying=*/true)
? EUseMontageResult::Started
: EUseMontageResult::NoMontage;
}
bool UHeldItemComponent::PlayCrouchMontage(bool bEntering)
{
UAnimMontage* const Held = bEntering ? HeldEnterCrouchMontage.Get() : HeldExitCrouchMontage.Get();
UAnimMontage* const Unarmed = bEntering ? UnarmedEnterCrouchMontage.Get() : UnarmedExitCrouchMontage.Get();
// Le repli sur la version mains vides est ce qui permet de n'en fournir
// qu'une au depart : les gestes « avec objet » arriveront plus tard sans
// qu'on ait a repasser dans le code.
//
// Le test porte sur le MESH monte et pas sur HeldItem, contrairement au clic
// gauche : ce qui change la posture des genoux, c'est un objet qu'on voit
// dans la main, pas une pomme rangee dans la case active.
UAnimMontage* const Montage = (bItemVisibleInHand && Held) ? Held : Unarmed;
// Pas de bSkipIfAlreadyPlaying : marteler la touche d'accroupissement doit
// relancer le geste, puisque la capsule, elle, change bel et bien de taille
// a chaque fois.
return PlayLocalArmsMontage(Montage, /*bSkipIfAlreadyPlaying=*/false);
}
bool UHeldItemComponent::PlayLocalArmsMontage(UAnimMontage* Montage, bool bSkipIfAlreadyPlaying)
{
if (!Montage)
{
return false;
}
AFpsPlayer* Player = GetOwningPlayer();
// Les bras sont en OnlyOwnerSee : chez l'hote, le pawn de chaque ami porte
// AUSSI un squelette de bras que personne ne rend. Y jouer un montage ferait
// tourner quatre machines a etats pour rien. La garde est deja tenue par
// RefreshFromInventory, on la repose ici parce qu'un futur appelant
// l'oublierait.
// tourner quatre machines a etats pour rien.
if (!Player || !Player->IsLocallyControlled())
{
return;
return false;
}
Player->PlayArmsMontage(HeldItem->EquipMontage);
if (bSkipIfAlreadyPlaying)
{
const USkeletalMeshComponent* Arms = Player->GetFirstPersonArms();
const UAnimInstance* ArmsAnim = Arms ? Arms->GetAnimInstance() : nullptr;
if (ArmsAnim && ArmsAnim->Montage_IsPlaying(Montage))
{
return false;
}
}
return Player->PlayArmsMontage(Montage) > 0.f;
}
void UHeldItemComponent::Server_SetHeldItem_Implementation(UItemDataAsset* NewItem)
@@ -0,0 +1,54 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "AnimNotify_ItemAction.generated.h"
/**
* Declenche l'effet de l'objet en main a l'instant precis ou le geste frappe.
*
* ------------------------------------------------------------------
* Pourquoi un notify plutot que le clic
* ------------------------------------------------------------------
*
* Le clic gauche et le COUP sont deux choses distinctes, separees par le temps
* que met le bras a descendre. Faire partir le trace de recolte sur l'appui
* donne une hache qui abat l'arbre avant de l'avoir touche : le joueur voit le
* resultat, puis l'animation qui aurait du le produire. C'est le meme
* decalage qu'un bruit de pas joue au debut de la foulee.
*
* L'instant de l'impact n'est pas une valeur qu'on puisse ecrire en C++ : il
* appartient a l'ANIMATION, il change d'un montage a l'autre, et il rebouge a
* chaque fois qu'on retouche la courbe. Un delai en secondes sur le DataAsset
* serait a re-regler a la main a chaque nouvelle animation livree -- et faux
* des qu'on toucherait au Play Rate. Le marqueur, lui, suit le montage.
*
* ------------------------------------------------------------------
* Reseau : ce notify ne tombe QUE chez le joueur local
* ------------------------------------------------------------------
*
* Le montage d'usage est joue sur les BRAS, qui sont en OnlyOwnerSee et
* n'existent visuellement que chez le proprietaire du pawn
* (UHeldItemComponent::PlayLocalArmsMontage). Le notify part donc sur une
* seule machine, exactement comme le clic qu'il remplace, et le routage vers
* le serveur reste celui d'avant : AFpsPlayer::TriggerHeldItemAction envoie la
* RPC, le serveur fait autorite.
*
* Corollaire a ne pas oublier le jour ou un montage d'usage sera joue aussi sur
* le CORPS (visible des autres joueurs) : ce montage-la tournerait sur les
* quatre machines, et sans la garde IsLocallyControlled() de
* TriggerHeldItemAction chaque coup de hache partirait en quatre exemplaires.
*/
UCLASS(const, hidecategories = Object, collapsecategories, meta = (DisplayName = "Item Action"))
class EMBERWILD_API UAnimNotify_ItemAction : public UAnimNotify
{
GENERATED_BODY()
public:
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference) override;
virtual FString GetNotifyName_Implementation() const override;
};
@@ -29,7 +29,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAppearanceApplied);
* pseudo, et pour la meme raison : elle doit survivre a la mort du pawn.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UCharacterAppearanceComponent : public UActorComponent
class EMBERWILD_API UCharacterAppearanceComponent : public UActorComponent
{
GENERATED_BODY()
@@ -56,7 +56,7 @@ enum class ECustomizationCategory : uint8
* Classe Abstract : on cree un WBP_CharacterCustomization qui en herite.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UCharacterCustomizationWidget : public UUserWidget
class EMBERWILD_API UCharacterCustomizationWidget : public UUserWidget
{
GENERATED_BODY()
@@ -94,7 +94,7 @@ struct FCharacterBodySet
* hors bornes a une valeur sure.
*/
UCLASS(BlueprintType)
class SURVIVAL_PROJET_API UCharacterPartsDataAsset : public UPrimaryDataAsset
class EMBERWILD_API UCharacterPartsDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
@@ -40,7 +40,7 @@ enum class ECharacterPreviewFraming : uint8
* moment -- une fois en partie.
*/
UCLASS()
class SURVIVAL_PROJET_API ACharacterPreviewActor : public AActor
class EMBERWILD_API ACharacterPreviewActor : public AActor
{
GENERATED_BODY()
@@ -23,7 +23,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConfirmDialogCancelled);
* Classe Abstract : on cree un WBP_ConfirmDialog qui en herite.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UConfirmDialogWidget : public UUserWidget
class EMBERWILD_API UConfirmDialogWidget : public UUserWidget
{
GENERATED_BODY()
@@ -139,7 +139,7 @@ struct FCookingInteraction
* interessante.
*/
UCLASS()
class SURVIVAL_PROJET_API ACookingStation : public AActor, public IInteractable
class EMBERWILD_API ACookingStation : public AActor, public IInteractable
{
GENERATED_BODY()
@@ -70,7 +70,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnCraftOverflow, UItemDataAsset*
* futur etabli pourra porter le meme composant avec son propre livre.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UCraftingComponent : public UActorComponent
class EMBERWILD_API UCraftingComponent : public UActorComponent
{
GENERATED_BODY()
@@ -20,7 +20,7 @@ class UTextBlock;
* afficher autre chose que ce sur quoi le bouton Fabriquer s'appuie.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UCraftingIngredientRowWidget : public UUserWidget
class EMBERWILD_API UCraftingIngredientRowWidget : public UUserWidget
{
GENERATED_BODY()
@@ -21,7 +21,7 @@ class UCraftingRecipeDataAsset;
* la table de craft du jeu.
*/
UCLASS(BlueprintType)
class SURVIVAL_PROJET_API UCraftingRecipeBook : public UPrimaryDataAsset
class EMBERWILD_API UCraftingRecipeBook : public UPrimaryDataAsset
{
GENERATED_BODY()
@@ -63,7 +63,7 @@ struct FCraftIngredient
* recette la debloquerait pour toutes les parties a la fois.
*/
UCLASS(BlueprintType)
class SURVIVAL_PROJET_API UCraftingRecipeDataAsset : public UPrimaryDataAsset
class EMBERWILD_API UCraftingRecipeDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
@@ -19,7 +19,7 @@ class UTextBlock;
* son propre clic, parce que c'est elle qui recoit l'evenement souris.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UCraftingRecipeSlotWidget : public UUserWidget
class EMBERWILD_API UCraftingRecipeSlotWidget : public UUserWidget
{
GENERATED_BODY()
@@ -28,7 +28,7 @@ class UStaticMeshComponent;
* qui change par instance, comme ItemData sur BP_Pickup.
*/
UCLASS()
class SURVIVAL_PROJET_API ACraftingStation : public AActor, public IInteractable
class EMBERWILD_API ACraftingStation : public AActor, public IInteractable
{
GENERATED_BODY()
@@ -27,7 +27,7 @@ class UUniformGridPanel;
* pour relire un etat.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UCraftingWidget : public UUserWidget
class EMBERWILD_API UCraftingWidget : public UUserWidget
{
GENERATED_BODY()
@@ -21,7 +21,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCustomizationCategoryClicked, int
* onze entrees.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UCustomizationCategoryWidget : public UUserWidget
class EMBERWILD_API UCustomizationCategoryWidget : public UUserWidget
{
GENERATED_BODY()
@@ -26,7 +26,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCustomizationOptionClicked, int32
* Classe Abstract : on cree un WBP_CustomizationOption qui en herite.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UCustomizationOptionWidget : public UUserWidget
class EMBERWILD_API UCustomizationOptionWidget : public UUserWidget
{
GENERATED_BODY()
@@ -101,7 +101,7 @@ struct FFootstepGaitSettings
* un pas local joue en 3D depuis les chevilles est attenue et sonne creux.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UFootstepComponent : public UActorComponent
class EMBERWILD_API UFootstepComponent : public UActorComponent
{
GENERATED_BODY()
@@ -108,7 +108,7 @@ struct FFootstepSurfaceSounds
* rende depuis un trace, et il tient dans un octet.
*/
UCLASS(BlueprintType)
class SURVIVAL_PROJET_API UFootstepSoundsDataAsset : public UPrimaryDataAsset
class EMBERWILD_API UFootstepSoundsDataAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
@@ -4,10 +4,9 @@
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "FpsPlayer.h"
#include "FpsArmsAnimInstance.generated.h"
class AFpsPlayer;
/**
* AnimInstance des bras en vue premiere personne.
*
@@ -22,7 +21,7 @@ class AFpsPlayer;
* de pawn que la replication a deja synchronise (vitesse, sprint, accroupi).
*/
UCLASS()
class SURVIVAL_PROJET_API UFpsArmsAnimInstance : public UAnimInstance
class EMBERWILD_API UFpsArmsAnimInstance : public UAnimInstance
{
GENERATED_BODY()
@@ -73,6 +72,19 @@ protected:
UPROPERTY(BlueprintReadOnly, Category = "Movement")
bool bIsFalling = false;
/**
* La reception qui vient d'avoir lieu, ou None tant qu'on est en l'air.
*
* C'est elle qui pilote la SORTIE de l'etat de chute, et il n'y a rien a
* composer autour : les trois valeurs menent aux trois etats de sortie
* (reception normale, reception encaissee, retour direct a la locomotion) et
* s'excluent par construction. Le seuil sous lequel une chute ne merite
* aucune animation vit sur le pawn -- c'est le meme que celui de la secousse
* de camera, pour que les deux effets ne se contredisent jamais.
*/
UPROPERTY(BlueprintReadOnly, Category = "Movement")
ELandingImpact LandingImpact = ELandingImpact::None;
/**
* Vitesse verticale signee (cm/s), positive vers le haut.
*
@@ -102,6 +114,7 @@ protected:
UPROPERTY(BlueprintReadOnly, Category = "Held Item")
bool bHasItemInHand = false;
/**
* Vitesse de lecture du cycle de deplacement, a brancher sur la broche
* Play Rate du noeud d'animation.
@@ -22,6 +22,35 @@ class UItemDataAsset;
class USurvivalStatsComponent;
struct FInputActionValue;
/**
* Ce que vaut la réception qui vient d'avoir lieu.
*
* Produit par le PAWN, qui est le seul à connaître les deux seuils, et republié
* tel quel par UFpsArmsAnimInstance. Un enum plutôt que deux booléens parce que
* les trois cas s'excluent par construction : la machine à états compare, elle
* n'a aucune condition à recomposer, et il n'existe aucune combinaison deux
* transitions seraient valides en même temps c'est-à-dire prises au hasard.
*
* Remis à None dès l'entrée en chute (voir Tick), donc il vaut None pendant tout
* le vol : une transition n'a jamais à vérifier bIsFalling en plus.
*/
UENUM(BlueprintType)
enum class ELandingImpact : uint8
{
/** En l'air, ou chute trop faible pour mériter une animation. */
None,
/** Réception normale (JumpLanding). */
Soft,
/**
* Réception encaissée (HardLanding) c'est-à-dire, très exactement, une
* chute qui a coûté de la vie. Le seuil n'est pas un réglage d'animation,
* c'est `FallDamageMinSpeed`.
*/
Hard
};
/**
* Ressort amorti à une dimension, partagé par les effets de caméra.
*
@@ -70,7 +99,7 @@ struct FCameraSpring
* dans le Blueprint enfant (BP_FpsPlayer), catégorie "Input".
*/
UCLASS()
class SURVIVAL_PROJET_API AFpsPlayer : public ACharacter
class EMBERWILD_API AFpsPlayer : public ACharacter
{
GENERATED_BODY()
@@ -100,6 +129,10 @@ public:
UFUNCTION(BlueprintPure, Category = "Animation")
USkeletalMeshComponent* GetFirstPersonArms() const { return FirstPersonArms; }
/** La réception en cours, ou None en l'air. Lue par les AnimInstance. */
UFUNCTION(BlueprintPure, Category = "Animation")
ELandingImpact GetLandingImpact() const { return LandingImpact; }
/**
* Joue un montage sur les bras du joueur LOCAL, et seulement lui.
*
@@ -322,34 +355,57 @@ protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
TObjectPtr<UAnimMontage> GrabMontage;
/**
* Réception au sol, version légère (MTG_Land).
*
* Un montage et pas un état de la machine : l'atterrissage est un geste
* PONCTUEL qui doit se superposer à ce que les bras faisaient déjà. Un état
* imposerait de décider combien de temps y rester, et retomber en pleine
* course y bloquerait le cycle de foulée le temps de l'animation.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
TObjectPtr<UAnimMontage> LandMontage;
/** Réception lourde (MTG_HardLand), au-delà de HardLandingFallSpeed. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Animation")
TObjectPtr<UAnimMontage> HardLandMontage;
// ------------------------------------------------------------------
// Dégâts de chute
//
// C'est ce bloc qui définit la réception encaissée, et pas l'inverse :
// `ELandingImpact::Hard` signifie exactement « cet atterrissage a coûté de la
// vie ». Un seuil d'animation réglé de son côté finirait forcément par
// diverger de celui des dégâts, et le joueur verrait son personnage encaisser
// sans rien perdre — ou pire, perdre de la vie en se réceptionnant
// souplement. Une animation qui ment sur l'état du jeu est pire que pas
// d'animation du tout.
//
// L'atterrissage est un ÉTAT de la machine et non un montage : les trois
// animations livrées (JumpStart, JumpFallLoop, JumpLanding) forment une
// chaîne dont le milieu est une BOUCLE, et une boucle ne se joue pas en
// montage.
// ------------------------------------------------------------------
/**
* Vitesse de chute (cm/s) à partir de laquelle la réception passe en lourde.
* Vitesse de chute (cm/s) à partir de laquelle on prend des dégâts.
*
* Doit rester nettement au-dessus de la retombée d'un saut sur place
* (~JumpZVelocity, 450 par défaut), sinon chaque saut se termine par une
* réception encaissée et le déplacement devient poussif.
* (~JumpZVelocity, 450 par défaut), sinon sauter coûte de la vie et le
* déplacement devient une punition.
*
* Le seuil BAS, lui, est LandPunchMinFallSpeed : sous cette vitesse aucun
* montage n'est joué, exactement comme la caméra ne bouge pas. Une seule
* valeur à comprendre pour les deux effets, qui restent donc cohérents.
* À 800 cm/s, c'est une chute d'environ 3,2 m (v = racine(2·g·h), g = 980).
* Ne pas raisonner en hauteur pour régler cette valeur : la hauteur ne se
* mesure nulle part dans le moteur, alors que la vitesse d'impact est
* exactement ce que le CharacterMovement a sous la main et elle tient
* compte, gratuitement, d'un saut fait en tombant.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation", meta = (ClampMin = "0"))
float HardLandingFallSpeed = 800.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "0"))
float FallDamageMinSpeed = 800.f;
/** Vitesse (cm/s) au-delà de laquelle les dégâts plafonnent (~20 m de chute). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "1"))
float FallDamageMaxSpeed = 2000.f;
/**
* Dégâts pris pile au seuil. Volontairement non nul : franchir la limite
* doit se sentir, sinon le seuil n'existe que dans le code.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "0"))
float FallDamageMin = 15.f;
/** Dégâts pris à FallDamageMaxSpeed et au-delà. À 100, la chute est mortelle. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fall Damage", meta = (ClampMin = "0"))
float FallDamageMax = 100.f;
/** Les dégâts de la chute qui vient d'avoir lieu, 0 sous le seuil. */
UFUNCTION(BlueprintPure, Category = "Fall Damage")
float ComputeFallDamage(float FallSpeed) const;
/**
* Balancement de tête procédural : respiration à l'arrêt, pas à la marche,
@@ -678,13 +734,42 @@ protected:
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.
* Clic gauche : lance le GESTE de l'objet actif de la barre rapide.
*
* Elle ne produit plus l'effet elle-même c'est le marqueur
* `UAnimNotify_ItemAction` posé dans le montage qui le fait, au moment le
* geste frappe. Sans montage à jouer, elle retombe sur l'effet immédiat :
* un objet qui n'a pas d'animation n'aura jamais de marqueur, et le priver
* d'effet serait un objet cassé plutôt qu'un objet sans animation.
*/
void UseSelectedItem();
public:
/**
* L'objet en main agit MAINTENANT. Appelée par l'AnimNotify du montage.
*
* Publique et `BlueprintCallable` pour cette seule raison : c'est le point
* d'entrée de l'animation vers le gameplay, y compris depuis un notify
* Blueprint. Elle ne fait rien d'autre que ce que faisait la seconde moitié
* de `UseSelectedItem` : router vers l'autorité.
*/
UFUNCTION(BlueprintCallable, Category = "Items")
void TriggerHeldItemAction();
private:
/**
* La case visée par le clic, retenue jusqu'à l'impact.
*
* Relire la case active au moment du marqueur serait faux : trois dixièmes
* de seconde séparent l'appui de l'impact, et la molette peut passer sur un
* autre objet entre-temps le coup de hache partirait alors avec la pomme
* qu'on vient de sélectionner. Changer d'objet coupe bien le montage en
* cours (`PlayEquipMontage` ne saute pas), mais seulement quand le nouvel
* objet a un montage d'équipement : ce n'est pas une garantie sur laquelle
* s'appuyer.
*/
int32 PendingUseSlotIndex = INDEX_NONE;
// ------------------------------------------------------------------
// Réseau
//
@@ -781,6 +866,17 @@ private:
/** Vitesse verticale mémorisée pendant la chute, pour calibrer l'atterrissage. */
float LastFallSpeed = 0.f;
/**
* La réception en cours, lue par les AnimInstance.
*
* Écrite à deux endroits et deux seulement : remise à None dans Tick tant
* qu'on est en l'air, posée à sa vraie valeur dans Landed(). C'est ce qui
* garantit qu'elle vaut None pendant tout le vol sans quoi la machine à
* états prendrait sa transition de réception avec la valeur du saut
* précédent, dès la première image de chute.
*/
ELandingImpact LandingImpact = ELandingImpact::None;
/** Répliqué pour que les autres joueurs voient la bonne vitesse et la bonne anim. */
UPROPERTY(ReplicatedUsing = OnRep_IsSprinting)
bool bIsSprinting = false;
@@ -33,7 +33,7 @@ struct FInputActionValue;
* Si tu creais le HUD dans AFpsPlayer, il disparaitrait a chaque respawn.
*/
UCLASS()
class SURVIVAL_PROJET_API AFpsPlayerController : public APlayerController
class EMBERWILD_API AFpsPlayerController : public APlayerController
{
GENERATED_BODY()
@@ -85,7 +85,7 @@ struct FHeadBobProfile
* la camera.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UHeadBobComponent : public UActorComponent
class EMBERWILD_API UHeadBobComponent : public UActorComponent
{
GENERATED_BODY()
@@ -4,15 +4,41 @@
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ItemDataAsset.h"
#include "HeldItemComponent.generated.h"
class AFpsPlayer;
class UAnimInstance;
class UItemDataAsset;
class UStaticMeshComponent;
/** Diffuse a chaque changement d'objet en main, nul compris. L'UI et l'anim s'y abonnent. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHeldItemChanged, UItemDataAsset*, NewItem);
/**
* Ce qu'a fait PlayUseMontage, du point de vue de l'appelant.
*
* Trois etats et pas un booleen, parce que l'appelant en tire trois conduites
* differentes : un geste qui demarre porte son propre marqueur d'impact et il
* n'y a plus qu'a attendre ; un geste deja en cours en enverra un lui aussi ;
* une absence de montage, elle, ne produira JAMAIS de marqueur et doit retomber
* sur l'effet immediat. Les confondre donne soit un objet sans montage qui ne
* fait plus rien du tout, soit un clic martele qui declenche l'effet a chaque
* image pendant que le bras, lui, ne rejoue pas.
*/
UENUM(BlueprintType)
enum class EUseMontageResult : uint8
{
/** Le geste vient de demarrer : son AnimNotify declenchera l'effet. */
Started,
/** Le meme geste tournait deja ; on ne l'a pas relance. */
AlreadyPlaying,
/** Rien a jouer -- l'objet n'a pas de UseMontage, ou les bras sont distants. */
NoMontage
};
/**
* Montre dans la main l'objet occupant la case active de la barre rapide.
*
@@ -47,7 +73,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHeldItemChanged, UItemDataAsset*,
* clignoter ce qu'il tient deja.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UHeldItemComponent : public UActorComponent
class EMBERWILD_API UHeldItemComponent : public UActorComponent
{
GENERATED_BODY()
@@ -71,6 +97,7 @@ public:
UFUNCTION(BlueprintPure, Category = "Held Item")
bool HasItemInHand() const { return bItemVisibleInHand; }
/**
* Relit la case active et met la main a jour.
*
@@ -81,6 +108,24 @@ public:
UFUNCTION(BlueprintCallable, Category = "Held Item")
void RefreshFromInventory();
/**
* Joue le geste du clic gauche avec l'objet en main.
*
* Appelee par le PAWN au moment de l'input, donc chez le joueur local et
* AVANT tout aller-retour reseau : un coup de pioche qui attendrait la
* reponse du serveur partirait avec un temps de retard sur le clic, et c'est
* la premiere chose qu'on sent dans un jeu.
*
* Il ne fait QUE l'animation : l'effet de l'objet part du marqueur
* UAnimNotify_ItemAction pose dans le montage, pour tomber au moment de
* l'impact et pas a l'appui. D'ou le retour a trois etats, que l'appelant
* doit lire -- voir EUseMontageResult.
*
* @return ce qu'il est advenu du geste.
*/
UFUNCTION(BlueprintCallable, Category = "Held Item")
EUseMontageResult PlayUseMontage();
UPROPERTY(BlueprintAssignable, Category = "Held Item")
FOnHeldItemChanged OnHeldItemChanged;
@@ -99,6 +144,67 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held Item")
FName ThirdPersonSocket = TEXT("hand_r");
/**
* Geste du clic gauche quand les mains sont VIDES (MTG_Punch).
*
* Ici et pas sur un DataAsset, pour la raison evidente qu'il n'y a pas
* d'objet a qui le demander. Ici et pas sur AFpsPlayer a cote de GrabMontage,
* parce que c'est ce composant qui choisit quel geste part au clic : deux
* moities de la meme decision dans deux classes finiraient par diverger.
*
* Il ne sert QUE quand aucun objet n'occupe la case active. Un objet en main
* sans UseMontage ne joue rien -- retomber sur le poing ferait boxer le
* joueur en mangeant une pomme.
*/
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item")
TObjectPtr<class UAnimMontage> UnarmedUseMontage;
// ------------------------------------------------------------------
// Accroupissement
//
// Des MONTAGES et pas des etats de la machine, alors que CrouchIdle et
// CrouchWalk, eux, sont bien des etats. La frontiere est la meme que partout
// ailleurs : ce qui DURE est un etat, ce qui ARRIVE est un montage. Plier les
// genoux prend trois dixiemes de seconde et se superpose au cycle en cours.
//
// Raison technique par-dessus, et elle est decisive : un etat dont la sortie
// depend de la fin de l'animation utilise « Automatic Rule Based on Sequence
// Player in State », qui a besoin d'un Sequence Player pour lire sa duree. Un
// etat qui contiendrait un choix entre deux animations -- avec et sans objet
// -- lui retire cette certitude, et il faudrait ecrire des durees en dur dans
// les transitions, a re-regler a chaque animation livree.
//
// Ici et pas sur UItemDataAsset : le geste est GENERIQUE, deux variantes en
// tout. Le jour ou un outil voudra la sienne, un champ optionnel sur son
// DataAsset primera sur ceux-ci, sans rien casser.
// ------------------------------------------------------------------
/** Geste d'accroupissement, mains vides (MTG_EnterCrouch). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
TObjectPtr<class UAnimMontage> UnarmedEnterCrouchMontage;
/** Meme geste, un objet en main. Vide = on rejoue la version mains vides. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
TObjectPtr<class UAnimMontage> HeldEnterCrouchMontage;
/** Geste de relevee, mains vides (MTG_ExitCrouch). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
TObjectPtr<class UAnimMontage> UnarmedExitCrouchMontage;
/** Meme geste, un objet en main. Vide = on rejoue la version mains vides. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Held Item|Crouch")
TObjectPtr<class UAnimMontage> HeldExitCrouchMontage;
/**
* Joue le geste d'accroupissement ou de relevee, selon ce qu'on tient.
*
* Appelee par AFpsPlayer depuis OnStartCrouch / OnEndCrouch, qui sont deja
* surchargees : le pawn sait exactement quand la capsule change de taille,
* personne n'a a le deviner.
*/
UFUNCTION(BlueprintCallable, Category = "Held Item|Crouch")
bool PlayCrouchMontage(bool bEntering);
protected:
virtual void BeginPlay() override;
virtual void OnUnregister() override;
@@ -137,6 +243,30 @@ private:
*/
void PlayEquipMontage();
/**
* Joue un montage sur les bras du joueur LOCAL, et lui seul.
*
* @param bSkipIfAlreadyPlaying vrai pour le clic gauche : marteler le bouton
* relancerait le geste depuis le debut a chaque image, ce qui donne un
* bras qui tremble au lieu de frapper. Faux pour l'equipement, ou
* changer d'outil doit couper net le geste precedent.
* @return true si un montage a demarre.
*/
bool PlayLocalArmsMontage(class UAnimMontage* Montage, bool bSkipIfAlreadyPlaying);
/**
* Branche ou debranche la couche d'animation de l'objet sur les bras.
*
* Locale elle aussi, et pour la meme raison que le montage : les bras sont en
* OnlyOwnerSee. Chez l'hote, lier une couche sur le squelette de bras d'un ami
* ferait evaluer un graphe que personne ne rend.
*
* Idempotente : elle compare a la couche deja liee et ne fait rien si elle est
* la bonne. C'est ce qui permet de l'appeler a chaque rafraichissement
* d'inventaire sans relancer le graphe.
*/
void UpdateArmsAnimLayer();
/**
* Cache le mesh du CORPS chez le joueur qui controle ce pawn, et lui seul.
*
@@ -192,6 +322,18 @@ private:
*/
bool bItemVisibleInHand = false;
/**
* La couche actuellement liee aux bras, ou nulle.
*
* Indispensable, et pas un simple cache : LinkAnimClassLayers AJOUTE une
* couche, elle ne remplace pas la precedente. Sans garder ce qui est en
* place pour le delier, passer de la hache a la pioche laisserait les deux
* branchees -- et c'est la premiere qui gagnerait sur les fonctions que la
* seconde n'implemente pas.
*/
UPROPERTY(Transient)
TSubclassOf<UAnimInstance> LinkedArmsLayer;
/** Evite de repeter l'avertissement de socket introuvable a chaque montage. */
bool bLoggedMissingSocket = false;
};
@@ -28,7 +28,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnHotbarSlotHovered, UInventoryC
* existant s'en occupe deja sans aucun code supplementaire.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UHotbarWidget : public UUserWidget
class EMBERWILD_API UHotbarWidget : public UUserWidget
{
GENERATED_BODY()
@@ -31,7 +31,7 @@ class UInteractable : public UInterface
* Actor->Interact() directement. Seul le wrapper Execute_ sait router
* l'appel vers une implementation Blueprint.
*/
class SURVIVAL_PROJET_API IInteractable
class EMBERWILD_API IInteractable
{
GENERATED_BODY()
@@ -31,7 +31,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInteractionPromptChanged, const F
* revenir toucher a ce fichier.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UInteractionComponent : public UActorComponent
class EMBERWILD_API UInteractionComponent : public UActorComponent
{
GENERATED_BODY()
@@ -27,7 +27,7 @@ class UTextBlock;
* qui glisse d'un emplacement du grill a l'autre sur le meme acteur.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UInteractionPromptWidget : public UUserWidget
class EMBERWILD_API UInteractionPromptWidget : public UUserWidget
{
GENERATED_BODY()
@@ -58,7 +58,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSelectedHotbarSlotChanged, int32,
* disparaitre en silence.
*/
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class SURVIVAL_PROJET_API UInventoryComponent : public UActorComponent
class EMBERWILD_API UInventoryComponent : public UActorComponent
{
GENERATED_BODY()
@@ -16,7 +16,7 @@ class UInventoryComponent;
* reel au moment du drop au lieu d'appliquer une donnee perimee.
*/
UCLASS()
class SURVIVAL_PROJET_API UInventoryDragDropOperation : public UDragDropOperation
class EMBERWILD_API UInventoryDragDropOperation : public UDragDropOperation
{
GENERATED_BODY()
@@ -36,7 +36,7 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnInventoryScreenTabChanged, bool,
* Classe Abstract : on cree un WBP_InventoryScreen qui en herite.
*/
UCLASS(Abstract)
class SURVIVAL_PROJET_API UInventoryScreenWidget : public UUserWidget
class EMBERWILD_API UInventoryScreenWidget : public UUserWidget
{
GENERATED_BODY()

Some files were not shown because too many files have changed in this diff Show More