(Feat) Customisation de personnage -- donnees et montage
Lot 2 du chantier : le corps du lot precedent devient assemblable. Tete, coiffure, sourcils, barbe et moustache sont montes en leader pose sur le mesh du Character, et les couleurs sont posees sur des MID. FCharacterAppearance tient en 11 octets : des INDEX et jamais des chemins d'assets. Un chemin couterait une chaine a chaque replication, alors qu'un index tient dans un octet et se VALIDE contre le catalogue -- un client ne peut donc pas demander un asset arbitraire. Le corps se monte depuis une LISTE de meshes meme si elle n'a qu'une case. Le pack livre exprès SKM_BodyA_torso, _arms, _legs_01/02/03 pour qu'une armure puisse eteindre les segments qu'elle recouvre : le jour venu la case 0 devient SKM_BodyA_empty et les huit segments suivent, sans que ce code change. Un pointeur unique imposerait de tout reprendre au premier plastron. Trois pieges rencontres, aucun visible depuis le code : - Les UFUNCTION(Exec) d'un UActorComponent ne sont JAMAIS appelees. La chaine de routage (UPlayer::Exec) 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. Et rien ne proteste. D'ou EmberPart / EmberColor / EmberAppearanceDump posees sur AFpsPlayer, qui delegue. - L'ordre des slots de materiau varie d'un mesh a l'autre : sur SKM_HeadA_01 l'Element 0 est celui des YEUX et le 1 celui de la peau. Les six tetes venant de deux packs, on cherche le slot par son NOM et on ne pose rien si aucun ne correspond -- ecraser le slot de la peau repeint le visage entier de la couleur des yeux. - Un nom de parametre materiau se lit dans le Material INSTANCE, pas dans les chaines du master : M_Head_Base contient « Skin Base color » mais le parametre expose s'appelle « Skin ». D'ou des UPROPERTY de catalogue plutot que des constantes, pour corriger sans recompiler. Body B reste a remplir dans DA_CharacterParts, avec son ABP_PlayerBody_B et sans barbes ni moustaches -- le pack n'en livre pas, et une liste vide se comporte deja comme NoPart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
// 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<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. */
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user