(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,194 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "CharacterAppearanceTypes.h"
|
||||
#include "CharacterPartsDataAsset.generated.h"
|
||||
|
||||
class UAnimInstance;
|
||||
class UMaterialInterface;
|
||||
class USkeletalMesh;
|
||||
class UTexture2D;
|
||||
|
||||
/**
|
||||
* Tout ce qui compose une silhouette : ses meshes de corps, son AnimBP, et les
|
||||
* pieces disponibles pour chaque categorie.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FCharacterBodySet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** La vignette du bouton de silhouette, meme role que celle d'une piece. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Body")
|
||||
TObjectPtr<UTexture2D> Icon;
|
||||
|
||||
/**
|
||||
* Les morceaux du corps, dans l'ordre. Le PREMIER est le porteur : c'est lui
|
||||
* qui va sur le composant Mesh du Character et qui joue l'animation, les
|
||||
* suivants deviennent des composants enfants en leader pose.
|
||||
*
|
||||
* Une LISTE alors qu'il n'y a qu'une case aujourd'hui, et ce n'est pas de la
|
||||
* sur-conception : le pack livre exprès `SKM_BodyA_torso`, `_arms`,
|
||||
* `_legs_01/02/03`, `_hands`, `_feet`, precisement pour qu'une armure puisse
|
||||
* eteindre les segments qu'elle recouvre. Le jour venu, la case 0 deviendra
|
||||
* `SKM_BodyA_empty` -- invisible, porteur d'animation -- et les huit segments
|
||||
* suivront derriere, SANS que ce code change. Un TObjectPtr unique, lui,
|
||||
* imposerait de tout reprendre au premier plastron.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Body")
|
||||
TArray<TObjectPtr<USkeletalMesh>> BodyMeshes;
|
||||
|
||||
/** L'AnimBP du porteur. Il change avec la silhouette, d'ou sa place ici. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Body")
|
||||
TSubclassOf<UAnimInstance> AnimClass;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Parts")
|
||||
TArray<FCharacterPartEntry> Heads;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Parts")
|
||||
TArray<FCharacterPartEntry> Hairstyles;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Parts")
|
||||
TArray<FCharacterPartEntry> Eyebrows;
|
||||
|
||||
/** Vide pour une silhouette qui n'en porte pas -- le pack n'en donne qu'a A. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Parts")
|
||||
TArray<FCharacterPartEntry> Beards;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Parts")
|
||||
TArray<FCharacterPartEntry> Mustaches;
|
||||
|
||||
/**
|
||||
* Fragment cherche dans le NOM des slots de materiau du mesh de tete pour y
|
||||
* trouver celui des yeux.
|
||||
*
|
||||
* Les yeux ne sont pas un mesh : le pack les livre comme un element du mesh
|
||||
* de tete. Il ne suffit donc pas de poser un parametre de couleur, il faut
|
||||
* REMPLACER le materiau de ce slot -- encore faut-il savoir lequel.
|
||||
*
|
||||
* Un fragment de nom et pas un index, parce qu'un index est faux deux fois :
|
||||
* il oblige a le verifier a la main dans l'editeur, et il suppose surtout
|
||||
* que les six tetes rangent leurs slots dans le MEME ordre. Les slots du
|
||||
* pack s'appellent `M_Eye_A_01`, `M_Eye`, `M_Eyes_02`... tous contiennent
|
||||
* « Eye », aucun slot de peau ne le contient.
|
||||
*
|
||||
* Si le fragment ne trouve rien, le materiau n'est PAS pose et un
|
||||
* avertissement part dans le log. C'est volontaire : poser l'oeil sur un
|
||||
* slot au hasard repeindrait le visage entier, un symptome qu'on met bien
|
||||
* plus longtemps a diagnostiquer qu'une ligne de log.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Parts")
|
||||
FName EyeMaterialSlotHint = TEXT("Eye");
|
||||
};
|
||||
|
||||
/**
|
||||
* Le catalogue : la table de correspondance entre les index d'une
|
||||
* FCharacterAppearance et les assets reels.
|
||||
*
|
||||
* Un seul asset a remplir, et le SEUL endroit a toucher pour ajouter une
|
||||
* coiffure. C'est aussi lui qui borne ce qu'un client peut demander : une
|
||||
* apparence arrivee du reseau est passee a Sanitize(), qui ramene tout index
|
||||
* hors bornes a une valeur sure.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class SURVIVAL_PROJET_API UCharacterPartsDataAsset : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Une entree par valeur d'ECharacterBodyType, dans l'ordre de l'enum.
|
||||
* L'ordre EST le contrat : c'est lui qui donne son sens a BodyType.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog")
|
||||
TArray<FCharacterBodySet> BodySets;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Palettes : une couleur = un bouton a l'ecran, sans reglage entre les
|
||||
// deux. L'ordre EST l'ordre d'affichage, et l'index qui part sur le reseau.
|
||||
//
|
||||
// Partagees par les deux silhouettes : deux nuanciers de peau a tenir a
|
||||
// jour divergeraient au premier ajout.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Palettes")
|
||||
TArray<FLinearColor> SkinColors;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Palettes")
|
||||
TArray<FLinearColor> HairColors;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Palettes")
|
||||
TArray<FLinearColor> EyeColors;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Palettes")
|
||||
TArray<FLinearColor> UnderwearColors;
|
||||
|
||||
/** Les materiaux d'iris, un par forme d'oeil proposee. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Palettes")
|
||||
TArray<TObjectPtr<UMaterialInterface>> EyeMaterials;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Noms des parametres des materiaux du pack.
|
||||
//
|
||||
// Ici et pas en dur dans le C++ : ils appartiennent aux assets, pas au
|
||||
// code. Changer de pack de personnages ne doit demander qu'un nouveau
|
||||
// catalogue -- et ces noms contiennent des ESPACES, ce qu'on n'invente
|
||||
// pas de memoire.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/** `M_Body_Base`. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Material Parameters")
|
||||
FName SkinColorParameter = TEXT("Skin color");
|
||||
|
||||
/**
|
||||
* `M_Head_Base` -- le meme role que ci-dessus, mais l'autre master, et
|
||||
* surtout un NOM DIFFERENT : `Skin` cote tete, `Skin color` cote corps.
|
||||
*
|
||||
* Le master contient bien la chaine « Skin Base color », mais ce n'est pas
|
||||
* le nom du parametre expose -- verifie dans `MI_HeadA_01_a`, ou le chercher
|
||||
* ne rend aucun resultat alors que `Skin` apparait dans le groupe
|
||||
* « Base colors ». Morale : un nom de parametre se lit dans l'editeur du
|
||||
* Material Instance, jamais dans les chaines du binaire du master.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Material Parameters")
|
||||
FName HeadSkinColorParameter = TEXT("Skin");
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Material Parameters")
|
||||
FName UnderwearColorParameter = TEXT("Underware color");
|
||||
|
||||
/** `M_Hairstyle_Base`, parent des cheveux, barbes, moustaches ET sourcils. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Material Parameters")
|
||||
FName HairColorParameter = TEXT("Color");
|
||||
|
||||
/** `M_Eyes_Base`. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Catalog|Material Parameters")
|
||||
FName EyeColorParameter = TEXT("Eye color");
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Lecture
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/** Le jeu de pieces d'une silhouette, ou nullptr si l'index est hors bornes. */
|
||||
const FCharacterBodySet* GetBodySet(ECharacterBodyType BodyType) const;
|
||||
|
||||
/** La liste d'une categorie pour une silhouette, vide si rien n'est defini. */
|
||||
const TArray<FCharacterPartEntry>& GetParts(ECharacterBodyType BodyType, ECharacterPartCategory Category) const;
|
||||
|
||||
/** Nombre d'options d'une categorie. Sert a borner l'UI comme la console. */
|
||||
UFUNCTION(BlueprintPure, Category = "Catalog")
|
||||
int32 GetPartCount(ECharacterBodyType BodyType, ECharacterPartCategory Category) const;
|
||||
|
||||
/**
|
||||
* Ramene une apparence dans les bornes du catalogue.
|
||||
*
|
||||
* Appelee sur le SERVEUR a la reception d'une apparence client, et au
|
||||
* chargement des reglages -- un catalogue ampute entre deux versions du jeu
|
||||
* laisserait sinon des index qui ne designent plus rien. Les pieces
|
||||
* facultatives (barbe, moustache) retombent sur NoPart, les obligatoires
|
||||
* sur 0 : mieux vaut la mauvaise tete que pas de tete.
|
||||
*/
|
||||
void Sanitize(FCharacterAppearance& InOut) const;
|
||||
};
|
||||
Reference in New Issue
Block a user