453 lines
23 KiB
Markdown
453 lines
23 KiB
Markdown
# Coffres
|
|
|
|
Contenants posés dans le monde : on les ouvre, on y range, on y reprend. Le couvercle se lève
|
|
pour tout le monde, le contenu est partagé et accessible à plusieurs en même temps.
|
|
|
|
**Fichiers** : `StorageContainer.h/.cpp`, plus le panneau de coffre d'`InventoryScreenWidget`,
|
|
l'API de transfert d'`InventoryComponent` et le bloc `UI|Storage` de `FpsPlayerController`.
|
|
|
|
---
|
|
|
|
## 1. Le principe
|
|
|
|
**Un coffre n'a aucun modèle de données à lui.** Son contenu est un `UInventoryComponent`,
|
|
exactement le même que celui du joueur, simplement configuré sans barre rapide. Tout le reste
|
|
en découle :
|
|
|
|
- le glisser-déposer entre le sac et le coffre ne convertit rien — ce sont les mêmes cases,
|
|
le même `FInventorySlot` ;
|
|
- la durabilité d'une hache traverse le coffre **sans une ligne de code**, puisque l'usure
|
|
vit sur le slot ;
|
|
- il n'y a pas de « format coffre » à faire évoluer le jour où l'inventaire change.
|
|
|
|
**Le coffre ne décide de rien.** Il se contente d'appeler `OpenStorage(this)` sur le
|
|
controller. C'est ce dernier qui referme le couvercle — par Échap, par la mort du joueur, ou
|
|
parce qu'il s'est éloigné. Un coffre qui déciderait seul finirait ouvert dans la moitié des
|
|
cas, et il n'a de toute façon aucun moyen de savoir que l'écran s'est fermé.
|
|
|
|
**Ce n'est pas un écran à part**, c'est un panneau de plus sur `WBP_InventoryScreen` — voir §5.
|
|
|
|
---
|
|
|
|
## 2. L'acteur `AStorageContainer`
|
|
|
|
### 2.1 Composants
|
|
|
|
| Composant | Rôle |
|
|
|---|---|
|
|
| `BaseMesh` | La caisse : `SM_Chest_Bottom`. Racine, **purement visuelle**, collision désactivée |
|
|
| `InteractionBox` | L'**unique** collision du coffre. Fille de la caisse |
|
|
| `LidMesh` | Le couvercle : `SM_Chest_Top`. Enfant direct de la caisse, collision désactivée |
|
|
| `Storage` | Le contenu. Un `UInventoryComponent` ordinaire |
|
|
|
|
### 2.2 Réglages exposés
|
|
|
|
**Catégorie `Storage`**
|
|
|
|
| Propriété | Défaut | Effet |
|
|
|---|---|---|
|
|
| `SlotCount` | 20 | Nombre de cases. **Réglable par instance** : le même Blueprint donne un petit coffre de 12 et un grand de 30 |
|
|
| `ColumnCount` | 6 | Cases par ligne dans l'écran. Purement visuel |
|
|
| `DisplayName` | `Chest` | Titre affiché au-dessus de la grille |
|
|
| `InteractionPrompt` | `Ouvrir le coffre` | Texte affiché quand le joueur vise |
|
|
| `StartingItems` | — | Contenu versé **une seule fois** au `BeginPlay` |
|
|
|
|
`StartingItems` est de la donnée de level design, pas du gameplay : un coffre de départ, une
|
|
cache à trouver. Ce qui ne rentre pas part dans l'Output Log en warning plutôt que de
|
|
disparaître en silence.
|
|
|
|
**Catégorie `Storage|Collision`**
|
|
|
|
| Propriété | Défaut | Effet |
|
|
|---|---|---|
|
|
| `bAutoFitInteractionBox` | ☑ | La boîte épouse seule les deux meshes, couvercle fermé |
|
|
| `InteractionBoxPadding` | 0 cm | Marge **additive** autour du volume trouvé |
|
|
|
|
**Catégorie `Storage|Lid`**
|
|
|
|
| Propriété | Défaut | Effet |
|
|
|---|---|---|
|
|
| `OpenRotation` | `(-100, 0, 0)` | Rotation **relative** du couvercle ouvert. Le repos vaut toujours zéro |
|
|
| `LidDuration` | 0.35 s | Durée de l'ouverture |
|
|
|
|
**Catégorie `Storage|Sound`** — `OpenSound`, `CloseSound`. Facultatifs : un champ vide ne joue
|
|
rien.
|
|
|
|
### 2.3 API publique
|
|
|
|
```cpp
|
|
void SetOpen(bool bInOpen); // SERVEUR uniquement. Lève ou rabat le couvercle
|
|
bool IsOpen() const;
|
|
UInventoryComponent* GetStorage() const;
|
|
FText GetDisplayName() const;
|
|
int32 GetColumnCount() const;
|
|
```
|
|
|
|
`SetOpen` est appelée par le **controller**, pas par le coffre : lui seul sait quand l'écran
|
|
se referme.
|
|
|
|
---
|
|
|
|
## 3. Le couvercle
|
|
|
|
### 3.1 La rotation se fait sur le pivot du mesh
|
|
|
|
**C'est `LidMesh` lui-même qu'on tourne**, et un `UStaticMeshComponent` pivote toujours autour
|
|
de l'origine de son mesh. La charnière est donc celle que l'artiste a donnée à `SM_Chest_Top` —
|
|
rien à placer, rien à régler.
|
|
|
|
> Un `SceneComponent` vide `LidPivot` a existé entre la caisse et le couvercle, pour découpler
|
|
> la charnière du pivot du pack. Il a été **retiré** : il fallait le placer à la main dans
|
|
> chaque Blueprint, et laissé à `(0,0,0)` il faisait tourner le couvercle autour du centre de
|
|
> la caisse — c'est-à-dire à travers elle. C'était le symptôme, pas une configuration exotique.
|
|
|
|
Corollaire assumé : si un pack livre un couvercle dont le pivot est au centre, **on corrige
|
|
l'asset, pas le code** — Modeling Mode > XForm > **Edit Pivot** sur le Static Mesh, pivot posé
|
|
sur l'arête arrière, Accept. C'est payé une fois par pack de coffre.
|
|
|
|
`OpenRotation` est un `FRotator` complet et pas un simple angle : selon l'orientation dans
|
|
laquelle le mesh a été exporté, la charnière peut tomber sur le **pitch**, le **yaw** ou le
|
|
**roll**. On règle les trois à l'œil dans le viewport plutôt que d'aller comprendre quel axe
|
|
local du mesh pointe où.
|
|
|
|
### 3.2 L'animation
|
|
|
|
Un `Tick`, mais **éteint au repos** (`bStartWithTickEnabled = false`). `ApplyOpenState()` le
|
|
rallume, et le tick s'éteint lui-même en fin de course.
|
|
|
|
```cpp
|
|
LidAlpha = FMath::FInterpConstantTo(LidAlpha, Target, DeltaTime, 1.f / LidDuration);
|
|
const float Eased = FMath::SmoothStep(0.f, 1.f, LidAlpha);
|
|
LidMesh->SetRelativeRotation(OpenRotation * Eased);
|
|
```
|
|
|
|
- **`SmoothStep`** : un couvercle à vitesse constante démarre et s'arrête d'un coup, ce qui se
|
|
lit comme une saccade sur un objet lourd.
|
|
- **L'alpha repart de sa valeur courante**, donc refermer un coffre à moitié ouvert ne saute
|
|
pas.
|
|
- **La valeur exacte est forcée avant de se rendormir**. Sans ça le couvercle resterait à un
|
|
poil de sa position finale pour le reste de la partie.
|
|
- **`BeginPlay` repose l'alpha à zéro** quelle que soit la rotation laissée dans le Blueprint :
|
|
le repos *est* la référence de l'animation. Une rotation authorée sur `LidMesh` serait donc
|
|
perdue au lancement.
|
|
|
|
---
|
|
|
|
## 4. La collision — une seule boîte
|
|
|
|
**Les deux 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 tourne** :
|
|
|
|
- compter sur les collisions des meshes imposerait que le pack en fournisse deux correctes ;
|
|
- et laisserait surtout un **trou dans la surface visable** dès que le couvercle se lève —
|
|
viser le haut d'un coffre ouvert ne toucherait plus rien.
|
|
|
|
La boîte est fille de la **caisse**, jamais du couvercle : le coffre se vise exactement au même
|
|
endroit ouvert et fermé. Sinon le prompt disparaîtrait au moment précis où l'on s'en sert.
|
|
|
|
Son profil est `BlockAll`, posé explicitement : elle a deux rôles indissociables, empêcher le
|
|
joueur de traverser le coffre **et** bloquer le canal `Visibility` pour que le trace
|
|
d'interaction la trouve. Un profil qui laisserait passer `Visibility` rendrait le coffre
|
|
inutilisable sans qu'aucune erreur ne le signale.
|
|
|
|
### `FitInteractionBox()` — deux pièges
|
|
|
|
Appelée depuis `OnConstruction`, donc elle tourne **aussi dans l'éditeur** : changer de variante
|
|
de coffre recale la boîte à l'instant, sans lancer le jeu.
|
|
|
|
1. **Lire les bounds de l'ASSET (`UStaticMesh::GetBoundingBox()`), pas `Mesh->Bounds`** — ces
|
|
dernières sont déjà multipliées par l'échelle, que `SetBoxExtent` remultiplie.
|
|
2. **Composer avec la transform RELATIVE à la racine**, pas la transform monde : la boîte doit
|
|
rester juste où que l'acteur soit posé dans le niveau.
|
|
|
|
Même paire de pièges que dans `APickupItem::FitInteractionBox`.
|
|
|
|
---
|
|
|
|
## 5. L'écran
|
|
|
|
### 5.1 Un panneau, pas un écran
|
|
|
|
Le contenu du coffre s'affiche **à côté** de la grille du joueur, sur `WBP_InventoryScreen`,
|
|
comme **frère du WidgetSwitcher** et `Collapsed` par défaut.
|
|
|
|
La raison est du ressenti pur : **le sac du joueur ne bouge pas d'un pixel** entre « j'ouvre
|
|
mon sac » et « j'ouvre un coffre ». Un écran distinct le ferait sauter à chaque fois.
|
|
|
|
Ce n'est **pas non plus un troisième onglet** : les deux grilles doivent être visibles
|
|
*ensemble*, ce qu'un WidgetSwitcher interdit par construction.
|
|
|
|
### 5.2 C'est une deuxième instance du même `WBP_Inventory`
|
|
|
|
Elle ne peut pas vivre *dans* `WBP_Inventory` — un Widget Blueprint qui se contient lui-même est
|
|
une référence circulaire, et UMG refuse de le compiler. D'où deux conséquences directes :
|
|
|
|
- **`bAutoBindToPawn` doit être DÉCOCHÉ** sur la grille de coffre. Sans ça elle s'accroche au
|
|
pawn dans son `NativeConstruct` — qui s'exécute avant que l'écran ait eu le temps de lui
|
|
donner le conteneur — et le sac du joueur s'affiche des **deux** côtés.
|
|
- **Le panneau de détail est coupé par le code, jamais par une case à cocher**
|
|
(`SetItemDetailsEnabled`). Un réglage par instance serait un bug qui attend qu'on oublie de
|
|
le décocher sur l'une des deux.
|
|
|
|
### 5.3 Ce que l'écran fait à l'ouverture
|
|
|
|
| | |
|
|
|---|---|
|
|
| `StorageGrid->SetColumnCount` | La forme vient du **coffre posé dans le niveau**, pas du Widget Blueprint |
|
|
| `StorageGrid->BindToInventory` | Branche la grille sur le contenu |
|
|
| Cibles de clic droit croisées | Sac → coffre et coffre → sac (§6.2) |
|
|
| `InventoryPage->SetItemDetailsEnabled(false)` | Le sac perd son panneau de détail : trois colonnes seraient illisibles |
|
|
| `ShowInventoryTab()` | Un coffre ouvert sous l'onglet Fabrication n'aurait aucun sens |
|
|
|
|
Et **l'onglet Fabrication est grisé** tant qu'un coffre est ouvert : le panneau étant frère du
|
|
WidgetSwitcher, il resterait affiché à côté de la grille de recettes. La touche de craft, elle,
|
|
**rend le coffre** plutôt que d'ignorer l'appui — la touche exprime une intention claire.
|
|
|
|
`HideStorage()` est appelée dès `NativeConstruct` : l'état par défaut est **reposé** à chaque
|
|
construction plutôt qu'hérité de la Designer ou de la session précédente. Elle coupe aussi les
|
|
transferts rapides **avant** de débrancher — une case qui garderait le coffre en mémoire y
|
|
enverrait encore des objets au prochain clic droit, panneau fermé.
|
|
|
|
Tous les éléments de coffre sont en **`BindWidgetOptional`** : l'écran doit rester valide dans
|
|
un projet où le panneau n'a pas encore été construit, sinon ajouter les coffres casserait la
|
|
compilation de `WBP_InventoryScreen` — et donc l'inventaire entier.
|
|
|
|
---
|
|
|
|
## 6. Les transferts
|
|
|
|
### 6.1 Un seul déplacement pour tout
|
|
|
|
```cpp
|
|
static bool TransferSlot(UInventoryComponent* From, int32 FromIndex,
|
|
UInventoryComponent* To, int32 ToIndex, int32 Quantity);
|
|
```
|
|
|
|
**Statique**, parce qu'elle n'appartient ni à la source ni à la destination : elle arbitre entre
|
|
les deux. `MoveItem()` et `MoveItemQuantity()` ne sont plus que des appels avec `From == To`.
|
|
|
|
Elle diffuse `OnInventoryChanged` **des deux côtés**. Oublier la seconde diffusion laisse la
|
|
grille d'en face afficher un état périmé jusqu'au prochain événement, ce qui se lit comme un
|
|
objet qui disparaît.
|
|
|
|
### 6.2 Clic droit = envoyer en face
|
|
|
|
`QuickTransferSlot()` passe par `AddItem()`, donc **exactement comme un ramassage** : piles
|
|
entamées d'abord, puis barre rapide, puis grille. Ce qui ne rentre pas reste sur place.
|
|
|
|
La destination est **poussée** par l'écran (`SetQuickTransferTarget`), jamais devinée. Hors de
|
|
l'écran de coffre il n'existe aucun « autre côté », et deviner ferait disparaître des objets.
|
|
La **barre rapide** reçoit elle aussi le coffre, pour que le clic droit y marche par-dessus
|
|
l'écran.
|
|
|
|
### 6.3 « Tout ranger » / « Tout prendre »
|
|
|
|
```cpp
|
|
int32 TransferAllTo(UInventoryComponent* To, int32 FirstIndex = 0);
|
|
```
|
|
|
|
- **Tout ranger** part de `GetBackpackStartIndex()` : la barre rapide porte les outils qu'on
|
|
vient d'utiliser, les ranger d'office serait une punition déguisée en confort.
|
|
- **Tout prendre** part de l'index 0 — un coffre n'a pas de barre rapide. Ce qui ne rentre pas
|
|
dans le sac reste dans le coffre.
|
|
|
|
---
|
|
|
|
## 7. Le cycle d'ouverture
|
|
|
|
Le controller est le seul maître. `ReleaseActiveStorage()` est le **point de sortie unique** :
|
|
toute fermeture d'écran y passe, quelle qu'en soit la cause.
|
|
|
|
| Déclencheur | Chemin |
|
|
|---|---|
|
|
| Interagir avec le coffre | `Interact_Implementation` → `OpenStorage()` |
|
|
| Échap | `HandlePauseInput()` — le coffre passe avant l'inventaire |
|
|
| Tab | `SetInventoryScreenOpen(false)` |
|
|
| Touche de craft | Rend le coffre, puis affiche l'onglet Fabrication |
|
|
| Mort du joueur | Fermeture de l'écran |
|
|
| Éloignement | `CheckStorageDistance()`, timer à 0,25 s |
|
|
| Coffre détruit | Idem, la `TWeakObjectPtr` est nulle |
|
|
| Ouvrir un autre coffre | `OpenStorage()` rend le précédent avant |
|
|
|
|
**`MaxStorageDistance` (400 cm)** : sans elle on viderait un coffre depuis l'autre bout de la
|
|
carte, rien n'obligeant le joueur à rester devant une fois l'écran ouvert. Surveillée par un
|
|
**timer**, jamais un Tick, et **seulement pendant que l'écran est ouvert** — un timer permanent
|
|
pour une condition vraie deux minutes par partie serait du gaspillage. `DistSquared`, pour ne
|
|
pas prendre une racine carrée quatre fois par seconde.
|
|
|
|
Le timer est aussi nettoyé à la destruction du HUD : il rappellerait une méthode d'un controller
|
|
en train de disparaître.
|
|
|
|
---
|
|
|
|
## 8. Réseau
|
|
|
|
**Autorité serveur sur tout**, comme pour le reste du monde.
|
|
|
|
| Élément | Réplication |
|
|
|---|---|
|
|
| `bOpen` | `ReplicatedUsing = OnRep_Open` — voir le couvercle d'un coffre se lever est le seul signe qu'un coéquipier fouille dedans |
|
|
| `Slots` du conteneur | Répliqué à **tout le monde** par `UInventoryComponent` |
|
|
| L'animation, le son | **Rien** : dérivés de `bOpen` des deux côtés par `ApplyOpenState()` |
|
|
|
|
### Les quatre pièges
|
|
|
|
1. **`Interact_Implementation` s'exécute sur le SERVEUR.** `PlayerController->OpenStorage(...)`
|
|
y créerait un widget dans le vide. D'où `Client_OpenStorage` : le serveur lève le couvercle
|
|
(état du monde) et **renvoie l'ordre d'écran** au client. C'est le piège le plus
|
|
contre-intuitif du portage, parce que le code *semble* correct et marche parfaitement chez
|
|
l'hôte.
|
|
2. **`SetOpen` ne fait rien sans autorité.** Chez un client, refermer l'écran doit passer par
|
|
`Server_ReleaseStorage` — sinon le coffre resterait ouvert pour les trois autres joueurs
|
|
alors que l'écran est fermé ici.
|
|
3. **`FindNetProxy` est le passage obligé des transferts.** Un `UInventoryComponent` de coffre
|
|
n'appartient à aucune connexion : une RPC émise depuis lui est **silencieusement jetée**. On
|
|
route donc toujours par l'inventaire du pawn local, et la source comme la destination sont
|
|
passées **en paramètres** — jamais déduites du récepteur, sinon « Tout prendre » viderait le
|
|
sac du joueur dans lui-même.
|
|
4. **`Slots` est répliqué à tout le monde, pas au seul propriétaire.** Ce n'est pas un
|
|
raccourci : les conditions de `GetLifetimeReplicatedProps` sont mises en cache **par classe**,
|
|
pas par instance. Un `COND_OwnerOnly` frapperait donc aussi le composant du coffre — or un
|
|
coffre n'appartient à personne, et sa grille n'arriverait chez aucun joueur.
|
|
|
|
**Accès simultané, pas de verrou.** Deux joueurs peuvent fouiller le même coffre : `TransferSlot`
|
|
diffuse déjà `OnInventoryChanged` des deux côtés, et tous les widgets y sont abonnés. L'UI se
|
|
recâble donc toute seule à chaque `OnRep_Slots()`.
|
|
|
|
**Pas de prédiction.** Le client envoie sa RPC et attend la réplication. Quelques dizaines de
|
|
millisecondes sur un relais Steam, invisibles pour un glisser-déposer, alors qu'une prédiction
|
|
fausse donnerait une pile qui saute.
|
|
|
|
---
|
|
|
|
## 9. Guide éditeur
|
|
|
|
### 9.1 Le Blueprint du coffre
|
|
|
|
1. Content Browser → `Content/Game/Building/Storage/` → clic droit → **Blueprint Class** →
|
|
**All Classes** → `StorageContainer` → nomme-le **`BP_Chest_Wooden`**.
|
|
2. Ouvre-le. Sélectionne **`BaseMesh`** → Details → **Static Mesh** = `SM_Chest_Bottom`.
|
|
3. Sélectionne **`LidMesh`** → **Static Mesh** = `SM_Chest_Top`.
|
|
4. Toujours sur `LidMesh`, Details → **Transform** :
|
|
- **Location** : les deux meshes viennent du même export, donc **`0, 0, 0`** les repose l'un
|
|
sur l'autre. Ajuste seulement s'il flotte ou s'enfonce.
|
|
- **Rotation** : **`0, 0, 0`** obligatoirement. `BeginPlay` écrase cette valeur — une
|
|
rotation laissée ici serait perdue au lancement.
|
|
5. Sélectionne l'acteur racine (`Self`) → Details → **Storage | Lid** → **`Open Rotation`**.
|
|
Le défaut est `Pitch = -100`. Tape des valeurs et regarde le viewport : si le couvercle
|
|
bascule sur le côté, essaie le **Roll**, puis le **Yaw**.
|
|
6. **`Storage`** → règle `Slot Count` et `Column Count`. Mets un `Display Name` (« Coffre en
|
|
bois ») et un `Interaction Prompt`.
|
|
7. **`Storage | Sound`** → `Open Sound` / `Close Sound` si tu en as.
|
|
8. Laisse **`Auto Fit Interaction Box`** coché : la boîte se cale seule sur les deux meshes.
|
|
Vérifie-la avec **Show > Collision** dans le viewport.
|
|
9. **Compile** et **Save**.
|
|
|
|
Pose-le dans le niveau. `Slot Count`, `Column Count`, `Display Name` et `Starting Items` se
|
|
règlent **par instance** dans le World Outliner : un seul Blueprint sert tous les coffres.
|
|
|
|
### 9.2 Le panneau de coffre dans `WBP_InventoryScreen`
|
|
|
|
À ne faire qu'une fois. Tout est en `BindWidgetOptional`, donc l'écran compile même à mi-chemin.
|
|
|
|
1. Ouvre **`WBP_InventoryScreen`**.
|
|
2. Dans la Designer, à côté du `TabSwitcher` — **frère, pas enfant** — pose un **Horizontal Box**
|
|
ou une **Border**, et renomme-le **`StoragePanel`**. Mets sa **Visibility** à `Collapsed`
|
|
(le code la repose de toute façon).
|
|
3. Dedans, un **Vertical Box** contenant :
|
|
- un **Text Block** nommé **`StorageTitle`** ;
|
|
- ton **`WBP_Inventory`** (Palette → User Created), renommé **`StorageGrid`** ;
|
|
- un **Horizontal Box** avec deux **Button** nommés **`StoreAllButton`** et
|
|
**`TakeAllButton`**, chacun avec son Text Block (« Tout ranger », « Tout prendre »).
|
|
4. **⚠ L'étape qu'on rate** : sélectionne **`StorageGrid`** → Details → **Inventaire** →
|
|
**DÉCOCHE `Auto Bind To Pawn`**.
|
|
5. Rappel : un **Canvas Panel a une taille désirée de zéro**. Si `StoragePanel` disparaît dans
|
|
un conteneur en `Auto`, c'est ça — et un enfant en `Size = Fill` ne compte pas non plus dans
|
|
la taille désirée de son parent.
|
|
6. **Compile** et **Save**.
|
|
|
|
### 9.3 Le controller
|
|
|
|
Un seul réglage : `BP_FpsPlayerController` → Details → **UI | Storage** → **`Max Storage
|
|
Distance`** (400 cm par défaut). Rien d'autre à brancher, le coffre appelle `OpenStorage`
|
|
lui-même.
|
|
|
|
### 9.4 Vérifier que ça marche
|
|
|
|
1. Vise le coffre : le prompt affiche ton `Interaction Prompt`, et **au même endroit** que le
|
|
coffre soit ouvert ou fermé.
|
|
2. Interagis : le couvercle se lève en ~0,35 s, avec un départ et un arrêt adoucis, et l'écran
|
|
d'inventaire s'ouvre avec **le sac inchangé** et le coffre à côté.
|
|
3. L'onglet **Fabrication est grisé**, et le panneau de détail du sac a disparu.
|
|
4. Glisse un objet du sac vers le coffre : **Maj** = la moitié, **Ctrl** = un seul.
|
|
5. **Clic droit** sur une case : elle part en face. Fais-le depuis la barre rapide aussi.
|
|
6. **Tout ranger** vide la grille mais **laisse la barre rapide** intacte.
|
|
7. Recule de plus de 4 m : l'écran se ferme seul et le couvercle se rabat.
|
|
8. Appuie sur la touche de craft coffre ouvert : le coffre est rendu et l'onglet Fabrication
|
|
s'affiche.
|
|
9. **En coop** (build Steam) : l'autre joueur voit le couvercle se lever, et un objet déposé
|
|
apparaît chez lui sans qu'il ait à refermer et rouvrir.
|
|
|
|
---
|
|
|
|
## 10. Dépannage
|
|
|
|
| Symptôme | Cause |
|
|
|---|---|
|
|
| Le couvercle traverse la caisse / tourne de travers | Mauvais axe d'`Open Rotation`, ou pivot de `SM_Chest_Top` posé au centre du mesh — corrige-le dans Modeling Mode > XForm > Edit Pivot |
|
|
| Le couvercle est décalé de la caisse | `Location` de `LidMesh` (§9.1 étape 4) |
|
|
| Le couvercle saute au lancement | Une `Rotation` a été laissée sur `LidMesh` : `BeginPlay` la remet à zéro |
|
|
| Le coffre n'est pas visable | `Auto Fit Interaction Box` décoché avec une boîte restée minuscule, ou profil de collision changé |
|
|
| Le prompt disparaît une fois ouvert | La boîte a été reparentée sous `LidMesh` — elle doit rester fille de `BaseMesh` |
|
|
| Le sac du joueur s'affiche **des deux côtés** | `Auto Bind To Pawn` resté coché sur `StorageGrid` |
|
|
| Le panneau ne s'affiche pas du tout | `StoragePanel` ou `StorageGrid` mal nommé — **l'Output Log le dit** |
|
|
| Le panneau est là mais vide et plat | Canvas Panel dans un conteneur `Auto`, ou tous les enfants en `Fill` |
|
|
| Le coffre s'ouvre chez l'hôte, pas chez le client | `Client_OpenStorage` court-circuité, ou `Interact` appelé hors de `Execute_Interact` |
|
|
| Le couvercle reste levé chez les autres après fermeture | Chemin de sortie qui ne passe pas par `ReleaseActiveStorage()` |
|
|
| Un objet réapparaît après un transfert | Transfert exécuté côté client sans RPC — c'est la réplication qui le remet |
|
|
| « Tout prendre » vide le sac dans lui-même | Source déduite du récepteur au lieu d'être passée en paramètre |
|
|
| Les objets de départ manquent | `Slot Count` trop petit : le débordement part en warning dans l'Output Log |
|
|
|
|
---
|
|
|
|
## 11. Étendre
|
|
|
|
Gratuit, sans une ligne de C++ :
|
|
|
|
- **D'autres coffres** — le Blueprint est générique, seuls les meshes changent. La boîte
|
|
d'interaction se recale seule.
|
|
- **Des tailles différentes** — `Slot Count` par instance.
|
|
- **Un coffre-piège, une cache** — `Starting Items`.
|
|
|
|
Ce qui demanderait du code :
|
|
|
|
- **La sauvegarde du contenu.** C'est le chantier réel : `Slots` n'est pas sérialisé
|
|
aujourd'hui. Le format est déjà le bon — le même `FInventorySlot` que le sac, donc une seule
|
|
sérialisation servira les deux.
|
|
- **Les ressources du coffre comptant dans les recettes** — c'est la raison pour laquelle
|
|
l'onglet Fabrication est grisé plutôt que masqué : le jour où `UCraftingComponent` saura lire
|
|
un inventaire voisin, il suffira de le dégriser.
|
|
- **Un cadenas, un propriétaire** — il n'y a aujourd'hui aucun verrou, et c'est délibéré : coop
|
|
entre amis.
|
|
- **Un coffre en un seul mesh** (tonneau, sac) — la `UBoxComponent` reste valable, il suffit de
|
|
laisser `LidMesh` vide et `OpenRotation` à zéro. `FitInteractionBox` ignore déjà un mesh
|
|
absent.
|
|
|
|
---
|
|
|
|
## 12. Ce que ce système a changé ailleurs
|
|
|
|
- **`UInventoryComponent::TransferSlot`** est devenue l'**unique** implémentation du déplacement.
|
|
`MoveItem` / `MoveItemQuantity` ne sont plus que des appels avec `From == To`.
|
|
- **`ConfigureAsContainer()`** : zéro barre rapide, donc `GetBackpackStartIndex()` vaut 0 et la
|
|
grille affiche tout. Aucun nouveau modèle de données.
|
|
- **`UInventoryWidget` a perdu son lien vers sa grille parente** : une case diffuse
|
|
`OnHoverChanged` au lieu de connaître son propriétaire. Avec deux grilles à l'écran, l'index
|
|
seul ne désignait plus rien de façon unique — d'où `GetHoveredSlot(inventaire, index)`.
|
|
- **`NativeOnMouseLeave` n'arrive jamais quand un widget s'efface sous le curseur**, ce qui est
|
|
exactement ce qui se passe en fermant un coffre. D'où le passage obligé par `SetHoveredSlot()`
|
|
dans `ClearSlotWidgets()` et `BindToInventory()` — cette dernière annonce la sortie **avant**
|
|
de changer d'inventaire, sinon on ne sait plus quel affichage périmer.
|
|
- **`SetItemDetailsEnabled`** sur la grille, piloté par l'écran et non par une case à cocher.
|