(Feat) Add footsteps
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "HeldItemComponent.generated.h"
|
||||
|
||||
class AFpsPlayer;
|
||||
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);
|
||||
|
||||
/**
|
||||
* Montre dans la main l'objet occupant la case active de la barre rapide.
|
||||
*
|
||||
* Un systeme = un composant, comme le reste du projet. Celui-ci ne decide de
|
||||
* rien et ne consomme rien : il regarde l'inventaire et pose deux meshes. Ce que
|
||||
* l'objet FAIT quand on clique reste dans AFpsPlayer::PerformUseItem.
|
||||
*
|
||||
* Deux meshes et pas un, parce qu'il y a deux corps a habiller :
|
||||
*
|
||||
* - les BRAS (FirstPersonArms), visibles du seul proprietaire, attaches a la
|
||||
* camera. C'est la hache qu'on voit soi-meme.
|
||||
* - le CORPS (le Mesh du Character), invisible pour son proprietaire mais vu
|
||||
* par les trois autres joueurs -- et dont on percoit sa propre ombre.
|
||||
*
|
||||
* Les bras recopient du porteur leur « visible pour moi seul ». Le mesh du corps,
|
||||
* lui, se cache par la VISIBILITE chez le seul joueur qui controle ce pawn --
|
||||
* bOwnerNoSee y couperait son ombre, voir UpdateLocalVisibility().
|
||||
*
|
||||
* ------------------------------------------------------------------
|
||||
* Reseau : c'est le CLIENT qui pilote, toujours
|
||||
* ------------------------------------------------------------------
|
||||
*
|
||||
* UInventoryComponent::SelectedHotbarIndex n'est PAS replique -- c'est une
|
||||
* intention purement locale, et il n'y a aucune raison de la mettre sur le
|
||||
* reseau. Le serveur ne peut donc pas deviner ce que le joueur a en main : lire
|
||||
* l'inventaire d'un pawn distant y trouverait toujours la case 0.
|
||||
*
|
||||
* Ce qui se replique est donc l'OBJET lui-meme, pas l'index. Le proprietaire
|
||||
* l'applique en local et l'annonce ; le serveur l'enregistre et le rediffuse aux
|
||||
* autres. Exactement le schema de bIsSprinting, COND_SkipOwner compris : renvoyer
|
||||
* a l'emetteur une valeur qui a un aller-retour de retard ne peut que faire
|
||||
* clignoter ce qu'il tient deja.
|
||||
*/
|
||||
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
|
||||
class SURVIVAL_PROJET_API UHeldItemComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UHeldItemComponent();
|
||||
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/** L'objet actuellement montre dans la main. Nul = les mains sont vides. */
|
||||
UFUNCTION(BlueprintPure, Category = "Held Item")
|
||||
UItemDataAsset* GetHeldItem() const { return HeldItem; }
|
||||
|
||||
/**
|
||||
* Vrai quand un mesh est effectivement monte dans la main.
|
||||
*
|
||||
* Different de `GetHeldItem() != nullptr`, et la nuance compte pour
|
||||
* l'animation : la plupart des ressources du jeu n'ont pas bShowInHand, donc
|
||||
* les tenir ne met rien dans la paume. Se fier a l'objet ferait jouer la
|
||||
* posture « objet en main » sur des mains vides, ce qui se voit tout de suite.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Held Item")
|
||||
bool HasItemInHand() const { return bItemVisibleInHand; }
|
||||
|
||||
/**
|
||||
* Relit la case active et met la main a jour.
|
||||
*
|
||||
* Sans effet ailleurs que chez le joueur local : ce composant est le seul du
|
||||
* projet dont la SOURCE de verite vive uniquement sur la machine du
|
||||
* proprietaire. Idempotente, donc appelable a la volee.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Held Item")
|
||||
void RefreshFromInventory();
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Held Item")
|
||||
FOnHeldItemChanged OnHeldItemChanged;
|
||||
|
||||
/**
|
||||
* Os ou socket des BRAS auquel l'objet s'accroche.
|
||||
*
|
||||
* C'est un OS du rig et pas un socket taille sur mesure, d'ou les offsets
|
||||
* par objet dans UItemDataAsset : creer un socket par outil demanderait de
|
||||
* rouvrir le squelette a chaque nouvel objet, alors que l'offset se regle
|
||||
* dans le DataAsset qu'on est deja en train d'ecrire.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held Item")
|
||||
FName FirstPersonSocket = TEXT("RightHand_Holder_JTN");
|
||||
|
||||
/** Os de la main droite du mannequin UE5, porteur du mesh 3e personne. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held Item")
|
||||
FName ThirdPersonSocket = TEXT("hand_r");
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
virtual void OnUnregister() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* L'objet en main. Un pointeur d'asset se replique comme n'importe quelle
|
||||
* reference d'objet -- c'est deja ce que fait FInventorySlot::Item, et c'est
|
||||
* pour ca qu'on peut se permettre d'envoyer l'objet plutot qu'un index.
|
||||
*
|
||||
* COND_SkipOwner : le proprietaire vient de l'appliquer en local, le lui
|
||||
* renvoyer ecraserait son etat courant par une valeur en retard.
|
||||
*/
|
||||
UPROPERTY(ReplicatedUsing = OnRep_HeldItem)
|
||||
TObjectPtr<UItemDataAsset> HeldItem;
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_HeldItem();
|
||||
|
||||
/** Point d'entree unique : applique en local, puis annonce au serveur. */
|
||||
void SetHeldItem(UItemDataAsset* NewItem);
|
||||
|
||||
UFUNCTION(Server, Reliable)
|
||||
void Server_SetHeldItem(UItemDataAsset* NewItem);
|
||||
|
||||
/** Monte ou demonte les deux meshes selon HeldItem. Tourne sur TOUTES les machines. */
|
||||
void ApplyHeldVisuals();
|
||||
|
||||
/**
|
||||
* Joue le geste de sortie d'outil sur les bras du joueur LOCAL.
|
||||
*
|
||||
* Appelee depuis SetHeldItem et pas depuis ApplyHeldVisuals, alors que les
|
||||
* deux disent « l'objet en main a change » : ApplyHeldVisuals tourne sur
|
||||
* toutes les machines (OnRep, serveur), et un geste de bras n'a de sens que
|
||||
* chez celui qui les voit.
|
||||
*/
|
||||
void PlayEquipMontage();
|
||||
|
||||
/**
|
||||
* Cache le mesh du CORPS chez le joueur qui controle ce pawn, et lui seul.
|
||||
*
|
||||
* Par la VISIBILITE et non par bOwnerNoSee : sur un UStaticMeshComponent,
|
||||
* bCastHiddenShadow n'honore que la premiere -- cache par le proprietaire,
|
||||
* l'objet perd son ombre en meme temps que son mesh. Detail dans le .cpp.
|
||||
*/
|
||||
void UpdateLocalVisibility();
|
||||
|
||||
/**
|
||||
* Cree les deux composants de mesh s'ils manquent.
|
||||
*
|
||||
* A la demande et pas dans le constructeur : le composant n'a alors aucune
|
||||
* hypothese sur l'ordre de creation des sous-objets d'AFpsPlayer, et un pawn
|
||||
* qui ne tient jamais rien ne paie pas deux composants pour rien.
|
||||
*/
|
||||
void EnsureHeldMeshComponents();
|
||||
|
||||
/**
|
||||
* Cree un mesh d'objet en main attache a Parent, ou nullptr si impossible.
|
||||
*
|
||||
* @param bCastWorldShadow l'objet projette-t-il une ombre dans le monde.
|
||||
* Vrai pour la main du corps, faux pour les bras -- un mesh colle a
|
||||
* l'objectif projetterait une ombre enorme en travers du decor.
|
||||
*/
|
||||
UStaticMeshComponent* CreateHeldMeshComponent(FName ComponentName, class USkeletalMeshComponent* Parent,
|
||||
FName SocketName, bool bCastWorldShadow);
|
||||
|
||||
/** Les deux delegues d'inventaire menent ici. */
|
||||
UFUNCTION()
|
||||
void HandleInventoryChanged();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleSelectedHotbarSlotChanged(int32 NewIndex);
|
||||
|
||||
/** Le pawn porteur, ou nullptr si le composant vit ailleurs. */
|
||||
AFpsPlayer* GetOwningPlayer() const;
|
||||
|
||||
/** Mesh vu par le seul proprietaire, accroche aux bras. */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UStaticMeshComponent> FirstPersonHeldMesh;
|
||||
|
||||
/** Mesh vu par les autres joueurs, accroche a la main du corps. */
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UStaticMeshComponent> ThirdPersonHeldMesh;
|
||||
|
||||
/**
|
||||
* Un mesh est-il reellement monte en main, en cache.
|
||||
*
|
||||
* Recopie de la decision prise par ApplyHeldVisuals plutot que recalculee :
|
||||
* l'AnimInstance la lit a chaque frame, et surtout deux facons de repondre a
|
||||
* la meme question finiraient par diverger.
|
||||
*/
|
||||
bool bItemVisibleInHand = false;
|
||||
|
||||
/** Evite de repeter l'avertissement de socket introuvable a chaque montage. */
|
||||
bool bLoggedMissingSocket = false;
|
||||
};
|
||||
Reference in New Issue
Block a user