Statebagとは
Statebagは、効率的に状態を管理・同期するための仕組みです。
各エンティティ(プレイヤー、車両、オブジェクト)に紐づけられたデータをサーバー・クライアント間でやり取りできます。
従来のように
サーバー
→ クライアント
サーバー
← クライアント
で都度イベントを発行していた方法に比べて、簡潔に書けます。
グローバルStatebag
他のクライアント、サーバー、リソースでも参照できるグローバルなStatebagです。
サーバー側
local globalState = GlobalState.testState = 'Test'
クライアント側
print(GlobalState.testState) --Test と出力される
GlobalState.testState = 'Sample' --書き換え不可
クライアント側で書き換えることは出来ません。
機密性の高いもの、共有したくないデータを持たせるのはやめましょう。
書き込み(state:set)
local playerId = source
local playerEntity = GetPlayerPed(playerId)
Entity(playerEntity).state:set("isSpeaking", true, true)-- 第3引数true: グローバル同期
読み取り
local playerPed = GetPlayerPed(-1)
local isSpeaking = Entity(playerPed).state.isSpeaking
stateの変更を監視
AddStateBagChangeHandlerを使えば変更があったときだけ反応できます。
AddStateBagChangeHandler("blockTasks", nil, function(bagName, key, value)
local entity = GetEntityFromStateBagName(bagName)
if entity == 0 then return end
while not HasCollisionLoadedAroundEntity(entity) do
if not DoesEntityExist(entity) then return end
Wait(250)
end
SetEntityInvincible(entity, value)
FreezeEntityPosition(entity, value)
TaskSetBlockingOfNonTemporaryEvents(entity, value)
end)
Statebagを使わない書き方
クライアント側
local flashlightStates = {}
RegisterNetEvent("updateFlashlightState", function(playerId, state)
flashlightStates[playerId] = state
end)
CreateThread(function()
while true do
Wait(100)
local ped = PlayerPedId()
local flashlightOn = IsFlashLightOn(ped)
if flashlightState ~= flashlightOn then
flashlightState = flashlightOn
TriggerServerEvent("syncFlashlightState", flashlightOn)
end
end
end)
サーバー側
RegisterNetEvent("syncFlashlightState", function(state)
local src = source
TriggerClientEvent("updateFlashlightState", -1, src, state)
end)
Statebagを使った書き方
クライアント側
AddStateBagChangeHandler('flashlightOn', nil, function(bagName, key, value)
local netId = tonumber(bagName:gsub("entity:", ""), 10)
local entity = NetworkGetEntityFromNetworkId(netId)
if DoesEntityExist(entity) then
print(("Entity %s flashlight: %s"):format(netId, tostring(value)))
--描画処理を加えたり
end
end)
CreateThread(function()
while true do
Wait(100)
local ped = PlayerPedId()
local flashlightOn = IsFlashLightOn(ped)
if flashlightState ~= flashlightOn then
flashlightState = flashlightOn
Entity(ped).state:set("flashlightOn", flashlightOn, false)
end
end
end)
サーバー側
不要です。
非推奨なこと
ネストされたプロパティの直接変更
-- これは動作しない(変更が同期されない)
Entity(x).state.x.y = 'b'
Statebagは内部的に浅い構造で管理しているので同期されません。
非効率なデータアクセス
-- 非効率な例(同じデータを2回デシリアライズする)
local y = Entity(x).state.x.y
local z = Entity(x).state.x.z
解決方法
ネスト構造を避けてフラットなキー名を使用。
Entity(x).state['x:y'] = 'b'
Entity(x).state['x:z'] = 'c'
一度だけデシリアライズしましょう。
local xState = Entity(x).state.x
local y = xState.y
local z = xState.z
他のプラグインと競合しそうな単純な名前も避けたほうが良いです。
statebagのレート制限
server.cfg
にこれを追加します
set rateLimiter_stateBag_rate 2000
set rateLimiter_stateBag_burst 3000
これにより不具合を避けたり、プレイヤーがキックされるのを減らせます。
まとめ
便利な半面、構造の複雑化が増すこともあるのでうまいこと使い分けるのが良いと思いました。
イベントとの使い分けを考える必要がありそうです。
引用
docs.fivem.net
How to use State Bags - forum.cfx.re
wiki.bigdaddyscripts.com