(Feat) Add footsteps
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
# Customisation de personnage
|
||||
|
||||
Le joueur choisit sa silhouette, son visage, sa coiffure et ses couleurs avant
|
||||
de lancer une partie. Les trois autres joueurs le voient tel qu'il s'est fait.
|
||||
|
||||
Ce document a **deux moitiés**, à lire séparément :
|
||||
|
||||
- **Partie A — pour l'artiste** : ajouter une coiffure, une couleur, un corps.
|
||||
Aucune ligne de code, aucune recompilation. Commence au §1.
|
||||
- **Partie B — pour le développeur** : architecture, réseau, pièges du moteur.
|
||||
Commence au §6.
|
||||
|
||||
---
|
||||
|
||||
# PARTIE A — Ajouter du contenu
|
||||
|
||||
## 1. Le principe en une phrase
|
||||
|
||||
**Tout passe par un seul asset : `DA_CharacterParts`.** C'est la table qui dit
|
||||
« l'option n° 3 de la catégorie Cheveux, c'est ce mesh-là ». Le code ne connaît
|
||||
que des numéros ; c'est le catalogue qui leur donne un sens.
|
||||
|
||||
Conséquence directe : **ajouter du contenu ne demande jamais de toucher au
|
||||
code**, et jamais de fermer l'éditeur.
|
||||
|
||||
```
|
||||
DA_CharacterParts
|
||||
├── Body Sets une entrée par silhouette (A, B, …)
|
||||
│ ├── Body Meshes les morceaux du corps
|
||||
│ ├── Anim Class l'AnimBP qui l'anime
|
||||
│ ├── Heads ┐
|
||||
│ ├── Hairstyles │ les listes d'options,
|
||||
│ ├── Eyebrows │ propres à CETTE silhouette
|
||||
│ ├── Beards │
|
||||
│ └── Mustaches ┘
|
||||
└── Palettes partagées par toutes les silhouettes
|
||||
├── Skin Colors
|
||||
├── Hair Colors
|
||||
├── Eye Colors
|
||||
├── Underwear Colors
|
||||
└── Eye Materials
|
||||
```
|
||||
|
||||
## 2. Ajouter une coiffure
|
||||
|
||||
1. Ouvre `Content/Game/Data/Character/DA_CharacterParts`
|
||||
2. Déplie **Body Sets** → l'entrée du corps concerné (`[0]` = A, `[1]` = B)
|
||||
3. Déplie **Hairstyles** → clique le **+**
|
||||
4. Dans la nouvelle entrée :
|
||||
- **Mesh** = ton `SKM_…`
|
||||
- **Icon** = la vignette (facultative, voir §5)
|
||||
5. Sauvegarde
|
||||
|
||||
C'est tout. La coiffure apparaît dans l'écran au prochain lancement.
|
||||
|
||||
> **L'ORDRE DE LA LISTE EST LE CONTRAT.** Un index est ce qui part sur le réseau
|
||||
> et ce qui se sauvegarde. **Insérer une entrée au milieu décale tout ce qui
|
||||
> suit** : les personnages déjà créés changeront de coiffure sans prévenir.
|
||||
> **Ajoute toujours à la fin.**
|
||||
|
||||
Même procédé pour **Heads**, **Eyebrows**, **Beards**, **Mustaches**.
|
||||
|
||||
## 3. Ajouter une couleur
|
||||
|
||||
1. Déplie **Palettes** → la palette voulue (`Skin Colors`, `Hair Colors`…)
|
||||
2. Clique le **+**
|
||||
3. Double-clique la case de couleur → dans le picker, colle une valeur dans le
|
||||
champ **Hex sRGB** (le plus fiable) ou règle R/G/B à la main
|
||||
4. Sauvegarde
|
||||
|
||||
Les palettes sont **partagées par toutes les silhouettes** : une teinte de peau
|
||||
ajoutée profite à A comme à B. Deux nuanciers à tenir à jour divergeraient au
|
||||
premier ajout.
|
||||
|
||||
> Même règle d'ordre qu'au §2 : **on ajoute à la fin**.
|
||||
|
||||
### Ce que chaque palette pilote
|
||||
|
||||
| Palette | Ce qu'elle teint |
|
||||
|---|---|
|
||||
| **Skin Colors** | le corps **et** le visage, ensemble |
|
||||
| **Hair Colors** | cheveux, **barbe, moustache et sourcils** en même temps |
|
||||
| **Eye Colors** | l'iris |
|
||||
| **Underwear Colors** | le sous-vêtement |
|
||||
|
||||
Une barre de couleur de cheveux qui ne teindrait pas la barbe donnerait un
|
||||
personnage incohérent — c'est voulu qu'elles suivent.
|
||||
|
||||
## 4. Ajouter une silhouette
|
||||
|
||||
Plus lourd, mais toujours sans code.
|
||||
|
||||
1. **Body Sets** → **+** → une nouvelle entrée
|
||||
2. **Body Meshes** → ajoute ton `SKM_` de corps (une seule case aujourd'hui)
|
||||
3. **Anim Class** → l'AnimBP à utiliser
|
||||
|
||||
> **Tu peux réutiliser `ABP_PlayerBody_A`** si ton nouveau corps partage les
|
||||
> mêmes **noms d'os**. Le moteur valide la compatibilité par les noms, pas par
|
||||
> l'identité du squelette. C'est ce qui permet à Body A et Body B de partager un
|
||||
> seul AnimBP : une correction profite aux deux.
|
||||
|
||||
4. Remplis **Heads**, **Hairstyles**, **Eyebrows** — ces listes appartiennent à
|
||||
la silhouette
|
||||
5. Laisse **Beards** et **Mustaches** vides si elle n'en porte pas : les
|
||||
catégories **disparaissent d'elles-mêmes** de la colonne de l'écran
|
||||
|
||||
## 5. Les vignettes
|
||||
|
||||
Le champ **Icon** est un `UTexture2D`, exactement comme l'icône d'un objet
|
||||
d'inventaire.
|
||||
|
||||
**Il est facultatif.** Une entrée sans vignette affiche son **numéro** dans la
|
||||
case — l'option reste choisissable. C'est délibéré : le catalogue doit rester
|
||||
utilisable avant que les trente-six vignettes existent, et le moyen le plus
|
||||
simple de les fabriquer est justement de capturer l'écran de customisation une
|
||||
fois qu'il tourne.
|
||||
|
||||
## 6. Le décor de l'écran
|
||||
|
||||
Le personnage montré pendant la customisation est un acteur posé dans
|
||||
`MenuScene` : **`BP_CharacterPreview`**. Trois choses comptent quand tu le
|
||||
places :
|
||||
|
||||
- **Hors du champ des `MenuCameraSpot`**, sinon il apparaît en arrière-plan du
|
||||
menu principal
|
||||
- **Devant un fond qui tient debout** — c'est ce qu'on voit derrière lui pendant
|
||||
toute la customisation
|
||||
- **Éclairé de face.** On passe l'essentiel du temps en gros plan sur le
|
||||
visage ; à contre-jour, les teints de peau deviennent illisibles et c'est
|
||||
précisément ce que le joueur règle
|
||||
|
||||
### Les deux cadrages
|
||||
|
||||
`FullBodyViewSpot` et `FaceViewSpot` sont deux repères vides que tu **déplaces à
|
||||
la souris** dans le viewport du Blueprint. La caméra glisse de l'un à l'autre
|
||||
selon la catégorie ouverte :
|
||||
|
||||
| Catégorie ouverte | Cadrage |
|
||||
|---|---|
|
||||
| Silhouette, Teint, Sous-vêtement | `FullBodyViewSpot` |
|
||||
| tout le reste | `FaceViewSpot` |
|
||||
|
||||
Pense à **décaler le personnage vers la droite** de l'image : la colonne d'UI
|
||||
occupe la gauche.
|
||||
|
||||
---
|
||||
|
||||
# PARTIE B — Architecture
|
||||
|
||||
## 7. Vue d'ensemble
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---|---|
|
||||
| `CharacterAppearanceTypes.h` | `FCharacterAppearance` (11 octets), les enums, `FCharacterPartEntry` |
|
||||
| `CharacterPartsDataAsset` | le catalogue : index → assets, palettes, noms de paramètres matériau |
|
||||
| `CharacterAppearanceComponent` | monte les pièces, pose les couleurs. Sur le pawn **et** sur le mannequin |
|
||||
| `CharacterPreviewActor` | le mannequin du menu : mesh + caméra + cadrages |
|
||||
| `CharacterCustomizationWidget` | l'écran, la colonne, la grille |
|
||||
| `CustomizationCategoryWidget` | une rangée de la colonne |
|
||||
| `CustomizationOptionWidget` | une case : vignette **ou** pastille |
|
||||
| `SurvivalPlayerState` | porte l'apparence répliquée |
|
||||
| `SurvivalUserSettings` | la persiste dans le `.ini` |
|
||||
|
||||
## 8. `FCharacterAppearance` — pourquoi des index
|
||||
|
||||
11 octets, **que des index**, jamais de `TSoftObjectPtr` ni de chemin d'asset.
|
||||
Deux raisons qui vont dans le même sens :
|
||||
|
||||
- un chemin coûte une chaîne à chaque réplication, un index tient dans un octet ;
|
||||
- un index se **valide** contre le catalogue, alors qu'un chemin envoyé par un
|
||||
client pourrait désigner n'importe quel asset du jeu.
|
||||
|
||||
Bénéfice collatéral : la struct se sérialise telle quelle dans le `.ini`
|
||||
(`CharacterAppearance=(BodyType=BodyA,HeadIndex=0,…)`). Une apparence faite de
|
||||
références d'assets aurait donné une douzaine de chemins à écrire, qu'une seule
|
||||
renommée aurait cassés silencieusement.
|
||||
|
||||
`NoPart` vaut **255** : c'est « aucune pièce », pour une barbe rasée.
|
||||
|
||||
## 9. Le montage — `UCharacterAppearanceComponent`
|
||||
|
||||
Un `USkeletalMeshComponent` par catégorie, enfant du mesh porteur, en **Leader
|
||||
Pose**. Un seul mesh porte l'animation, les autres recopient sa pose **par nom
|
||||
d'os** — ce qui marche même entre squelettes distincts.
|
||||
|
||||
Le composant recopie sur chaque pièce le `bOwnerNoSee` et le `bCastHiddenShadow`
|
||||
du porteur. C'est ce qui fait que le **mannequin du menu est visible** sans une
|
||||
ligne de code de plus, alors que le corps du joueur reste invisible à son
|
||||
propriétaire.
|
||||
|
||||
### Le corps se monte depuis une LISTE
|
||||
|
||||
`BodyMeshes` est un `TArray` même s'il ne contient qu'une case. Le pack livre
|
||||
exprès `SKM_BodyA_torso`, `_arms`, `_legs_01/02/03`, `_hands`, `_feet` : le jour
|
||||
où une armure devra cacher les segments qu'elle recouvre, la case 0 deviendra
|
||||
`SKM_BodyA_empty` — porteur d'animation invisible — et les huit segments
|
||||
suivront derrière, **sans que ce code change**.
|
||||
|
||||
> **Contrainte à vérifier avant tout achat de pack d'armure : il doit être
|
||||
> skinné sur `SK_BodyA` / `SK_BodyB`.**
|
||||
|
||||
### Les couleurs
|
||||
|
||||
Chaque paramètre est posé sur **tous les éléments** de chaque mesh, sans chercher
|
||||
lequel le porte : un `SetVectorParameterValue` dont le paramètre n'existe pas est
|
||||
simplement ignoré. Le slot des yeux ignore donc `Skin`, celui de la peau ignore
|
||||
`Eye color`. Cela évite une table d'index de slots qui casserait au premier mesh
|
||||
mal rangé.
|
||||
|
||||
## 10. Réseau et persistance
|
||||
|
||||
```
|
||||
USurvivalUserSettings ──lit──► AnnounceCharacterAppearance()
|
||||
(.ini du joueur) │
|
||||
▼
|
||||
SubmitCharacterAppearance() ← POINT D'ENTRÉE UNIQUE
|
||||
│ │
|
||||
persiste Server_SetCharacterAppearance
|
||||
│
|
||||
ASurvivalPlayerState (autorité)
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
OnRep (clients) appel direct (hôte)
|
||||
└──────────┬──────────┘
|
||||
▼
|
||||
ApplyAppearanceToPawn()
|
||||
```
|
||||
|
||||
**`SubmitCharacterAppearance()` persiste ET annonce, jamais l'un sans l'autre.**
|
||||
Persister sans annoncer laisserait le joueur seul à se voir changé ; annoncer
|
||||
sans persister le ferait repartir au visage par défaut au lancement suivant.
|
||||
|
||||
L'apparence vit sur le **PlayerState** et pas sur le pawn : elle décrit le
|
||||
JOUEUR, pas le corps. Le pawn meurt et réapparaît, le PlayerState non —
|
||||
réapparaître avec le visage d'un autre n'aurait aucun sens, alors que perdre son
|
||||
inventaire au sol est la règle du jeu.
|
||||
|
||||
**Rien n'est validé côté PlayerState, délibérément** : borner un index demande le
|
||||
catalogue, que seul le composant connaît — or il passe déjà tout ce qu'il monte
|
||||
par `Catalog->Sanitize()`, sur chaque machine.
|
||||
|
||||
## 11. Le parcours de l'écran
|
||||
|
||||
```
|
||||
clic Jouer → fondu au noir → SetViewTarget(BP_CharacterPreview)
|
||||
→ colonne affichée → fondu depuis le noir
|
||||
├─ « Jouer » → apparence persistée → fondu de départ → GameScene
|
||||
└─ « Retour » → fondu → nouvel angle de menu
|
||||
```
|
||||
|
||||
On ne charge **aucune map** : le mannequin est posé dans `MenuScene`. La bascule
|
||||
de caméra se fait pendant que l'écran est noir — un travelling depuis l'angle du
|
||||
menu traverserait le décor.
|
||||
|
||||
**L'écran part toujours de l'apparence par défaut**, jamais de la persistée.
|
||||
C'est un provisoire assumé : à terme l'apparence appartiendra à la **sauvegarde**
|
||||
et non au joueur, et l'on regardera au clic sur *Jouer* si cette partie a déjà un
|
||||
personnage. `SetupCustomization(Preview, Appearance)` prend déjà l'apparence en
|
||||
paramètre — il n'y aura qu'à changer son origine.
|
||||
|
||||
Laisser **`Customization Menu Class` vide** sur `BP_MainMenuPlayerController`
|
||||
restaure l'ancien comportement (Jouer lance directement). Pratique pour tester
|
||||
autre chose sans traverser l'écran.
|
||||
|
||||
## 12. Les pièges du moteur rencontrés
|
||||
|
||||
Aucun n'était visible depuis le code. Ils sont ici pour ne pas être redécouverts.
|
||||
|
||||
### 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 introuvable, sans message.
|
||||
|
||||
D'où `EmberPart` / `EmberColor` / `EmberAppearanceDump` posées sur `AFpsPlayer`,
|
||||
qui délègue. *(`EmberJoin` marche parce que `UGameInstance`, lui, route vers ses
|
||||
subsystems.)*
|
||||
|
||||
### `OverrideMaterials` survit à un changement de mesh
|
||||
|
||||
`SetSkeletalMeshAsset` remplace l'asset mais ne touche pas au tableau
|
||||
`OverrideMaterials`, qui appartient au **composant**. Sans vidage préalable,
|
||||
passer du corps A au corps B garde le MID dérivé de `MI_BodyA_a` : on obtient une
|
||||
silhouette féminine peinte avec la peau et les abdominaux du modèle masculin.
|
||||
Le symptôme trompe, parce que le mesh, lui, a bien changé.
|
||||
|
||||
D'où `SetPartMesh()`, qui appelle `EmptyOverrideMaterials()` avant d'assigner —
|
||||
et ne fait rien si le mesh est déjà le bon, pour que les MID survivent à un simple
|
||||
changement de couleur.
|
||||
|
||||
### 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`, valeur `Eye`), et
|
||||
si aucun ne correspond on ne pose rien et on l'écrit dans le log : écraser le slot
|
||||
de la peau repeint le visage entier de la couleur des yeux, symptôme bien plus
|
||||
long à diagnostiquer qu'une ligne de log.
|
||||
|
||||
### Un nom de paramètre matériau se lit dans le Material INSTANCE
|
||||
|
||||
Le binaire de `M_Head_Base` contient la chaîne « 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.
|
||||
|
||||
### `LeaderPoseComponent` est `BlueprintReadOnly`
|
||||
|
||||
Elle n'apparaît **jamais** dans le Details panel — 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()`, en C++ ou en Construction Script.
|
||||
|
||||
### `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 mesure à peu près autant. Pour juger une taille :
|
||||
viewport en vue **Front**, et la ligne verte (`Z = 0`) comme repère — le bas de la
|
||||
capsule est à `-HalfHeight`, le haut à `+HalfHeight`.
|
||||
|
||||
### `Initialize` masque une virtuelle d'`UUserWidget`
|
||||
|
||||
`UUserWidget::Initialize()` existe et est virtuelle. Une surcharge de signature
|
||||
différente la **masque** au lieu de la surcharger (C4263/C4264). D'où
|
||||
`SetupCustomization`. Même famille de piège que les membres nommés `Player` ou
|
||||
`Slot` dans une classe dérivée du moteur.
|
||||
|
||||
## 13. Les commandes console
|
||||
|
||||
Sur le pawn, en partie. Elles appliquent en local **et** soumettent (persistance
|
||||
+ annonce au serveur), donc elles testent toute la chaîne.
|
||||
|
||||
```
|
||||
EmberPart body 0..1 silhouette
|
||||
EmberPart head 0..5 visage
|
||||
EmberPart hair 0..12 coiffure
|
||||
EmberPart eyebrows 0..5 sourcils
|
||||
EmberPart beard 0..5 barbe
|
||||
EmberPart mustache 0..5 moustache
|
||||
EmberPart eyes 0..4 forme d'iris
|
||||
EmberPart beard -1 retire la pièce
|
||||
|
||||
EmberColor skin 0..11 teint
|
||||
EmberColor hair 0..5 cheveux + barbe + sourcils + moustache
|
||||
EmberColor eye 0..4 iris
|
||||
EmberColor underwear 0..3 sous-vêtement
|
||||
|
||||
EmberAppearanceDump état courant + nombre d'options par catégorie
|
||||
```
|
||||
|
||||
`EmberAppearanceDump` est le premier réflexe de diagnostic : un compte à **0**
|
||||
signale une liste vide dans le catalogue.
|
||||
|
||||
## 14. Dépannage
|
||||
|
||||
| Symptôme | Cause |
|
||||
|---|---|
|
||||
| La colonne de l'écran est vide | `Catalog`, `Category Widget Class` ou `Option Widget Class` non assignés dans les **Class Defaults** de `WBP_CharacterCustomization` (mode **Graph** → *Class Defaults*) |
|
||||
| *Jouer* lance la partie sans montrer l'écran | `Customization Menu Class` vide sur `BP_MainMenuPlayerController` |
|
||||
| Écran noir qui ne s'éclaircit pas | aucun `BP_CharacterPreview` dans la map — un avertissement l'écrit dans l'Output Log |
|
||||
| Le visage ne suit pas la couleur de peau | `Head Skin Color Parameter` faux dans le catalogue (`Skin`, pas `Skin Base color`) |
|
||||
| Le visage entier prend la couleur des yeux | `Eye Material Slot Hint` ne correspond à aucun slot — le log liste les slots disponibles |
|
||||
| Le corps B garde la peau du corps A | régression de `SetPartMesh()` : les overrides ne sont plus vidés |
|
||||
| Une pièce flotte détachée du corps | leader pose non posé sur ce composant |
|
||||
| Corps figé en pose de référence | `Anim Class` absente du Body Set |
|
||||
| Les pastilles de couleur sont invisibles | le pinceau d'`OptionImage` est en `Draw As = Image` sans texture — un pinceau en mode Image sans texture ne peint rien |
|
||||
| En PIE à 2, les deux joueurs sont identiques | normal : les deux fenêtres lisent le même `.ini`. Change l'un des deux à la console |
|
||||
|
||||
## 15. Limites connues
|
||||
|
||||
- **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.
|
||||
- **La physique de cheveux est perdue en leader pose.** Coiffures, barbes et
|
||||
moustaches ont leur physics asset, mais l'AnimBP post-process d'un *follower*
|
||||
ne tourne pas. La démo du pack accepte déjà ce compromis.
|
||||
- **Tatouages et maquillage ne sont pas exposés.** `M_Head_Base` et
|
||||
`M_Body_Base` en ont pourtant les paramètres : ce ne sont que des couleurs et
|
||||
des masques de plus à ajouter au catalogue, aucune refonte.
|
||||
- **Pas d'animation accroupie.** Le pack n'en livre pas. Mais le squelette est
|
||||
celui du **Mannequin UE5** (`ik_foot_root`, `index_metacarpal_l`…), donc toute
|
||||
animation vendue « UE5 Mannequin compatible » se branche **sans retarget**.
|
||||
|
||||
## 16. Étendre
|
||||
|
||||
**Ajouter une catégorie** (tatouages, maquillage) :
|
||||
|
||||
1. un champ dans `FCharacterAppearance`
|
||||
2. une valeur dans `ECharacterPartCategory` (mesh) ou une palette (couleur)
|
||||
3. une valeur dans `ECustomizationCategory` + son libellé dans
|
||||
`CharacterCustomizationLabels::Get`
|
||||
4. les branches correspondantes dans `GetOptionCountForCategory`,
|
||||
`GetCurrentIndexForCategory` et `SetIndexForCategory`
|
||||
|
||||
L'écran, lui, n'a rien à changer : la colonne se construit depuis l'enum.
|
||||
|
||||
**Brancher les sauvegardes** : une seule ligne, dans
|
||||
`AMainMenuPlayerController::OpenCustomization()` — passer l'apparence de la save
|
||||
au lieu de `FCharacterAppearance()`.
|
||||
Reference in New Issue
Block a user