(Feat) Add footsteps
This commit is contained in:
+100
-7
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
LFS
Vendored
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user