Files
Unreal_EmberWild/Source/EmberWild/Public/CharacterAppearanceComponent.h
T

175 lines
7.0 KiB
C++

// 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 EMBERWILD_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<UCharacterPartsDataAsset> 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<USkeletalMeshComponent> 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<TObjectPtr<USkeletalMeshComponent>> 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<TObjectPtr<USkeletalMeshComponent>> ExtraBodyComponents;
FCharacterAppearance Appearance;
/** Faux tant qu'aucun montage n'a eu lieu : force la premiere reconstruction. */
bool bBuilt = false;
};