Retour au cours

games / fivem

Statebags et synchronisation d'entités

Leçon 121 exercice

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 statebagPortéeÉcriture recommandée
Entity stateUne entité précisePropriétaire de l'entité
Player stateUn joueur précisServeur, en général
GlobalStateTout le serveurServeur 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.

lua
-- 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
lua
-- 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)
lua
-- 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)
lua
-- 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 statebagPortéeÉcriture recommandée
Entity stateune entité précise (véhicule, ped)propriétaire de l'entité
Player stateun joueur précisserveur, en général
GlobalStatetout le serveurserveur 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).
  • AddStateBagChangeHandler réagit aux changements, y compris ceux reçus après un late-join.

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →