games / fivem
Statebags et synchronisation d'entités
Explication
Ce que vous allez apprendre
- Comprendre ce qu'est un statebag et en quoi il diffère d'un event classique
- Attacher un statebag à une entité, un joueur, ou au serveur entier (GlobalState)
- Réagir automatiquement à un changement avec
AddStateBagChangeHandler - Comprendre pourquoi les statebags gèrent nativement le "late-join"
- Choisir entre statebag et event selon la nature de la donnée à synchroniser
Dans quel contexte ?
Un système de portes verrouillables doit informer tous les joueurs de l'état actuel d'une porte (verrouillée ou non), y compris ceux qui se connectent bien après que la porte a été verrouillée. Avec un système d'events classique, il faudrait explicitement renvoyer cet historique à chaque nouveau joueur. Les statebags résolvent ce problème nativement, sans code supplémentaire.
| Type de statebag | Portée | Écriture recommandée |
|---|---|---|
| Entity state | Une entité précise | Propriétaire de l'entité |
| Player state | Un joueur précis | Serveur, en général |
| GlobalState | Tout le serveur | Serveur uniquement |
Astuce
Utilise un statebag plutôt qu'un event dès que la donnée doit être connue en continu par tous les clients concernés (y compris les nouveaux arrivants), et réserve les events aux actions ponctuelles qui n'ont pas besoin d'être "rejouées" pour un joueur qui rejoint plus tard.
Le problème que les events seuls ne résolvent pas bien
Synchroniser un petit état (une porte verrouillée, un joueur menotté) avec le système d'events classique impose d'écrire manuellement l'envoi de l'information à chaque client concerné, y compris à ceux qui rejoignent la partie plus tard et qui n'ont jamais reçu l'annonce initiale. Les statebags résolvent élégamment ce problème : une donnée associée à une entité, un joueur, ou au serveur entier, qui se réplique automatiquement à tous les clients concernés — y compris ceux qui se connectent après coup.
Une donnée attachée à "quelque chose"
Contrairement à une simple variable, un statebag est toujours rattaché à un objet précis : une entité (un véhicule, un ped), un joueur, ou l'ensemble du serveur (le "GlobalState"). Cette donnée est lisible depuis n'importe quel client qui "voit" cet objet, sans qu'aucun event n'ait besoin d'être explicitement envoyé pour la transmettre.
Réagir automatiquement aux changements
Plutôt que d'interroger sans cesse la valeur d'un statebag pour savoir si elle a changé, on enregistre un gestionnaire (AddStateBagChangeHandler) qui s'exécute automatiquement dès que la valeur change, quel que soit le client qui l'a modifiée. Ce mécanisme fonctionne aussi bien côté client que côté serveur, ce qui en fait un outil flexible pour garder plusieurs parties du code synchronisées sans effort.
Le late-join, un cas souvent oublié
Un joueur qui rejoint un serveur en cours de partie doit récupérer l'état actuel de tout ce qui l'entoure (portes déjà verrouillées, joueurs déjà menottés). Avec un système d'events manuel, il faudrait explicitement renvoyer tout cet historique au nouveau joueur. Les statebags gèrent cela automatiquement : leur valeur actuelle est transmise dès qu'un client "découvre" l'entité concernée, sans code supplémentaire à écrire.
Commandes & code
Statebags : synchroniser des données sur des entités
Les statebags répliquent automatiquement une valeur associée à une entité, un joueur, ou globalement, sans écrire d'events manuels.
-- Statebag sur une entité (véhicule, ped...) : répliqué à tous les clients concernés
local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
Entity(vehicle).state:set('locked', true, true) -- (clé, valeur, replicate = true)
-- Lecture depuis N'IMPORTE QUEL client qui voit cette entité
local isLocked = Entity(vehicle).state.locked-- Réagir à un changement de state (fonctionne côté client ET serveur)
AddStateBagChangeHandler('locked', '', function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if entity == 0 then return end -- entité pas encore streamée localement, on ignore
if value then
SetVehicleDoorsLocked(entity, 2) -- verrouillé
else
SetVehicleDoorsLocked(entity, 1) -- déverrouillé
end
end)-- Statebag sur un joueur (Player state), utile pour un statut RP (menottes, ko, etc.)
local plyServerId = GetPlayerServerId(PlayerId())
Player(plyServerId).state:set('isHandcuffed', true, true)
AddStateBagChangeHandler('isHandcuffed', '', function(bagName, key, value)
local ply = GetPlayerFromStateBagName(bagName)
if ply == '' then return end
if value then
DisableControlAction(0, 24, true) -- désactive le tir, par exemple
end
end)-- Statebag global (GlobalState), utile pour de la config partagée mise à jour en live
GlobalState.serverOpen = true
GlobalState:set('eventActive', false, true)
AddStateBagChangeHandler('eventActive', '', function(bagName, key, value)
if bagName == 'global' and value then
print('Un event serveur vient de démarrer')
end
end)| Type de statebag | Portée | Écriture recommandée |
|---|---|---|
| Entity state | une entité précise (véhicule, ped) | propriétaire de l'entité |
| Player state | un joueur précis | serveur, en général |
| GlobalState | tout le serveur | serveur uniquement |
Résumé
- Les statebags évitent d'écrire des events manuels pour chaque petite synchronisation.
- Seul le "owner" d'une entité peut normalement écrire son entity state (le serveur reste sûr dans tous les cas).
AddStateBagChangeHandlerréagit aux changements, y compris ceux reçus après un late-join.
Exercices pratiques
Mission : réparer les portes qui oublient leur état
Objectif : Diagnostiquer un bug de late-join causé par un event mal choisi, puis migrer un système de portes vers un statebag d'entité.
Contexte
Le système de portes verrouillables de technologik_rp envoie TriggerClientEvent('technologik:doorLocked', -1, doorId) uniquement au moment précis où un joueur verrouille une porte. Un testeur se connecte 10 minutes après qu'une porte a été verrouillée par quelqu'un d'autre, et la voit affichée comme déverrouillée, alors qu'elle est bien fermée pour tout le monde depuis 10 minutes.