// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "CharacterAppearanceTypes.h" #include "CharacterAppearanceComponent.generated.h" class UCharacterPartsDataAsset; class USkeletalMeshComponent; struct FCharacterBodySet; /** Diffuse a chaque apparence appliquee, pour que l'UI se rafraichisse. */ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAppearanceApplied); /** * Monte un personnage a partir d'une FCharacterAppearance : assigne les meshes, * les met en leader pose, et pose les couleurs sur des materiaux dynamiques. * * Un systeme = un composant, comme le reste du projet. Mais celui-ci a une * particularite qui vaut d'etre tenue : il ne suppose PAS vivre sur le pawn. Il * cherche le mesh d'un ACharacter s'il en trouve un, accepte qu'on lui en * designe un autre, et fonctionne donc a l'identique sur le mannequin de l'ecran * de customisation. C'est ce qui garantit que le personnage montre au joueur et * celui que ses amis verront ne peuvent pas diverger : c'est le meme code. * * Il ne replique rien -- l'apparence vivra sur ASurvivalPlayerState, comme le * 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 { GENERATED_BODY() public: UCharacterAppearanceComponent(); virtual void BeginPlay() override; UPROPERTY(BlueprintAssignable, Category = "Appearance") FOnAppearanceApplied OnAppearanceApplied; /** Ton DA_CharacterParts. Sans lui le composant ne monte rien et le dit. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance") TObjectPtr Catalog; /** * L'apparence posee au BeginPlay, tant que rien ne l'a remplacee. * * Elle sert au mannequin de l'ecran, aux tests en PIE, et de repli si les * reglages du joueur sont vides -- c'est-a-dire au tout premier lancement. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance") FCharacterAppearance DefaultAppearance; /** * Designe le mesh porteur. A appeler AVANT la premiere application. * * Inutile sur un ACharacter, dont le mesh est trouve tout seul. Necessaire * sur un acteur quelconque -- le mannequin de l'ecran de customisation. */ UFUNCTION(BlueprintCallable, Category = "Appearance") void SetTargetMesh(USkeletalMeshComponent* InTargetMesh); /** * Monte l'apparence demandee. Idempotente, et bon marche a la repetition : * les meshes ne sont retouches que si une PIECE a change, jamais pour une * simple couleur. C'est ce qui permet a un curseur d'appeler cette fonction * a chaque image sans engendrer un materiau par frame. */ UFUNCTION(BlueprintCallable, Category = "Appearance") void ApplyAppearance(const FCharacterAppearance& InAppearance); UFUNCTION(BlueprintPure, Category = "Appearance") const FCharacterAppearance& GetAppearance() const { return Appearance; } // ------------------------------------------------------------------ // Reglage par nom -- ce qui permet de tester le montage avant qu'il // existe la moindre interface. // // Ces fonctions ne portent PAS le specificateur Exec, et ce n'est pas un // oubli : la chaine de routage des commandes console (voir Player.cpp) // interroge le monde, le PlayerInput, le controller, le pawn, le HUD, le // GameMode, le CheatManager, le GameState et le camera manager -- jamais // les composants, AActor ne surchargeant pas ProcessConsoleExec pour les // parcourir. Une UFUNCTION(Exec) posee ici serait silencieusement ignoree. // Les commandes vivent donc sur AFpsPlayer, qui delegue ici. // ------------------------------------------------------------------ /** * Cherche, parmi les slots de materiau du mesh de tete, celui dont le nom * contient SlotHint. INDEX_NONE si aucun -- l'appelant ne pose alors rien. */ static int32 FindEyeMaterialSlot(const USkeletalMeshComponent* HeadComponent, FName SlotHint); /** Categories : body, head, hair, eyebrows, beard, mustache, eyes. */ UFUNCTION(BlueprintCallable, Category = "Appearance|Debug") void ApplyPartByName(const FString& Part, int32 Index); /** Categories : skin, hair, eye, underwear. */ UFUNCTION(BlueprintCallable, Category = "Appearance|Debug") void ApplyColorByName(const FString& Part, int32 ColorIndex); /** Ecrit l'apparence courante et le nombre d'options de chaque categorie. */ UFUNCTION(BlueprintCallable, Category = "Appearance|Debug") void LogAppearance() const; protected: virtual void OnUnregister() override; private: /** * Trouve le mesh porteur : celui de l'ACharacter en priorite, sinon le * premier SkeletalMesh de l'acteur. * * L'ordre compte et n'est pas anodin : AFpsPlayer porte AUSSI FirstPersonArms, * et prendre « le premier trouve » y monterait la tete du personnage sur les * bras vus a l'ecran. */ USkeletalMeshComponent* ResolveTargetMesh(); /** Cree le composant d'une categorie s'il manque, et le rend. */ USkeletalMeshComponent* EnsurePartComponent(ECharacterPartCategory Category); /** Cree un composant enfant deja attache, leader-pose et configure. */ USkeletalMeshComponent* CreateFollowerComponent(FName ComponentName); /** Assigne les meshes de corps, l'AnimBP, les pieces et le materiau d'iris. */ void RebuildMeshes(const FCharacterBodySet& BodySet); /** Pose les quatre couleurs sur les materiaux dynamiques. */ void ApplyColors(const FCharacterBodySet& BodySet); /** Pose un parametre vectoriel sur TOUS les elements d'un mesh. */ /** * Assigne un mesh a un composant en vidant d'abord ses materiaux surcharges. * * Indispensable : OverrideMaterials appartient au COMPOSANT, pas au mesh, et * survit donc a un changement d'asset. Sans ce vidage, le corps B garderait * la peau du corps A. */ static void SetPartMesh(USkeletalMeshComponent* MeshComponent, USkeletalMesh* NewMesh); static void TintAllElements(USkeletalMeshComponent* MeshComponent, FName Parameter, const FLinearColor& Color); /** Le mesh porteur, leader de tous les autres. */ UPROPERTY(Transient) TObjectPtr TargetMesh; /** * Un composant par categorie, indexe par ECharacterPartCategory. * * Un tableau plutot qu'un membre nomme par piece : le montage tient alors * dans une boucle, et ajouter une categorie ne demande qu'une valeur * d'enum. Une case peut rester nulle -- une silhouette sans barbe n'engendre * jamais le composant correspondant. */ UPROPERTY(Transient) TArray> PartComponents; /** * Les morceaux de corps au-dela du porteur. Vide aujourd'hui : le catalogue * n'a qu'un mesh par silhouette. C'est ici qu'atterriront les segments le * jour des armures, sans que la logique change. */ UPROPERTY(Transient) TArray> ExtraBodyComponents; FCharacterAppearance Appearance; /** Faux tant qu'aucun montage n'a eu lieu : force la premiere reconstruction. */ bool bBuilt = false; };