(Feat) Add Storage Chests + Item Details Panel
Coffres : AStorageContainer, un UInventoryComponent configure en conteneur, affiche comme panneau de WBP_InventoryScreen plutot que dans un ecran a lui. Deplacement unifie par UInventoryComponent::TransferSlot / TransferAllTo. Panneau de detail : UItemDetailsWidget, pose DANS WBP_Inventory a droite de la grille. Icone, nom, separation, description, barre d'usure. Il recoit un couple (inventaire, index) et s'abonne a OnInventoryChanged, donc une charge consommee sous le curseur se voit. Une case ne connait plus sa grille : elle diffuse OnHoverChanged. La barre rapide s'y abonne aussi et relaie par le PlayerController, seul a posseder a la fois le HUD et l'ecran. Le panneau efface la case qu'on QUITTE et non lui-meme, sinon passer de la barre a la grille viderait ce qui vient d'etre affiche. L'ecran allume et eteint le panneau (SetItemDetailsEnabled) : WBP_Inventory etant instancie deux fois en mode coffre, une case a cocher par instance serait un bug qui attend qu'on oublie de la decocher. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
# Système de fabrication
|
||||
|
||||
Documentation du système de craft : architecture, API, câblage éditeur, décisions de design
|
||||
et dépannage.
|
||||
|
||||
---
|
||||
|
||||
## 1. Vue d'ensemble
|
||||
|
||||
Quatre couches, chacune ignorante de celle du dessus.
|
||||
|
||||
```
|
||||
DONNÉES DA_Craft_* ──► DA_RecipeBook
|
||||
│
|
||||
LOGIQUE UCraftingComponent (sur le pawn)
|
||||
│ lit UInventoryComponent du même acteur
|
||||
│
|
||||
INTERFACE UCraftingWidget ──► UCraftingRecipeSlotWidget
|
||||
└──► UCraftingIngredientRowWidget
|
||||
│
|
||||
CONTRÔLE AFpsPlayerController (ouvre, ferme, décide du HUD)
|
||||
▲
|
||||
MONDE ACraftingStation (l'établi, via l'interaction)
|
||||
```
|
||||
|
||||
Règle qui gouverne tout le reste : **le composant calcule, les widgets affichent.**
|
||||
Aucun widget n'interroge l'inventaire directement.
|
||||
|
||||
---
|
||||
|
||||
## 2. Les données
|
||||
|
||||
### `UCraftingRecipeDataAsset`
|
||||
|
||||
Un asset par recette (`DA_Craft_*`). Donnée pure, **aucun état**.
|
||||
|
||||
| Champ | Rôle |
|
||||
|---|---|
|
||||
| `RecipeId` | identifiant stable pour la sauvegarde des recettes débloquées |
|
||||
| `Ingredients` | tableau de `FCraftIngredient { Item, Quantity }` |
|
||||
| `ResultItem` / `ResultQuantity` | ce qui est produit |
|
||||
| `RequiredStation` | `Hands` ou `Workbench` |
|
||||
| `DisplayNameOverride` / `IconOverride` | à laisser vides sauf cas particulier |
|
||||
|
||||
`GetDisplayName()` et `GetIcon()` retombent sur le `ResultItem` quand les overrides sont vides.
|
||||
**Passer toujours par ces deux fonctions**, jamais par `ResultItem` directement.
|
||||
|
||||
`IsDataValid()` refuse à la sauvegarde une recette sans résultat, et signale un `RecipeId` vide
|
||||
ou un objet listé deux fois dans les ingrédients. Ces trois erreurs ne planteraient pas — elles
|
||||
produiraient un comportement faux et silencieux.
|
||||
|
||||
### `UCraftingRecipeBook`
|
||||
|
||||
Le catalogue (`DA_RecipeBook`). Un simple `TArray` de recettes, dont **l'ordre est l'ordre
|
||||
d'affichage de la grille**.
|
||||
|
||||
Un catalogue plutôt qu'un scan de l'AssetManager : contenu déterministe, rien ne se charge par
|
||||
surprise, et l'ordre devient une donnée de design réglée à la souris. Un catalogue plutôt qu'une
|
||||
liste sur le composant, aussi : un établi pourra pointer son propre livre.
|
||||
|
||||
### `ECraftingStation`
|
||||
|
||||
```cpp
|
||||
enum class ECraftingStation : uint8 { Hands, Workbench, Count /*sentinelle*/ };
|
||||
```
|
||||
|
||||
`Count` n'est jamais assignée à une recette. Elle sert de valeur « tout » pour le filtre
|
||||
d'affichage et permet de parcourir l'enum pour construire des boutons.
|
||||
|
||||
**Fourneau et feu de cuisson n'y figurent pas volontairement** : ils ne passent par aucun menu.
|
||||
Ce seront des acteurs posés dans le monde où l'on dépose une ressource et où le mesh change
|
||||
d'état, façon Raft. Leur file d'attente leur appartiendra.
|
||||
|
||||
---
|
||||
|
||||
## 3. La logique — `UCraftingComponent`
|
||||
|
||||
Sur le pawn, à côté de `UInventoryComponent`, qu'il résout une fois à l'initialisation.
|
||||
|
||||
### API
|
||||
|
||||
| Appel | Usage |
|
||||
|---|---|
|
||||
| `GatherRecipes(Out, StationFilter)` | peupler la grille |
|
||||
| `CanCraft(Recipe, OutReason)` | activer ou griser un bouton |
|
||||
| `GatherIngredientStatus(Recipe, Out)` | les compteurs « 2/3 » |
|
||||
| `Craft(Recipe)` | le clic |
|
||||
| `HasStation(Station)` | test ponctuel |
|
||||
| `AddAvailableStation` / `RemoveAvailableStation` | par un poste de travail |
|
||||
|
||||
Délégués : `OnCraftSucceeded(Recipe)`, `OnAvailableStationsChanged()`,
|
||||
`OnCraftOverflow(Item, Quantity, RemainingUses)`.
|
||||
|
||||
`GatherRecipes` n'a **pas** de valeur par défaut sur `StationFilter` : UHT refuse une entrée
|
||||
`UMETA(Hidden)` comme défaut, et dé-cacher `Count` la rendrait sélectionnable dans le champ
|
||||
`Required Station` d'une recette. L'appelant écrit `ECraftingStation::Count` pour « tout ».
|
||||
|
||||
### Postes disponibles
|
||||
|
||||
`TMap<ECraftingStation, int32>` de **compteurs**, pas un `TSet`. Deux établis dont les portées
|
||||
se chevauchent s'ajoutent tous les deux ; sortir du premier ne doit pas retirer l'accès alors
|
||||
qu'on est encore devant le second. `Hands` est initialisée à 1 et ne peut pas être retirée.
|
||||
|
||||
La diffusion de `OnAvailableStationsChanged` n'a lieu que sur les transitions **0 → 1** et
|
||||
**1 → 0**. Entrer dans un deuxième établi ne reconstruit pas la grille pour rien.
|
||||
|
||||
**Disponibilité et filtre d'affichage sont deux notions distinctes.** La disponibilité est une
|
||||
*condition* de fabrication, le filtre est *visuel*. Les mélanger finirait par rendre un craft
|
||||
impossible à cause d'un bouton d'interface, et ce genre de bug ne se comprend jamais sur le
|
||||
moment.
|
||||
|
||||
### `Craft()` — l'ordre compte
|
||||
|
||||
```
|
||||
1. CanCraft() → refus, rien n'est touché
|
||||
2. RemoveItem() par ingrédient → consommation
|
||||
3. AddItem(résultat) → ajout
|
||||
4. reste > 0 ? OnCraftOverflow → le pawn le pose au sol
|
||||
```
|
||||
|
||||
**On consomme avant d'ajouter.** Refuser parce que l'inventaire est plein serait faux :
|
||||
consommer trois branches libère justement la case du résultat. Et si le résultat ne rentre
|
||||
malgré tout pas, il part par `OnCraftOverflow` — **aucune ressource ne disparaît, ni à l'entrée
|
||||
ni à la sortie**.
|
||||
|
||||
Le composant ne fait pas apparaître le pickup lui-même : il ne sait pas engendrer un acteur, et
|
||||
un établi qui porterait le même composant déposerait son surplus sur sa propre table. C'est
|
||||
`AFpsPlayer::HandleCraftOverflow()` qui s'en charge, abonné dans son `BeginPlay`. Si personne
|
||||
n'écoute, un `UE_LOG(Error)` le signale au lieu de perdre l'objet en silence.
|
||||
|
||||
Le résultat passe par `AddItem()`, donc **exactement comme un ramassage** : piles entamées
|
||||
d'abord, puis barre rapide, puis grille.
|
||||
|
||||
---
|
||||
|
||||
## 4. L'interface
|
||||
|
||||
### `UInventoryScreenWidget` — l'écran à onglets
|
||||
|
||||
Ce que Tab ouvre. Il ne contient aucune logique : les deux pages restent des widgets autonomes,
|
||||
et **`UInventoryWidget` n'a pas changé d'une ligne** en devenant une page.
|
||||
|
||||
| BindWidget | Type | Obligatoire |
|
||||
|---|---|---|
|
||||
| `TabSwitcher` | Widget Switcher | oui |
|
||||
| `InventoryTabButton` | Button | oui |
|
||||
| `CraftingTabButton` | Button | oui |
|
||||
| `InventoryPage` | ton `WBP_Inventory` | oui |
|
||||
| `CraftingPage` | ton `WBP_Crafting` | oui |
|
||||
|
||||
- `GetHoveredSlotIndex()` renvoie `INDEX_NONE` dès que l'onglet Fabrication est affiché, sinon
|
||||
la touche « jeter » agirait sur une case cachée sous le panneau de craft.
|
||||
- `ShowPage()` prend le **widget**, jamais un index : l'ordre des enfants d'un WidgetSwitcher se
|
||||
change d'un glisser dans la Designer, un `0`/`1` en dur inverserait silencieusement les onglets.
|
||||
- Diffuse `OnTabChanged(bool bInventoryTabActive)` et **rien d'autre**. Ce que ça implique pour
|
||||
le HUD ne le regarde pas.
|
||||
|
||||
### `UCraftingWidget` — la page de fabrication
|
||||
|
||||
| BindWidget | Type | |
|
||||
|---|---|---|
|
||||
| `RecipeGrid` | Uniform Grid Panel | obligatoire |
|
||||
| `ResultIcon` | Image | obligatoire |
|
||||
| `ResultNameText` | Text Block | obligatoire |
|
||||
| `CraftButton` | Button | obligatoire |
|
||||
| `DetailsPanel` | n'importe quel Widget | optionnel |
|
||||
| `IngredientList` | n'importe quel Panel | optionnel |
|
||||
| `ResultQuantityText` | Text Block | optionnel |
|
||||
| `ResultDescriptionText` | Text Block | optionnel |
|
||||
| `EmptyGridMessage` | n'importe quel Widget | optionnel |
|
||||
|
||||
Réglages : `RecipeSlotClass`, `IngredientRowClass`, `ColumnCount`.
|
||||
|
||||
`IngredientList` est déclaré en **`UPanelWidget`** et non en `UVerticalBox` : le code ne fait qu'y
|
||||
ajouter des enfants, donc passer à un Wrap Box ou un Grid Panel ne demande pas une ligne de C++.
|
||||
|
||||
`DetailsPanel` est déclaré en `UWidget` : le nom peut donc être porté par une **Border**, ce qui
|
||||
masque le fond en même temps que le contenu quand rien n'est sélectionné.
|
||||
|
||||
**Rafraîchissement, sans jamais de Tick :**
|
||||
|
||||
- abonné à `OnInventoryChanged` → recalcule seulement les teintes et l'état du bouton
|
||||
- abonné à `OnAvailableStationsChanged` → recalcule la liste
|
||||
|
||||
`RebuildGrid` compare la nouvelle liste à l'ancienne et **sort immédiatement si elle est
|
||||
identique**. Les lignes d'ingrédient sont **recyclées** et non recréées, puisque `RefreshDetails`
|
||||
repasse à chaque objet ramassé.
|
||||
|
||||
`HandleCraftClicked` ne re-vérifie pas `CanCraft` : `Craft()` le refait de toute façon, et
|
||||
dupliquer la condition, c'est se garantir qu'elles divergeront.
|
||||
|
||||
### `UCraftingRecipeSlotWidget` — une case
|
||||
|
||||
`RecipeIcon` obligatoire ; `RecipeNameText` et `SelectionBorder` optionnels.
|
||||
Réglages : `CraftableTint`, `UncraftableTint`.
|
||||
|
||||
Les recettes non réalisables restent **visibles et cliquables**, seulement assombries : le joueur
|
||||
doit pouvoir consulter ce qui lui manque, sinon il ne sait pas quoi aller chercher.
|
||||
|
||||
> **La racine du Widget Blueprint doit avoir `Visibility = Visible`.** En
|
||||
> `Self Hit Test Invisible`, la case ne reçoit jamais le clic.
|
||||
|
||||
### `UCraftingIngredientRowWidget` — une ligne de coût
|
||||
|
||||
`IngredientCountText` obligatoire ; `IngredientIcon` et `IngredientNameText` optionnels.
|
||||
Réglages : `EnoughColor`, `MissingColor`, `CountFormat` (`{0}` possédé, `{1}` requis).
|
||||
|
||||
Elle n'interroge jamais l'inventaire : le composant lui remet un `FCraftIngredientStatus` déjà
|
||||
calculé. Une ligne qui compterait elle-même referait le travail autant de fois qu'il y a
|
||||
d'ingrédients, et pourrait afficher autre chose que ce sur quoi le bouton s'appuie.
|
||||
|
||||
---
|
||||
|
||||
## 5. Le contrôleur
|
||||
|
||||
### Ouverture et fermeture
|
||||
|
||||
Tout passe par **`SetInventoryScreenOpen(bOpen, bShowCraftingTab)`** — point d'entrée unique. Le
|
||||
mode d'input, le curseur, la coupure d'input du pawn, la barre rapide et le poste actif sont
|
||||
décidés à un seul endroit. Deux chemins parallèles finiraient par diverger.
|
||||
|
||||
| Appel | Comportement |
|
||||
|---|---|
|
||||
| `ToggleInventory()` | Tab — ouvre/ferme, toujours sur l'onglet Inventaire |
|
||||
| `ToggleCrafting()` | touche dédiée — ouvre sur Fabrication, bascule depuis Inventaire, referme depuis Fabrication |
|
||||
| `OpenCraftingAtStation(Station)` | depuis un établi — accorde le poste puis ouvre |
|
||||
|
||||
Quand l'écran est **déjà ouvert**, on ne fait que changer d'onglet : repasser par le mode d'input
|
||||
serait au mieux inutile, au pire nuisible — `SetIgnoreLookInput` gère un compteur qu'un appel en
|
||||
trop déséquilibrerait durablement.
|
||||
|
||||
### Le poste actif
|
||||
|
||||
`ActiveStation` retient le poste accordé par le dernier `OpenCraftingAtStation`. Il est rendu
|
||||
dans `SetInventoryScreenOpen(false, …)`, donc **quel que soit le chemin de sortie** — Tab, Échap,
|
||||
la mort. On fabrique à l'établi parce qu'on est en train de s'en servir, pas parce qu'on en a
|
||||
croisé un un jour.
|
||||
|
||||
### La barre rapide
|
||||
|
||||
| Situation | Barre rapide |
|
||||
|---|---|
|
||||
| écran fermé | visible |
|
||||
| onglet Inventaire | visible |
|
||||
| onglet Fabrication | masquée |
|
||||
|
||||
Une seule fonction décide : `UpdateHotbarVisibility()`. Sinon, fermer l'écran depuis l'onglet
|
||||
craft laisserait la barre cachée pour le reste de la partie.
|
||||
|
||||
Sa visibilité d'origine est **relevée à la création** et restaurée telle quelle, plutôt que
|
||||
forcée à `Visible` : le Blueprint a peut-être choisi `SelfHitTestInvisible`, et l'écraser
|
||||
changerait son comportement au clic sans qu'on l'ait demandé.
|
||||
|
||||
### Remappage
|
||||
|
||||
L'action est exposée dans l'onglet Touches sous l'identifiant **`ToggleCrafting`**, qui doit
|
||||
correspondre **exactement** au champ `Name` des *Player Mappable Key Settings* du mapping dans
|
||||
`IMC_Default`. Une faute de frappe donne une ligne « Non assignée » sans le moindre message.
|
||||
|
||||
---
|
||||
|
||||
## 6. L'établi — `ACraftingStation`
|
||||
|
||||
Un acteur interactif, détecté par le trace de `UInteractionComponent` comme un objet au sol.
|
||||
|
||||
Il ne fabrique rien et ne contient aucune recette : il ouvre l'écran en annonçant **son** type de
|
||||
poste, et le composant du joueur filtre en conséquence.
|
||||
|
||||
**Un seul `BP_CraftingStation` suffit pour tous les postes** — c'est `StationType` qui change par
|
||||
instance, comme `ItemData` sur `BP_Pickup`.
|
||||
|
||||
### Pourquoi pas de sphère d'interaction
|
||||
|
||||
`APickupItem` en a une parce que les meshes d'asset packs sont souvent livrés **sans collision
|
||||
simple**, et que le trace est lancé en `bTraceComplex = false` — il les traverserait.
|
||||
|
||||
Pour un meuble construit, c'est l'inverse : le trace s'arrête au **premier bloqueur**, donc une
|
||||
sphère englobant la structure serait touchée avant le mesh. Le prompt apparaîtrait en visant le
|
||||
vide autour, et la sphère masquerait un objet posé au pied du meuble.
|
||||
|
||||
Le `Mesh` est donc réglé explicitement sur le profil **`BlockAll`** : il empêche le joueur de
|
||||
traverser *et* bloque `Visibility` pour que le trace le trouve. Un profil laissant passer
|
||||
`Visibility` rendrait le poste inutilisable sans qu'aucune erreur ne le signale.
|
||||
|
||||
> **Prérequis** : le Static Mesh doit avoir une collision simple. Vérifie avec
|
||||
> **Show → Simple Collision** dans l'éditeur de mesh. Sinon, `Collision → Add Box Simplified
|
||||
> Collision`, ou pose une Box Collision dédiée dans le Blueprint.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ajouter une recette
|
||||
|
||||
1. `Content/Game/Data/Crafting` → clic droit → **Miscellaneous → Data Asset** → classe
|
||||
**`CraftingRecipeDataAsset`** → nommer `DA_Craft_<Objet>`.
|
||||
2. Remplir : `RecipeId`, `Ingredients`, `ResultItem`, `ResultQuantity`, `RequiredStation`.
|
||||
3. Ouvrir **`DA_RecipeBook`** et ajouter la recette dans `Recipes`, **à sa place dans l'ordre
|
||||
d'affichage**.
|
||||
4. Vérifier que le `ResultItem` a bien une **icône** — sans elle la case sera vide et tu croiras
|
||||
à un bug de code.
|
||||
5. Sélectionner les assets → clic droit → **Asset Actions → Validate Assets** → 0 erreur.
|
||||
|
||||
Aucune recompilation, aucun Blueprint à toucher.
|
||||
|
||||
## 8. Ajouter un poste de travail
|
||||
|
||||
1. Ajouter la valeur dans `ECraftingStation`, **avant `Count`**.
|
||||
2. Créer un `BP_` dérivé de `CraftingStation`, régler son `Mesh` et son `StationType`.
|
||||
3. Poser l'acteur dans le niveau.
|
||||
|
||||
Rien d'autre : la grille, le filtrage et le bouton suivent.
|
||||
|
||||
---
|
||||
|
||||
## 9. Récapitulatif des noms à ne pas casser
|
||||
|
||||
Un `BindWidget` obligatoire mal orthographié **empêche le Blueprint de compiler**, avec le nom du
|
||||
coupable dans le Compiler Results. Un `BindWidgetOptional` mal orthographié **échoue en silence**.
|
||||
|
||||
| Widget Blueprint | Noms obligatoires | Noms optionnels |
|
||||
|---|---|---|
|
||||
| `WBP_InventoryScreen` | `TabSwitcher`, `InventoryTabButton`, `CraftingTabButton`, `InventoryPage`, `CraftingPage` | — |
|
||||
| `WBP_Crafting` | `RecipeGrid`, `ResultIcon`, `ResultNameText`, `CraftButton` | `DetailsPanel`, `IngredientList`, `ResultQuantityText`, `ResultDescriptionText`, `EmptyGridMessage` |
|
||||
| `WBP_RecipeSlot` | `RecipeIcon` | `RecipeNameText`, `SelectionBorder` |
|
||||
| `WBP_IngredientRow` | `IngredientCountText` | `IngredientIcon`, `IngredientNameText` |
|
||||
|
||||
Assignations à ne pas oublier :
|
||||
|
||||
| Où | Champ | Valeur |
|
||||
|---|---|---|
|
||||
| `BP_FpsPlayer` → `CraftingComponent` | Recipe Book | `DA_RecipeBook` |
|
||||
| `BP_FpsPlayerController` | Inventory Screen Class | `WBP_InventoryScreen` |
|
||||
| `BP_FpsPlayerController` | Toggle Crafting Action | `IA_ToggleCrafting` |
|
||||
| `WBP_Crafting` | Recipe Slot Class | `WBP_RecipeSlot` |
|
||||
| `WBP_Crafting` | Ingredient Row Class | `WBP_IngredientRow` |
|
||||
|
||||
---
|
||||
|
||||
## 10. Pièges UMG rencontrés
|
||||
|
||||
Tous ont coûté du temps au moins une fois.
|
||||
|
||||
- **Un Canvas Panel a une taille désirée de zéro.** Imbriqué dans un conteneur en `Auto`, il
|
||||
disparaît. C'est ce qui arrive à `WBP_Inventory` devenu page d'un WidgetSwitcher.
|
||||
- **Dans un Vertical/Horizontal Box, un enfant en `Size = Fill` ne compte pas** dans la taille
|
||||
désirée du parent. Tous les enfants en Fill → le parent mesure zéro, et la Border qui l'entoure
|
||||
se réduit à son padding.
|
||||
- **Un Size Box impose une taille désirée, pas une taille allouée.** Un parent qui l'aligne en
|
||||
`Fill` l'étire quand même. Il faut `Auto` + `Center`.
|
||||
- **Pour un cadre de taille fixe, le Size Box va autour de la Border.** Le widget le plus externe
|
||||
gagne toujours.
|
||||
- **Une Border ne peint rien avec `Draw As = Image` sans texture.** Pour un aplat, `Rounded Box`.
|
||||
Contour creux : Tint en alpha 0 + Outline Width.
|
||||
- **« Wrap With… » sur l'unique enfant d'une Border le fait sortir de la Border.** Vérifier
|
||||
l'indentation juste après.
|
||||
- **Un Uniform Grid Panel répartit la largeur qu'on lui donne en parts égales.** En `Fill`, les
|
||||
cases se retrouvent espacées ; il faut `Left` ou `Center` sur son slot pour qu'il prenne sa
|
||||
taille désirée. L'espacement se règle avec `Slot Padding`.
|
||||
- **`Scale Box` + `Stretch = Scale To Fit`** est le seul « keep aspect ratio » d'UMG. Un Size Box
|
||||
avec `Min/Max Aspect Ratio` à 1.0 suffit pour des icônes carrées.
|
||||
- **Le texte placeholder d'un Text Block fausse la mise en page dans la Designer.** Y mettre une
|
||||
valeur représentative — le code l'écrase à l'exécution.
|
||||
- **L'aperçu `Fill Screen`** étire la racine à 1920 × 1080. Basculer sur `Desired Size` pour voir
|
||||
la vraie taille.
|
||||
|
||||
---
|
||||
|
||||
## 11. Dépannage
|
||||
|
||||
| Symptôme | Cause probable |
|
||||
|---|---|
|
||||
| Grille vide, message « aucune recette » | `Recipe Book` non assigné, ou toutes les recettes en `Workbench` |
|
||||
| Grille vide, aucun message | `RecipeSlotClass` non assignée — voir Output Log |
|
||||
| Une case ne réagit pas au clic | racine de `WBP_RecipeSlot` en `Self Hit Test Invisible` |
|
||||
| Cases vides sans icône | le `ResultItem` n'a pas d'icône |
|
||||
| Panneau de détail toujours affiché | `DetailsPanel` non nommé, donc non lié |
|
||||
| Liste d'ingrédients vide | `IngredientRowClass` non assignée |
|
||||
| Le bouton reste grisé avec les ressources | `RequiredStation` inaccessible — pas devant l'établi |
|
||||
| Ligne « Non assignée » dans le menu Touches | `Name` des Player Mappable Key Settings ≠ `ToggleCrafting` |
|
||||
| Le prompt de l'établi n'apparaît jamais | le mesh n'a pas de collision simple |
|
||||
| Objets perdus après un craft | `OnCraftOverflow` sans abonné — cherche l'`Error` dans l'Output Log |
|
||||
|
||||
Les messages du système sont préfixés `UCraftingComponent`, `UCraftingWidget` ou
|
||||
`UInventoryWidget` dans l'Output Log.
|
||||
|
||||
---
|
||||
|
||||
## 12. Reste à faire
|
||||
|
||||
- **Boutons de filtre par poste** — `SetStationFilter()` existe, `ECraftingStation::Count` est la
|
||||
sentinelle « tout ».
|
||||
- **Recettes réelles** — les deux `DA_Craft_Test_*` sont du jetable bâti sur `DA_Branch` et
|
||||
`DA_Rocher`.
|
||||
- **Recettes à découvrir** — `RecipeId` est prévu pour ça ; la liste des identifiants connus ira
|
||||
dans la sauvegarde, jamais dans le DataAsset.
|
||||
- **Retour joueur** — son de fabrication sur `OnCraftSucceeded`, message quand le résultat tombe
|
||||
au sol.
|
||||
- **Postes qui travaillent sans le joueur** — fourneau, séchoir. Un **autre système** : acteurs du
|
||||
monde avec file d'attente et changement de mesh, sans passer par ce menu.
|
||||
Reference in New Issue
Block a user