(Feat) Add footsteps

This commit is contained in:
2026-08-11 15:23:10 +02:00
parent e8e128457a
commit 176beab9fc
4776 changed files with 18806 additions and 121 deletions
+100 -7
View File
@@ -1,6 +1,6 @@
# Survival_projet — Unreal Engine 5.8
Jeu de survie solo à la première personne, gameplay écrit en **C++**, Blueprints réservés au
Jeu de survie coop en ligne via steam p2p à la première personne, gameplay écrit en **C++**, Blueprints réservés au
câblage d'assets. Inspirations assumées : Raft, Aloft, Valheim, esthétique solarpunk.
Assets d'environnement : pack **SoStylized** (sa végétation n'est pas à l'échelle humaine).
@@ -124,12 +124,13 @@ qu'une compilation propre l'est vraiment.
| Menu principal | `MainMenuGameMode`, `MainMenuPlayerController`, `MainMenuWidget`, `MenuCameraSpot` |
| Fabrication | `CraftingRecipeDataAsset`, `CraftingRecipeBook`, `CraftingComponent`, `CraftingWidget`, `CraftingRecipeSlotWidget`, `CraftingIngredientRowWidget` |
| Cuisson | `CookingStation` + bloc *Cooking* / *Fuel* de `ItemDataAsset`**doc complète : [Docs/CookingSystem.md](../Docs/CookingSystem.md)** |
| Coffres | `StorageContainer`, `StorageScreenWidget` + `UInventoryComponent::TransferSlot` / `TransferAllTo` |
| Coffres | `StorageContainer` + le panneau de coffre d'`InventoryScreenWidget` et `UInventoryComponent::TransferSlot` / `TransferAllTo`**doc complète : [Docs/Storage.md](../Docs/Storage.md)** |
| Écran à onglets (inventaire / craft) | `InventoryScreenWidget` |
| Menu de pause | `PauseMenuWidget` + la partie pause de `FpsPlayerController` |
| Réglages | `SurvivalUserSettings`, `SettingsMenuWidget`, `SettingsTabWidget`, `SettingsGameplayWidget`, `SettingRowWidget` |
| Musique | `MusicDirector`, `MusicSubsystem`, `MusicPlaylistDataAsset` |
| Audio (volumes) | `SurvivalAudioSettings`, `SettingsAudioWidget` + `USurvivalUserSettings::ApplyAudioSettings` |
| Bruits de pas | `FootstepComponent`, `FootstepSoundsDataAsset` + les `PhysicalSurfaces` de `DefaultEngine.ini` |
| Fondus, boîtes de dialogue | `ScreenFadeComponent`, `ConfirmDialogWidget` |
| Coop Steam (sessions) | `SessionSubsystem`, `SurvivalGameMode`, `SurvivalGameState`, `SurvivalPlayerState` |
| Pseudo au-dessus des joueurs | `PlayerNameplateComponent`, `PlayerNameplateWidget` |
@@ -487,6 +488,57 @@ du joueur dans lui-même.
(`FApp::UnfocusedVolumeMultiplier`, dont la valeur d'usine est 0). Le réglage ne fait que
l'exposer ; il ne passe pas par le mix, puisqu'il baisse l'application entière bien en amont.
### Bruits de pas — la distance, pas les AnimNotify
- **Le pas est déclenché par la DISTANCE parcourue**, jamais par un notify d'animation.
`UFootstepComponent` accumule le déplacement horizontal et sonne tous les `StrideLength` cm.
Trois raisons : le corps 3e personne du joueur local est en `bOwnerNoSee`, les notifies posés
dans un **BlendSpace** se déclenchent selon l'échantillon dominant et sautent ou doublent un pas
au passage marche → course, et les bras FPS sont sur `BasePose_Skeleton`, qui n'a rien à voir
avec `SK_BodyA` — aucun notify ne pourrait servir aux deux.
- **`StrideLength` se calcule, il ne se devine pas** : `Vitesse / (2 × StrideFrequency)` du profil
correspondant de `UHeadBobComponent`, le facteur 2 parce qu'une foulée compte deux pas. D'où
143 / 182 / 210 cm pour accroupi / marche / sprint. Changer `WalkSpeed` ou `SprintSpeed` sans
retoucher ces valeurs désynchronise le son de la tête, et **ça s'entend avant de se voir**.
- **Rien n'est répliqué**, exactement comme `UPlayerBodyAnimInstance` : chaque machine calcule les
pas de tous les pawns qu'elle connaît, à partir de la vitesse et du mode de déplacement déjà
synchronisés. Un multicast par pas coûterait trois messages par seconde et par joueur pour un
résultat identique.
- **Saut et réception passent par `MovementModeChangedDelegate`, surtout pas par
`ACharacter::Landed` / `OnJumped`.** `Landed` vient de `ProcessLanded`, qui ne tourne que là où
le mouvement est réellement simulé, et `OnJumped` que là où l'input est lu : sur le pawn d'un ami
vu depuis notre machine, ni l'un ni l'autre n'existe. Le **mode de déplacement**, lui, est
répliqué, donc son délégué est diffusé partout. Et c'est le **signe de `Velocity.Z`** qui
distingue un saut d'une sortie de rebord, comme dans l'AnimBP du corps.
- **La vitesse de chute s'échantillonne PENDANT la chute** : au moment où le mode repasse à
`Walking`, le moteur a déjà remis `Velocity.Z` à zéro. Le cas où le tick à 20 Hz n'a pas eu le
temps de tourner est précisément celui d'une chute négligeable, qu'on veut de toute façon muette.
- **`bTraceComplex = true` n'est pas négociable.** Un `PhysicalMaterial` posé sur un **matériau**
n'est rendu que par un trace per-poly ; le trace simple remonte celui du `BodySetup` du mesh,
presque toujours vide. Symptôme d'un décochage : tout le décor sonne sur `DefaultSurface`, ce
qu'on lit comme « la table n'est pas remplie ». Coût réel : deux à trois traces par seconde et
par personnage, jamais un par frame — le tick tourne à **20 Hz** et ne fait qu'une soustraction
de vecteurs, le trace n'a lieu qu'au moment du pas.
- **Sur un Material INSTANCE, le champ *Phys Material* a une case à cocher devant lui, et elle
compte.** `UMaterialInstance::GetPhysicalMaterial()` teste `bOverridePhysMaterial` **avant** de
regarder le pointeur : décochée, l'instance hérite de son parent quoi qu'on ait glissé dans le
champ ; cochée mais vide, elle retombe sur `DefaultPhysMaterial` et **coupe** l'héritage.
Corollaire précieux pour SoStylized, dont les 398 instances descendent d'une poignée de parents :
taguer `MI_Rock`, `MI_Grass`, `MI_Snow`… suffit à couvrir toute leur descendance, et l'on ne
redescend dans un enfant que pour l'exception.
- **L'ordre des `PhysicalSurfaces` dans `DefaultEngine.ini` EST le contrat.** Le moteur ne stocke
que le numéro, jamais le nom : insérer une matière au milieu renumérote tout ce qui suit et les
`PM_` existants pointent silencieusement ailleurs. **Toute nouvelle surface s'ajoute à la fin.**
- Le pas du joueur local part en **2D** (`bPlayLocalStepsIn2D`), celui des autres en 3D à leurs
pieds : joué en 3D depuis ses propres chevilles, le pas local passe par l'atténuation et sonne
creux alors qu'il devrait être le son le plus présent du mixage. Les cues du pack n'ont pas de
`SoundClass`, donc les deux chemins tombent sur `SC_SFX` — le curseur *Effets* les couvre sans
qu'on ait rien à taguer.
- Les noms des surfaces décrivent le **monde**, pas le pack : `Grass` existe alors que le pack
livre *Leaves*, `Stone` alors qu'il livre *Concrete*. C'est `DA_Footsteps` qui fait la
traduction, et c'est sa raison d'être — changer de pack de sons ne doit toucher ni la config ni
les `PM_`.
### Décisions de gameplay figées
- **Inventaire** : un seul `TArray` de 30 slots. Index **0-9 = barre rapide**, **10-29 = grille**.
@@ -494,6 +546,20 @@ du joueur dans lui-même.
tableau uniquement pour que le glisser-déposer n'ait aucune conversion d'index à faire.
- **Ramassage** : complète les piles existantes partout, puis ouvre un slot dans la **barre en
priorité**, puis déborde dans la grille.
- **Le volume visable d'un `APickupItem` est une BOÎTE ajustée au mesh, jamais une sphère.** Un
seul `BP_Pickup` sert des centaines d'objets : sa taille se calcule à partir de
`UStaticMesh::GetBoundingBox()` dans `FitInteractionBox()`, appelée par `ApplyItemVisuals()`
donc par `OnConstruction` **et** `OnRep_ItemData`. Trois raisons de ne pas revenir à la sphère :
son rayon englobant vaut la **demi-diagonale**, ce qui est le pire cas exact pour les objets
allongés du jeu (une branche de 120×20×15 donnait 62 cm de rayon, vingt-sept fois son volume) ;
elle ne peut pas exprimer une orientation, là où la boîte tourne avec l'acteur ; et le symptôme
n'est pas cosmétique — deux pickups côte à côte voyaient leurs volumes se chevaucher, et le
trace retenait le **premier touché** plutôt que l'objet visé. La marge (`InteractionBoxPadding`)
est **additive en cm et jamais multiplicative** : un facteur appliqué à la demi-épaisseur d'une
branche n'ajoute rien tout en rallongeant son grand axe. Deux pièges partagés avec
`AStorageContainer::FitInteractionBox` : lire les bounds de l'**asset** et pas `Mesh->Bounds`,
déjà multipliées par l'échelle que `SetBoxExtent` remultiplie ; et recentrer sur
`GetCenter()`, le pivot d'un mesh de pack étant presque toujours au sol.
- **Glisser** : normal = pile entière, **Maj** = moitié arrondie au supérieur, **Ctrl** = un seul.
Lâcher hors de la grille jette au sol ; lâcher entre deux cases est absorbé (jamais de perte
accidentelle).
@@ -507,8 +573,26 @@ du joueur dans lui-même.
- Après un respawn, le pawn est un **nouvel acteur** : les widgets doivent se rebrancher via
`BindToOwningPawn()`, appelé depuis `OnPossess`. Même raison côté réglages : le pawn les
**relit** à son `BeginPlay` et s'abonne à `OnSurvivalSettingsApplied`.
- **Échap** a une priorité explicite dans `HandlePauseInput()` : boîte de confirmation →
réglages → inventaire → pause. Sans cet ordre, deux écrans se retrouvent ouverts en même temps.
- **Échap** a une priorité explicite dans `HandleEscapeInput()` : boîte de confirmation →
réglages → coffre → inventaire → pause. Sans cet ordre, deux écrans se retrouvent ouverts en
même temps.
- **Un widget UMG qui a le focus clavier COUPE la route des touches vers Enhanced Input.** Les
widgets vivent dans le `SGameLayerManager`, **frère** du `SViewport` et pas son enfant : une
touche remonte les parents du widget focalisé et n'atteint jamais le viewport. Or un `UButton`
prend le focus au clic. Symptôme exact rencontré : Échap refermait l'inventaire (que des cases,
rien de focusable) mais plus le menu de pause dès qu'on avait cliqué dedans — et jamais après un
aller-retour dans les réglages, qui prennent le focus (`SetKeyboardFocus`) sans le rendre.
La parade est donc double, et les deux moitiés comptent : tout écran à boutons est `Focusable`
et relaie Échap par `NativeOnKeyDown` vers `HandleEscapeInput()` — un seul point d'entrée, donc
la priorité ci-dessus reste vraie quel que soit le chemin — **et** l'écran qui redevient la
couche active **reprend** le focus (`UPauseMenuWidget::SetInteractive(true)`). Corollaire
assumé : menu de pause ouvert, Tab et les touches de barre rapide ne répondent plus.
- **Dans la map du menu, `AMainMenuPlayerController` ne binde AUCUNE action d'input.** Tout Échap
qui y fonctionne passe donc par le `NativeOnKeyDown` d'un widget — il n'existe pas d'autre
chemin, et un écran qui l'oublie n'a simplement pas de touche Retour. C'était le cas de
`UCharacterCustomizationWidget`. Son `SetWidgetToFocus()` côté contrôleur ne suffisait pas non
plus : **Slate refuse de focaliser un widget qui ne déclare pas supporter le focus clavier**,
donc l'appel ne faisait rien tant que `SetIsFocusable(true)` manquait.
- **Menu de pause : il ne met plus rien en pause.** `SetPause()` a disparu du projet. En coop la
pause est impossible par nature — côté client l'appel est ignoré (elle appartient à l'autorité),
côté hôte elle figerait le monde des quatre joueurs parce qu'un seul a ouvert son menu. Le monde
@@ -576,9 +660,14 @@ du joueur dans lui-même.
referme le couvercle — par Échap, par la mort, ou parce que le joueur s'est éloigné
(`MaxStorageDistance`, surveillé par un timer à 0,25 s, jamais un Tick). Un coffre qui
déciderait seul finirait ouvert dans la moitié de ces cas.
- **`LidPivot`**, un `SceneComponent` vide entre la caisse et le couvercle : le pivot de
`SM_Chest_Top` est celui du pack, presque jamais la charnière. Sans lui le couvercle tourne
autour de son centre et traverse la caisse. Le coffre ne tick **que** pendant l'animation.
- **Le couvercle tourne sur le pivot de son propre mesh.** `LidMesh` est enfant direct de la
caisse et c'est lui qu'on fait tourner : un `UStaticMeshComponent` pivote toujours autour de
l'origine de son mesh, donc la charnière est celle que le pack a donnée à `SM_Chest_Top`, sans
composant intermédiaire à placer à la main. *(Un `SceneComponent` `LidPivot` a existé pour
découpler les deux ; il a été retiré — un pivot vide laissé à `(0,0,0)` fait tourner le
couvercle autour du centre de la caisse, et c'était le symptôme.)* Si un jour un pack pose ce
pivot au centre du couvercle, on le corrige **dans l'asset** (Modeling Mode > XForm > Edit
Pivot), pas dans le code. Le coffre ne tick **que** pendant l'animation.
- **Toute la collision du coffre tient dans une `UBoxComponent`, les meshes n'en portent
aucune.** C'est la seule exception à la règle « le mesh EST la surface d'interaction » posée
par `ACraftingStation`, et elle vient de ce qu'un coffre est en **deux morceaux dont un
@@ -675,6 +764,10 @@ Les vraies recettes restent à écrire : celles en place sont deux `DA_Craft_Tes
### Chantier en cours : customisation de personnage
**Doc complète : [Docs/CharacterCustomization.md](../Docs/CharacterCustomization.md)** — deux
moitiés séparées, une pour l'artiste (ajouter une coiffure, une couleur, un corps, sans code) et
une pour le développeur (architecture, réseau, pièges du moteur).
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.
+29
View File
@@ -150,3 +150,32 @@ ManualIPAddress=
DefaultSoundClassName=/Game/Game/Sounds/Mix/SC_SFX.SC_SFX
DefaultBaseSoundMix=/Game/Game/Sounds/Mix/SM_Settings.SM_Settings
; ==================================================================
; Surfaces physiques -- bruits de pas
; ==================================================================
;
; Une entree par matiere du pack Essential_Foosteps_SK. C'est cette liste que
; l'on retrouve dans le menu deroulant "Surface Type" d'un PhysicalMaterial, et
; c'est la CLE de la table de UFootstepSoundsDataAsset.
;
; L'ORDRE EST LE CONTRAT : SurfaceType1 est le premier de cette liste, et le
; moteur ne stocke que ce numero, jamais le nom. Inserer une matiere au milieu
; renumerote donc tout ce qui suit, et les PhysicalMaterial deja crees pointent
; silencieusement sur la mauvaise matiere -- le decor se met a sonner en verre.
; Toute nouvelle surface s'AJOUTE A LA FIN, sans exception.
;
; SurfaceType_Default (l'entree 0, non listee ici) reste ce que porte tout mesh
; non tague : c'est lui qui tombe sur DefaultSurface dans la banque de sons.
[/Script/Engine.PhysicsSettings]
+PhysicalSurfaces=(Type=SurfaceType1,Name="Dirt")
+PhysicalSurfaces=(Type=SurfaceType2,Name="Grass")
+PhysicalSurfaces=(Type=SurfaceType3,Name="Wood")
+PhysicalSurfaces=(Type=SurfaceType4,Name="Stone")
+PhysicalSurfaces=(Type=SurfaceType5,Name="Gravel")
+PhysicalSurfaces=(Type=SurfaceType6,Name="Sand")
+PhysicalSurfaces=(Type=SurfaceType7,Name="Metal")
+PhysicalSurfaces=(Type=SurfaceType8,Name="Glass")
+PhysicalSurfaces=(Type=SurfaceType9,Name="Snow")
+PhysicalSurfaces=(Type=SurfaceType10,Name="Slush")
+PhysicalSurfaces=(Type=SurfaceType11,Name="Water")
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More