(Feat) Add Animation Fps Charater
This commit is contained in:
@@ -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
-2
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
+1
@@ -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;
|
||||
|
||||
+114
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
+130
-6
@@ -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;
|
||||
};
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+16
-3
@@ -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 où 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 où 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;
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+143
-1
@@ -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;
|
||||
};
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user