41d6e6a7f3
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>
420 lines
21 KiB
Markdown
420 lines
21 KiB
Markdown
# Système de cuisson
|
|
|
|
Postes de transformation posés dans le monde : feu de camp aujourd'hui, fourneau et
|
|
séchoir demain. Le joueur dépose un objet dessus, attend, et le récupère transformé.
|
|
|
|
**Fichiers** : `CookingStation.h/.cpp`, plus le bloc *Cooking* / *Fuel* de `ItemDataAsset.h`.
|
|
|
|
---
|
|
|
|
## 1. Le principe
|
|
|
|
Le modèle est celui de Raft, assumé jusqu'au bout : **aucune interface**. Pas de fenêtre,
|
|
pas de barre de progression, pas de compteur. Le joueur tient sa viande crue dans la barre
|
|
rapide, vise un emplacement du grill, appuie sur Interagir, et regarde le mesh changer.
|
|
|
|
Trois conséquences qui structurent tout le reste :
|
|
|
|
- **Le poste ne commente jamais son propre état.** Le prompt d'interaction n'affiche que des
|
|
actions réalisables, exactement comme un objet à ramasser. Ce qui cuit, ce qui manque, ce
|
|
qui brûle se lit sur les flammes, sur le nombre de bûches et sur le mesh de la nourriture.
|
|
- **Le feu s'allume et s'arrête tout seul.** Il n'y a pas de geste « allumer ». Le bois brûle
|
|
s'il y a du travail, et attend sinon.
|
|
- **Aucun `Tick`.** Toute la temporalité passe par des timers.
|
|
|
|
Ce n'est **pas** un système de recettes. La cuisson n'est pas un choix fait dans un menu,
|
|
c'est une propriété de l'objet — voir §2. Le `CLAUDE.md` l'avait tranché dès le craft :
|
|
*« Fourneau et feu de cuisson n'ont volontairement PAS leur entrée dans `ECraftingStation` »*.
|
|
|
|
---
|
|
|
|
## 2. Les données — `UItemDataAsset`
|
|
|
|
### 2.1 La chaîne de transformation
|
|
|
|
Cuire n'est pas une recette mais un **chaînage d'objets**. « Steak cru » pointe vers « Steak
|
|
cuit », qui pointe vers « Steak brûlé ». Chaque maillon est un DataAsset complet, donc le
|
|
changement de visuel sur le feu ne coûte pas une ligne de code : c'est le `WorldMesh` du
|
|
maillon suivant.
|
|
|
|
| Champ | Type | Rôle |
|
|
|---|---|---|
|
|
| `bCanBeCooked` | `bool` | L'objet se transforme sur un feu actif |
|
|
| `CookDuration` | `float` | Secondes de feu **actif** nécessaires |
|
|
| `CookedResult` | `UItemDataAsset*` | Ce qu'il devient |
|
|
| `bCanBurn` | `bool` | L'objet se dégrade s'il reste sur le feu |
|
|
| `BurnDuration` | `float` | Secondes avant dégradation |
|
|
| `BurntResult` | `UItemDataAsset*` | Ce qu'il devient |
|
|
| `bIsFuel` | `bool` | L'objet peut alimenter un feu |
|
|
| `FuelDuration` | `float` | Secondes de combustion par exemplaire |
|
|
|
|
`bCanBeCooked` et `bCanBurn` sont **volontairement indépendants**. Une seule chaîne uniforme
|
|
imposerait le même comportement à tout le monde, alors qu'on veut pouvoir décrire :
|
|
|
|
- un objet qui cuit puis brûle (viande crue → cuite → brûlée) ;
|
|
- un objet qui brûle sans jamais avoir été cuisinable (une planche jetée dans les flammes) ;
|
|
- un plat qui reste indéfiniment au chaud sans se gâcher (`bCanBurn` décoché).
|
|
|
|
Si les deux sont cochés sur le même asset, **la cuisson passe en premier**. C'est le maillon
|
|
*cuit* qui portera `bCanBurn` pour continuer vers le brûlé.
|
|
|
|
### 2.2 Ce qui n'est PAS dans le DataAsset
|
|
|
|
Le temps déjà passé sur le feu. C'est de l'état, il vit sur la station. Le mettre dans le
|
|
DataAsset ferait cuire tous les steaks du monde en même temps — c'est la règle générale du
|
|
projet : *le DataAsset décrit ce qu'un objet **est**, jamais où il en est*.
|
|
|
|
### 2.3 Le visuel : `WorldMeshMaterial`
|
|
|
|
`UItemDataAsset` porte, à côté de `WorldMesh`, un **`WorldMeshMaterial`** optionnel qui
|
|
n'écrase que le premier slot de matériau.
|
|
|
|
Il existe pour la cuisson : dès qu'un pack partage une géométrie entre plusieurs aliments —
|
|
c'est le cas de `StylizedFood_JC`, 73 meshes pour 4 instances de matériau — une chaîne à
|
|
trois étapes demanderait trois meshes distincts là où un seul suffit avec trois teintes.
|
|
|
|
La règle « mesh + matériau optionnel » est centralisée dans **`ApplyWorldVisualsTo()`**, sur
|
|
le DataAsset. Deux appelants aujourd'hui — l'objet au sol (`APickupItem`) et l'objet sur le
|
|
feu — et un troisième viendra avec l'objet en main. Dupliquer la règle garantirait qu'un des
|
|
trois oublie le matériau.
|
|
|
|
### 2.4 Validation
|
|
|
|
`UItemDataAsset::IsDataValid()` refuse à la sauvegarde :
|
|
|
|
- `bCanBeCooked` coché sans `CookedResult` (et l'équivalent pour `bCanBurn`) ;
|
|
- un résultat qui **pointe sur l'asset lui-même**.
|
|
|
|
Ce second cas est le piège vécu : la viande se transforme en elle-même toutes les 20 s, le
|
|
mesh ne change pas, elle reste `bCanBeCooked` donc non récupérable, et le feu ne s'arrête
|
|
jamais puisqu'il croit avoir du travail. En jeu, ça ressemble exactement à un timer cassé.
|
|
|
|
Le runtime s'en protège aussi : `GetNextStage()` ignore un résultat auto-référencé, un
|
|
avertissement part dans l'Output Log au moment du dépôt, et l'objet reste récupérable pour
|
|
ne pas être séquestré sur le grill.
|
|
|
|
---
|
|
|
|
## 3. Le carburant — une pile d'objets
|
|
|
|
Le foyer **n'est pas une jauge de secondes**, c'est un `TArray<FFuelSlotState>` : chaque
|
|
entrée mémorise *quel objet* a été donné et *combien de temps il lui reste*.
|
|
|
|
```
|
|
Fuel[0] ← celle qui brûle. Son compte à rebours est FuelTimer.
|
|
Fuel[1..n] ← attendent leur tour, à leur durée pleine.
|
|
```
|
|
|
|
Quand `Fuel[0]` est consommée, elle est retirée du tableau et tout avance d'un cran. Le foyer
|
|
se lit donc toujours « les N premières bûches sont pleines », ce qui rend l'affichage trivial
|
|
et sans état supplémentaire.
|
|
|
|
**Pourquoi ce modèle plutôt qu'un total en secondes** : une branche à 10 s et une planche à
|
|
3 min occupent la même place. Le joueur voit ce qu'il a donné au feu, pas un pourcentage
|
|
abstrait, et mélanger les combustibles reste lisible. Avec une jauge, il fallait dix branches
|
|
pour voir bouger deux bûches.
|
|
|
|
### 3.1 La capacité
|
|
|
|
`GetFuelCapacity()` renvoie **le nombre de bûches taguées** dans le Blueprint. La capacité
|
|
est une décision de mise en scène ; la régler à deux endroits garantirait qu'ils finissent
|
|
par se contredire. `FallbackFuelCapacity` ne sert qu'aux postes sans bûche visible — un four
|
|
fermé, par exemple.
|
|
|
|
### 3.2 Les bûches visibles
|
|
|
|
Les meshes portant le tag **`FuelLog`** (réglable via `FuelIndicatorTag`) sont recensés une
|
|
fois au `BeginPlay` par `CacheFuelIndicators()`, puis **triés par nom**.
|
|
|
|
- Un tag plutôt qu'une liste de composants : le nombre de bûches change sans recompiler, et
|
|
un renommage ne casse rien. Une `TArray<FComponentReference>` ferait de toute façon la même
|
|
recherche par nom en interne, pour un coût identique — la seule différence serait qu'elle
|
|
casserait en silence au moindre renommage.
|
|
- Le tri est indispensable : `GetComponents` ne promet aucun ordre, et sans lui la bûche qui
|
|
disparaît en premier changerait d'une compilation du Blueprint à l'autre.
|
|
- Tri **alphabétique**, donc `Log_10` passerait avant `Log_2`. Au-delà de neuf bûches,
|
|
nomme-les `Log_01`, `Log_02`…
|
|
- **`Log_1` doit être au fond du tas, `Log_N` au sommet** : la dernière disparaît en premier.
|
|
|
|
`RefreshFuelIndicators()` n'a plus rien à calculer : une bûche visible **est** un objet
|
|
stocké. Il n'y a ni palier, ni timer d'affichage.
|
|
|
|
---
|
|
|
|
## 4. L'acteur `ACookingStation`
|
|
|
|
### 4.1 Composants
|
|
|
|
| Composant | Rôle |
|
|
|---|---|
|
|
| `BaseMesh` | Racine, le foyer. Collision `QueryAndPhysics` — on n'entre pas dans un feu |
|
|
| `InteractionSphere` | Ce que le trace d'interaction touche. Bloque uniquement `Visibility` |
|
|
| `CookSlot_0` … `CookSlot_3` | Un par emplacement de cuisson : porte la nourriture **et** sert à la visée |
|
|
| `FireEffect` | Niagara. `AutoActivate` désactivé — c'est le C++ qui décide |
|
|
| `FireLight` | Halo du foyer, allumé avec la flamme |
|
|
|
|
Les `CookSlot_` sont créés **dans le constructeur**, en nombre fixe (`MaxCookingSlots = 4`).
|
|
C'est le prix à payer pour pouvoir les positionner **à la souris** dans le viewport du
|
|
Blueprint : des composants engendrés à l'exécution ne seraient réglables qu'en tapant des
|
|
coordonnées à l'aveugle. `CookingSlotCount` limite ceux réellement utilisés.
|
|
|
|
**`FireLight` est `Movable`.** Le défaut d'un composant lumière est `Static`, c'est-à-dire
|
|
cuit dans le lightmap : l'allumer au runtime n'aurait aucun effet à l'écran. Ses ombres sont
|
|
désactivées par défaut — une lumière ponctuelle projette sur six faces de cubemap, chaque
|
|
frame, pour chaque objet à portée. C'est de loin le poste le plus cher du système.
|
|
|
|
### 4.2 Réglages exposés
|
|
|
|
**Catégorie `Cooking`**
|
|
|
|
| Propriété | Défaut | Effet |
|
|
|---|---|---|
|
|
| `CookingSlotCount` | 2 | Emplacements de cuisson utilisés (1-4) |
|
|
| `CookingMeshScale` | 1.0 | Échelle du mesh de nourriture posé |
|
|
| `SlotAimHalfAngle` | 25° | Cône de visée autour de chaque emplacement |
|
|
| `CookSpeedMultiplier` | 1.0 | Divise les durées. Un fourneau va plus vite |
|
|
| `bAllowTakingRawItems` | ☐ | Autorise à reprendre ce qui n'a pas fini de cuire |
|
|
| `bShowRemainingTimeInPrompt` | ☐ | Ajoute les secondes au prompt — **outil de réglage** |
|
|
| `bHighlightOnFocus` | ☑ | Custom Depth quand le poste est visé |
|
|
|
|
**Catégorie `Cooking\|Fuel`**
|
|
|
|
| Propriété | Défaut | Effet |
|
|
|---|---|---|
|
|
| `FuelIndicatorTag` | `FuelLog` | Tag des meshes de bûches |
|
|
| `FallbackFuelCapacity` | 5 | Capacité si aucune bûche n'est taguée |
|
|
| `bFuelIndicatorsUseItemMesh` | ☐ | Chaque bûche prend le mesh de son combustible |
|
|
| `StartingFuelItem` / `StartingFuelCount` | — | Bois déjà présent au démarrage |
|
|
|
|
**Catégorie `Cooking\|Sound`** — `LightSound`, `ExtinguishSound`, `PlaceItemSound`. Tous
|
|
facultatifs, un champ vide ne joue rien.
|
|
|
|
### 4.3 API publique
|
|
|
|
```cpp
|
|
bool Light(); // Normalement jamais appelée : UpdateFireState décide
|
|
void Extinguish();
|
|
bool HasPendingWork() const; // Un emplacement a-t-il encore une étape à franchir
|
|
bool IsLit() const;
|
|
int32 GetFuelCapacity() const;
|
|
int32 GetFuelCount() const;
|
|
float GetFuelRemaining() const; // Somme sur tout le foyer
|
|
float GetSlotTimeRemaining(int32) const;
|
|
|
|
FOnCookingStationChanged OnStationChanged; // Dépôt, retrait, transformation, allumage
|
|
```
|
|
|
|
---
|
|
|
|
## 5. L'interaction
|
|
|
|
### 5.1 Viser un emplacement
|
|
|
|
`ResolveAimedSlot()` prend le point de vue réel de l'interactor via
|
|
`GetActorEyesViewPoint()` — sur un pawn possédé, c'est le point de vue du controller, donc
|
|
exactement ce que le joueur voit. La station ne dépend ainsi pas d'`AFpsPlayer`.
|
|
|
|
Le choix se fait par **produit scalaire**, pas par distance à l'écran : c'est l'écart
|
|
*angulaire* au centre du viseur qui compte, et il ne demande ni projection ni accès au
|
|
viewport. Sous le cône `SlotAimHalfAngle`, aucun emplacement n'est visé et le joueur
|
|
s'adresse au foyer lui-même — c'est ce qui lui permet d'ajouter du bois sans devoir éviter le
|
|
grill du regard.
|
|
|
|
Aucune collision par emplacement : la transform du `CookSlot_` suffit. Zéro requête physique
|
|
supplémentaire.
|
|
|
|
### 5.2 Priorité des actions
|
|
|
|
`ResolveAction()` calcule **une seule fois** ce que ferait un appui, et le résultat sert à la
|
|
fois au texte du prompt et à l'exécution. Deux cascades de `if` séparées finiraient par
|
|
diverger, et le joueur lirait une chose en en obtenant une autre.
|
|
|
|
| # | Condition | Action | Prompt |
|
|
|---|---|---|---|
|
|
| 1 | Emplacement visé, occupé, récupérable | `TakeItem` | `Pick up {objet}` |
|
|
| 2 | Emplacement visé libre + objet cuisinable en main | `PlaceItem` | `Cook {objet}` |
|
|
| 3 | Du bois dans l'inventaire + place au foyer | `AddFuel` | `Add {bois} to the fire` |
|
|
| — | Sinon | `None` | *(vide)* |
|
|
|
|
Récupérer passe avant tout : sans ça, impossible de sortir sa viande en ayant du bois en main.
|
|
|
|
Le bois n'a **pas besoin d'être en main** : `FindFuelSource()` prend le slot actif s'il
|
|
contient du carburant, sinon le premier slot de l'inventaire qui en contient. La main sert à
|
|
*choisir* le combustible, pas à autoriser le geste — et le prompt nomme toujours ce qui va
|
|
partir, pour qu'on ne brûle jamais ses planches par surprise.
|
|
|
|
**Un exemplaire par appui.** Donner du bois est irréversible ; vider vingt branches d'un coup
|
|
sur un feu qui n'en demandait qu'une ne se rattrape pas.
|
|
|
|
### 5.3 Ce qui peut être repris
|
|
|
|
`CanTakeFromSlot()` : on ne retire pas ce qui est **en train de cuire**.
|
|
|
|
Le critère est porté par l'objet lui-même, sans état à maintenir : **tant qu'il sait encore
|
|
cuire (`bCanBeCooked`), il est cru, donc engagé.** Dès qu'il ne sait plus que brûler, c'est
|
|
qu'il a fini sa cuisson et qu'on peut le sauver.
|
|
|
|
C'est plus subtil qu'un booléen « a déjà transformé » : avec celui-ci, reposer une viande
|
|
cuite sur le feu l'aurait re-verrouillée jusqu'à ce qu'elle carbonise.
|
|
|
|
Un second filet libère les objets dont la chaîne est cassée — sans lui, une donnée mal
|
|
branchée séquestrerait l'objet sur le grill pour toujours.
|
|
|
|
`bAllowTakingRawItems` permet de revenir sur cette règle sans recompiler.
|
|
|
|
---
|
|
|
|
## 6. Le cycle du feu
|
|
|
|
### 6.1 `UpdateFireState()`, point de décision unique
|
|
|
|
```
|
|
Le feu brûle ⟺ il y a du bois ET il y a du travail
|
|
```
|
|
|
|
Toutes les actions qui changent le contenu du foyer ou du grill y passent : dépôt, retrait,
|
|
ajout de bois, fin d'étape. Laisser chaque action décider aurait fini par oublier un cas —
|
|
typiquement le dernier objet retiré, laissant le feu se consumer dans le vide.
|
|
|
|
Il n'y a donc **pas d'action « allumer »**. Le joueur met du bois, pose sa viande, ça part.
|
|
|
|
### 6.2 « Du travail », précisément
|
|
|
|
`HasPendingWork()` compte comme travail **tout objet ayant une étape devant lui, y compris la
|
|
carbonisation**.
|
|
|
|
C'est un arbitrage délibéré : pris au pied de la lettre, « le feu s'arrête quand tout est
|
|
fini de cuire » l'éteindrait à l'instant précis où la viande devient cuite, et la
|
|
sur-cuisson ne se produirait jamais. Oublier sa viande doit rester puni. Le feu ne s'arrête
|
|
donc qu'une fois la viande brûlée — bout de chaîne.
|
|
|
|
*Si ce comportement doit changer un jour, c'est la seule condition de `HasPendingWork()`.*
|
|
|
|
### 6.3 Les timers
|
|
|
|
| Timer | Porté par | Ce qu'il fait |
|
|
|---|---|---|
|
|
| `FCookingSlotState::Timer` | Chaque emplacement | Fin d'étape → `HandleSlotFinished` |
|
|
| `FuelTimer` | La station | Fin de `Fuel[0]` → `HandleFuelConsumed` |
|
|
|
|
**Aucun `Tick`, et aucun timer périodique.** Éteindre le feu met les timers de cuisson en
|
|
**pause** : la cuisson reprend exactement où elle s'était arrêtée, sans qu'une seule ligne ne
|
|
compte le temps à la main.
|
|
|
|
Poser un objet sur un feu éteint est autorisé : le compte à rebours existe, il est
|
|
immédiatement mis en pause et attend qu'on alimente le foyer.
|
|
|
|
Le carburant, lui, ne se met pas en pause mais se **resynchronise** : `SyncFuelFromTimer()`
|
|
recopie le restant du timer dans `Fuel[0]` avant toute modification. Feu allumé, c'est le
|
|
timer qui fait autorité ; feu éteint, c'est `RemainingSeconds`.
|
|
|
|
`EndPlay` nettoie tous les timers. Ils portent un délégué vers `this`, et les laisser tourner
|
|
après la destruction de l'acteur ne se voit qu'au changement de niveau, sous la forme d'un
|
|
crash sans pile lisible.
|
|
|
|
### 6.4 Débordement
|
|
|
|
`TakeItemToInventory()` tente l'ajout **avant** de vider l'emplacement. Inventaire plein : la
|
|
nourriture reste sur le feu — et continue donc de cuire, ce qui est la bonne sanction plutôt
|
|
que de la faire disparaître.
|
|
|
|
---
|
|
|
|
## 7. Guide éditeur
|
|
|
|
### 7.1 La chaîne d'objets
|
|
|
|
Crée-les **du dernier vers le premier**, chacun référençant le suivant.
|
|
|
|
1. Content Browser → `Content/Game/Data/Item` → clic droit sur un DataAsset existant →
|
|
**Duplicate** (il hérite du mesh et de l'icône, c'est le plus rapide).
|
|
2. **`DA_BurntMeat`** : `Item Id` = `Meat_Burnt`, `Display Name` = `Burnt Meat`,
|
|
`Hunger Restore` = 2, `Health Restore` = -5. **Toute la section Cooking décochée** — c'est
|
|
ce qui arrête la chaîne.
|
|
3. **`DA_CookedMeat`** : `Hunger Restore` = 40, `Health Restore` = 5. `Can Be Cooked`
|
|
**décoché**, `Can Burn` **coché**, `Burn Duration` = 45, `Burnt Result` → `DA_BurntMeat`.
|
|
4. **`DA_RawMeat`** : `Hunger Restore` = 8, `Health Restore` = **-10** (manger cru punit).
|
|
`Can Be Cooked` **coché**, `Cook Duration` = 20, `Cooked Result` → `DA_CookedMeat`.
|
|
5. Donne à chaque étape un visuel distinct : soit un `SM_Food_XX` différent, soit **le même
|
|
mesh** avec un `World Mesh Material` différent (`MI_StylizedFood_01` / `02` / `04`).
|
|
6. Le combustible : sur `DA_Branch`, coche **`Is Fuel`** et règle `Fuel Duration`.
|
|
|
|
### 7.2 Le Blueprint
|
|
|
|
1. Clic droit → **Blueprint Class** → **All Classes** → `CookingStation` → nomme-le
|
|
`BP_Campfire`.
|
|
2. `BaseMesh` → le mesh du foyer. Ajoute des Static Mesh enfants pour composer les pierres.
|
|
3. `FireEffect` → **Niagara System Asset** = `NS_Stylized_Fire_01_Infinite`. Prends bien la
|
|
variante `_Infinite`, les autres s'arrêtent seules. **Laisse `Auto Activate` décoché.**
|
|
4. `FireLight` → ajuste intensité, couleur, rayon. Coche `Cast Shadows` **uniquement** sur le
|
|
feu principal du campement.
|
|
5. **Les bûches** : ajoute N Static Mesh (`Log_1` … `Log_5`), positionne-les en tas, puis sur
|
|
chacun → Details → **`Component Tags`** → `+` → **`FuelLog`**.
|
|
`Log_1` au fond, `Log_N` au sommet.
|
|
6. **Les emplacements de cuisson** : sélectionne `CookSlot_0`, donne-lui un `Cube` de repérage
|
|
(Scale 0.15) pour le voir, place-le au-dessus des flammes. Le jeu efface ce mesh au
|
|
démarrage. Répète selon `CookingSlotCount`.
|
|
7. Racine → `Cooking` : ajuste `Cooking Slot Count` et `Cooking Mesh Scale`.
|
|
|
|
### 7.3 Vérifier que ça marche
|
|
|
|
1. Le foyer démarre **vide** : pas de bûche visible, pas de flamme. C'est normal.
|
|
2. Branche en main, vise le foyer → `[E] Add Branch to the fire` → **une bûche apparaît**.
|
|
3. Répète : le tas se remplit, puis l'action disparaît une fois la capacité atteinte.
|
|
4. Viande crue en main, vise le grill → `[E] Cook Raw Meat` → **les flammes partent seules**.
|
|
5. Pendant la cuisson : **aucun prompt**, impossible de reprendre la viande.
|
|
6. À la fin : le mesh change et le prompt devient `[E] Pick up Cooked Meat`.
|
|
7. Laisse traîner : le mesh repasse au brûlé, et le feu s'arrête de lui-même.
|
|
8. Retire tout le bois en cours de cuisson : les flammes s'éteignent et la cuisson **se fige**.
|
|
Remets du bois : elle reprend où elle en était, pas de zéro.
|
|
|
|
---
|
|
|
|
## 8. Dépannage
|
|
|
|
| Symptôme | Cause |
|
|
|---|---|
|
|
| `Cook X` n'apparaît jamais | Les `CookSlot_` sont restés à l'origine, donc **dans** le foyer. Repositionne-les, ou monte `Slot Aim Half Angle` |
|
|
| L'objet ne change jamais d'état, jamais récupérable | Chaîne auto-référencée : `Cooked Result` pointe sur l'asset lui-même. **L'Output Log le dit** |
|
|
| Rien ne se passe quand on pose | Vérifie que `bCanBeCooked` **et** `CookedResult` sont renseignés |
|
|
| Aucune bûche n'apparaît | Tag `FuelLog` absent, ou mal orthographié |
|
|
| Les bûches disparaissent dans le désordre | Ordre alphabétique des noms de composants |
|
|
| Le feu ne démarre pas malgré du bois | Rien à transformer : c'est le comportement voulu |
|
|
| Pas de flamme mais le feu tourne | `Niagara System Asset` vide, ou variante non-`_Infinite` |
|
|
| La lumière ne s'allume pas | `Mobility` du `FireLight` repassée à `Static` |
|
|
| Le mesh de repérage reste visible | Il est effacé au `BeginPlay` — tu regardes le viewport de l'éditeur |
|
|
| Nourriture énorme ou minuscule | `Cooking Mesh Scale` |
|
|
|
|
---
|
|
|
|
## 9. Étendre
|
|
|
|
**Un fourneau** est déjà possible sans une ligne de C++ : même classe, `CookSpeedMultiplier`
|
|
à 2, d'autres meshes, plus de `CookSlot_`. Un **séchoir** de même, avec des durées longues et
|
|
`bCanBurn` décoché sur ses résultats.
|
|
|
|
Ce qui demanderait du code :
|
|
|
|
- **Une file d'attente sans le joueur** (déposer dix minerais, revenir plus tard) : le modèle
|
|
actuel est un emplacement = un exemplaire.
|
|
- **Un allume-feu obligatoire** (silex, briquet) : `Light()` est déjà publique, il ne manque
|
|
qu'une condition et une action d'interaction.
|
|
- **La sauvegarde** : `Slots` et `Fuel` sont `Transient`. Le jour où la sauvegarde arrivera,
|
|
ce sont ces deux tableaux plus les temps restants qu'il faudra sérialiser.
|
|
- **Séparer « le feu éclaire » de « le feu consomme »** : aujourd'hui les deux sont liés, donc
|
|
un feu de camp n'éclaire pas quand il n'a rien à cuire. Invisible tant qu'il n'y a pas de
|
|
cycle jour/nuit, gênant le jour où il y en aura un. Une dizaine de lignes.
|
|
|
|
---
|
|
|
|
## 10. Ce que ce système a changé ailleurs
|
|
|
|
- **`IInteractable::GetInteractionPrompt` prend désormais l'`Interactor`.** Sans lui, la
|
|
station ne peut pas savoir quel emplacement le joueur regarde. Resservira pour les coffres.
|
|
- **`UInteractionComponent` met le prompt en cache et diffuse `OnPromptChanged`.** Le texte
|
|
peut changer sans que la cible change — en balayant d'un emplacement à l'autre sur le même
|
|
acteur. S'abonner à `OnFocusChanged` seul laissait ces cas figés à l'écran. Le composant ne
|
|
reconstruit le texte qu'une fois par trace, et ne réveille l'UI que sur un vrai changement.
|
|
- **`UItemDataAsset::ApplyWorldVisualsTo()`** et le champ `WorldMeshMaterial` (§2.3).
|
|
- Les `FText` d'`APickupItem` sont passés en anglais, conformément à la convention du projet.
|