(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:
@@ -133,6 +133,62 @@ qu'une compilation propre l'est vraiment.
|
||||
| Fondus, boîtes de dialogue | `ScreenFadeComponent`, `ConfirmDialogWidget` |
|
||||
| Coop Steam (sessions) | `SessionSubsystem`, `SurvivalGameMode`, `SurvivalGameState`, `SurvivalPlayerState` |
|
||||
| Pseudo au-dessus des joueurs | `PlayerNameplateComponent`, `PlayerNameplateWidget` |
|
||||
| Corps 3e personne | `PlayerBodyAnimInstance` + `ABP_PlayerBody_A`, le `Mesh` de `BP_FpsPlayer` |
|
||||
|
||||
### Le corps vu par les autres — pièges du personnage modulaire
|
||||
|
||||
Le pack **Stylized Characters** (+ *CustomizationPack01*, tous deux dans `Content/External/`)
|
||||
livre un personnage **en morceaux** : `SKM_BodyA` s'arrête au cou, la tête, les sourcils, les
|
||||
cheveux, la barbe et la moustache sont des meshes séparés.
|
||||
|
||||
- **Un seul mesh porte l'animation, les autres recopient sa pose.** C'est le
|
||||
**Leader Pose Component** : le corps est le leader avec son AnimBP, tous les autres sont des
|
||||
`USkeletalMeshComponent` enfants **sans animation à eux**. La correspondance se fait **par nom
|
||||
d'os**, donc elle marche même entre squelettes distincts — les têtes du pack de customisation
|
||||
sont sur `SK_BodyA_empty` et se posent sans problème sur un corps `SK_BodyA`.
|
||||
- **`LeaderPoseComponent` est `BlueprintReadOnly`, PAS `EditAnywhere`.** Elle n'apparaît donc
|
||||
**jamais** dans le Details panel, et on la cherche longtemps : la section « Leader Pose
|
||||
Component » qu'on y voit ne contient que des cases de bornes et de LOD. Elle ne s'assigne que
|
||||
par `SetLeaderPoseComponent()` — Construction Script en Blueprint, ou C++.
|
||||
- **La physique de cheveux est perdue en leader pose.** Coiffures, barbes et moustaches ont leur
|
||||
propre squelette et leur physics asset, mais l'AnimBP post-process d'un *follower* ne tourne
|
||||
pas. La démo du pack accepte déjà ce compromis.
|
||||
- **Le squelette est celui du Mannequin UE5** (`ik_foot_root`, `ik_hand_gun`, `spine_01..05`,
|
||||
`index_metacarpal_l` — les métacarpiens n'existaient pas sur le squelette UE4). Conséquence
|
||||
précieuse : **toute animation vendue « UE5 Mannequin compatible » se branche sans retarget.**
|
||||
C'est par là qu'arriveront l'accroupissement et les gestes d'outil, absents du pack.
|
||||
- **`Approx Size` d'un Skeletal Mesh n'est pas sa hauteur visible** : les bounds incluent une
|
||||
marge et celles du Physics Asset. `SKM_BodyA` affiche 184 alors que le personnage entier,
|
||||
tête comprise, mesure à peu près autant. Pour juger une taille, passer le viewport en vue
|
||||
**Front** et se servir de la ligne verte (`Z = 0`, centre de la capsule) : le bas est à
|
||||
`-HalfHeight`, le haut à `+HalfHeight`. Le personnage fait ~184 cm pour une capsule de 176,
|
||||
et on **ne scale pas** — la capsule porte la collision et le game feel déjà calibré.
|
||||
|
||||
**Côté animation**, `UPlayerBodyAnimInstance` suit le partage des rôles de
|
||||
`UFpsArmsAnimInstance` : le C++ lit l'état du pawn et le republie, le Blueprint choisit
|
||||
l'animation. Deux points valent d'être retenus :
|
||||
|
||||
- **Rien n'y est répliqué, et c'est voulu.** Le corps est vu par tout le monde, mais chaque
|
||||
variable **dérive** d'un état déjà synchronisé : `Velocity` par le CharacterMovement,
|
||||
`bIsCrouched` par `ACharacter`, `bIsSprinting` par `AFpsPlayer`, et le tangage du regard par
|
||||
`RemoteViewPitch` que `APawn` réplique tout seul — d'où `GetBaseAimRotation()` et surtout pas
|
||||
`GetControlRotation()`, qui n'existe que sur la machine propriétaire.
|
||||
- **`bIsFalling` ne distingue pas un saut d'une chute** : le moteur appelle « falling » tout ce
|
||||
qui n'est pas au sol. C'est le **signe de `VerticalSpeed`** qui tranche, sans quoi sortir d'un
|
||||
rebord jouerait l'animation de détente. Et la condition `Ground → Jump` a besoin du `AND` avec
|
||||
`bIsFalling` : gravir une pente donne aussi une vitesse verticale positive.
|
||||
- **Le `Cast` du pawn est relancé à chaque frame tant qu'il est nul.** Chez un client le mesh
|
||||
arrive par réplication et peut s'animer **avant** le pawn : un `Cast` fait une seule fois dans
|
||||
`NativeInitializeAnimation` laisserait le corps de l'ami figé en pose de référence pour toute
|
||||
la partie. Invisible en solo comme chez l'hôte.
|
||||
|
||||
Enfin, le BlendSpace du pack posait `Walk_InPlace` à la vitesse 0 — un personnage qui piétine
|
||||
sur place à l'arrêt. On y a mis l'idle à la place, plutôt qu'un état `Idle` séparé : un état,
|
||||
c'est un **seuil** qui claque au démarrage et au freinage, là où le BlendSpace interpole en
|
||||
continu. Ses points restent calés sur les **vitesses réelles mesurées des animations**
|
||||
(225.6 et 500), ce qui est précisément ce qui évite le glissement de pieds — on ne les déplace
|
||||
pas. Il s'arrête en revanche à 500 alors que le sprint est à 650 : un second échantillon de
|
||||
course posé à 650 avec un **Rate Scale** de 1.3 le couvrirait.
|
||||
|
||||
### Coop Steam — décisions figées
|
||||
|
||||
@@ -354,6 +410,7 @@ hôte qui plante, câble arraché — et se tait pendant un départ voulu grâce
|
||||
| `StorageContainer` (contenu, couvercle) | ✅ fait |
|
||||
| `CraftingComponent` (`Server_Craft`) | ✅ fait |
|
||||
| `CookingStation` (grill, bûches, flamme) | ✅ fait |
|
||||
| Corps 3e personne animé (rien à répliquer, tout dérivé) | ✅ fait |
|
||||
|
||||
**Un acteur du monde qui ouvre une interface doit RENVOYER l'ordre au client.**
|
||||
`Interact_Implementation` s'exécute sur le serveur : `PlayerController->OpenStorage(...)` y créerait
|
||||
@@ -616,6 +673,89 @@ Les vraies recettes restent à écrire : celles en place sont deux `DA_Craft_Tes
|
||||
`DA_Branch` et `DA_Rocher`. Les **boutons de filtre** par poste ne sont pas faits non plus
|
||||
(`SetStationFilter` existe déjà, `ECraftingStation::Count` sert de sentinelle « tout »).
|
||||
|
||||
### Chantier en cours : customisation de personnage
|
||||
|
||||
Découpé en quatre lots. **Le lot 1 est fait** : le pawn a enfin un corps visible et animé (voir
|
||||
*Le corps vu par les autres* plus haut) — sans lui il n'y avait rien à customiser, et les autres
|
||||
joueurs voyaient un pseudo flotter au-dessus du vide.
|
||||
|
||||
**Le lot 2 est fait aussi** : `FCharacterAppearance` (11 octets d'index), `UCharacterPartsDataAsset`
|
||||
(le catalogue) et `UCharacterAppearanceComponent` (monte les pièces en leader pose, pose les
|
||||
couleurs sur des MID). `DA_CharacterParts` couvre Body A ; **Body B reste à remplir**, avec son
|
||||
`ABP_PlayerBody_B` et sans barbes ni moustaches — le pack n'en livre pas, et une liste vide se
|
||||
comporte déjà comme `NoPart`.
|
||||
|
||||
Restent les lots 3 et 4 :
|
||||
|
||||
- **Lot 3** — réplication par `ASurvivalPlayerState` en `ReplicatedUsing`, persistance dans
|
||||
`USurvivalUserSettings`
|
||||
- **Lot 4** — l'écran : `ACharacterPreviewActor` dans `MenuScene`, caméra à cadrages interpolés,
|
||||
UI à gauche
|
||||
|
||||
#### Trois pièges du lot 2, aucun visible depuis le code
|
||||
|
||||
- **Les `UFUNCTION(Exec)` d'un `UActorComponent` ne sont JAMAIS appelées.** La chaîne de routage
|
||||
(`UPlayer::Exec`, dans `Player.cpp`) interroge le monde, le `PlayerInput`, le contrôleur, 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. Et rien ne
|
||||
proteste : la commande est simplement introuvable. D'où `EmberPart` / `EmberColor` /
|
||||
`EmberAppearanceDump` posées sur `AFpsPlayer`, qui délègue au composant. *(`EmberJoin` marche
|
||||
parce que `UGameInstance`, lui, route vers ses subsystems.)*
|
||||
- **L'ordre des slots de matériau varie d'un mesh à l'autre.** Sur `SKM_HeadA_01`, l'Element 0
|
||||
est celui des **yeux** et l'Element 1 celui de la peau — l'inverse de l'intuition. Les six têtes
|
||||
viennent de deux packs différents, rien ne garantit qu'elles s'accordent. On cherche donc le slot
|
||||
**par son nom** (`EyeMaterialSlotHint`, « Eye »), et si rien ne correspond on ne pose rien et on
|
||||
le dit dans le log : écraser le slot de la peau repeint le visage entier de la couleur des yeux,
|
||||
symptôme qu'on met bien plus longtemps à diagnostiquer qu'une ligne de log.
|
||||
- **Un nom de paramètre matériau se lit dans l'éditeur du Material INSTANCE, jamais dans les
|
||||
chaînes du master.** Le binaire de `M_Head_Base` contient « Skin Base color », mais le paramètre
|
||||
réellement exposé s'appelle **`Skin`** — le chercher sous l'autre nom dans `MI_HeadA_01_a` ne
|
||||
rend aucun résultat. Corollaire : les noms de paramètres sont des `UPROPERTY` du catalogue et
|
||||
pas des constantes C++, pour qu'une erreur se corrige sans recompiler.
|
||||
|
||||
Le montage lui-même n'a rien de surprenant : le composant crée un `USkeletalMeshComponent` par
|
||||
catégorie, le met en leader pose du `Mesh` du Character, et recopie sur chacun le `bOwnerNoSee` et
|
||||
le `bCastHiddenShadow` du porteur — c'est ce qui fera que le mannequin du menu, lui, sera visible
|
||||
sans une ligne de plus. Les couleurs sont posées sur **tous** les éléments de chaque mesh sans
|
||||
chercher lequel les porte : un `SetVectorParameterValue` dont le paramètre n'existe pas est
|
||||
ignoré, ce qui évite une table d'index de slots qui casserait au premier mesh mal rangé.
|
||||
|
||||
**Décisions figées** :
|
||||
|
||||
- **Couleurs = palette pure, un bouton par couleur.** Une `TArray<FLinearColor>` par catégorie
|
||||
dans le catalogue, un index d'un octet dans l'apparence. Cliquer une pastille pose la couleur,
|
||||
sans réglage intermédiaire. Le curseur de nuance envisagé au départ a été retiré : l'auteur veut
|
||||
des boutons.
|
||||
- **Pas de `DisplayName` sur les entrées du catalogue** — la vignette (`Icon`, un `UTexture2D`
|
||||
comme celle d'`UItemDataAsset`) identifie l'option à elle seule.
|
||||
- **Écran imposé sur le chemin *Jouer*.** Corollaire assumé : l'arrivée par invitation Steam ne
|
||||
passe pas par là, l'invité garde donc son apparence persistée du dernier passage.
|
||||
- **Étendue : silhouette + couleurs.** Tatouages et maquillage reportés — ce ne sont que des
|
||||
paramètres texture de plus (`M_Head_Base` et `M_Body_Base` les exposent déjà), aucune refonte.
|
||||
- **Les index et jamais les chemins d'assets** dans la struct répliquée : quelques octets au lieu
|
||||
d'une chaîne, et un index se valide contre le catalogue.
|
||||
- **Le corps se monte depuis une LISTE de meshes, jamais un pointeur unique**, même quand la liste
|
||||
n'a qu'une case. C'est ce qui permettra aux armures de cacher des segments (`SKM_BodyA_torso`,
|
||||
`_arms`, `_legs_01/02/03`…) sans refonte — le pack les fournit exprès. Contrainte qui en
|
||||
découle : **tout futur pack d'armure devra être skinné sur `SK_BodyA` / `SK_BodyB`**, à vérifier
|
||||
avant achat. Le jour venu, `SKM_BodyA_empty` deviendra le porteur d'animation invisible — il
|
||||
faudra lui réassigner `SK_BodyA`, son squelette d'origine étant `SK_BodyA_empty`.
|
||||
- Limite acceptée : **les bras vus en 1re personne ne sont pas customisés.** Leur squelette
|
||||
(`BasePose_Skeleton`) n'a rien à voir avec `SK_BodyA` ; les rendre customisables imposerait de
|
||||
retargeter toutes les animations FPS. Raft assume le même compromis.
|
||||
|
||||
**Les matériaux du pack sont pilotables par MID**, ce qui rend les variantes `_a` / `_b` / `_c`
|
||||
inutiles — ce ne sont que des presets de couleur figés. Une seule instance par pièce suffit :
|
||||
|
||||
| Master | Paramètres utiles |
|
||||
|---|---|
|
||||
| `M_Body_Base` | `Skin color`, `Underware color`, `Tattoo 01..05 color` + masques |
|
||||
| `M_Head_Base` | `Skin Base color`, `Skin roughness`, `Blush` / `Lipstick` / `Eyeliner` / `Eyeshadow` / `Eyelashes` / `Tattoo` (couleur + masque chacun) |
|
||||
| `M_Hairstyle_Base` | `Color` — parent des **cheveux, barbes, moustaches ET sourcils** |
|
||||
| `M_Eyes_Base` | `Eye color`, groupes Iris / Pupil / Sclera |
|
||||
|
||||
### Après la customisation
|
||||
|
||||
Prochaine étape gameplay recommandée : **spawner de ressources avec repousse**
|
||||
(`AResourceSpawner`, liste pondérée de DataAssets, rayon, densité, délai). Sans lui la carte se
|
||||
vide en dix minutes. À écrire **serveur-autoritaire d'emblée** — c'est un acteur du monde.
|
||||
|
||||
Reference in New Issue
Block a user