StateBag(状态包)是 FiveM 自 v2843 版本之后更新的一项功能,其作用是更方便的在不同客户端以及服务器之间同步实体 / 玩家数据。举个例子,在使用状态包之前,如果你想实现一个车辆转向灯的同步功能,你可能需要使用 NetworkEvent 进行同步:
服务端 server.lua
RegisterServerEvent('indicator:update')
AddEventHandler('indicator:update', function(networkId, isLeft, status)
TriggerClientEvent('indicator:update', -1, networkId, isLeft, status)
end)
客户端 client.lua
RegisterNetEvent('indicator:update')
AddEventHandler('indicator:update', function(networkId, isLeft, status)
local entity = NetworkGetEntityFromNetworkId(networkId)
if DoesEntityExists(entity) then
SetVehicleIndicatorLights(entity, isLeft and 1 or 0, status)
end
end)
但是这样做有一个问题,那就是在转向灯开启之后才加入游戏的玩家,他是无法看到车辆开启转向灯的,因为在他的游戏没有接收到 indicator:update
事件,而如果想要解决这个问题,你可能还要为此设计一套同步系统,非常的麻烦,而且非常占用网络事件资源。
现在,StateBag 来了,它就是为了解决这个问题而诞生的,利用 StateBag,你可以很轻松的实现客户端之间同步数据,无需自己手动写网络事件,那么我们现在来看看它是如何使用的。
实体状态包
在实体上的基本用法:
-- 获取玩家驾驶的车辆
local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
-- 获取车辆实体状态 indicator 的值,因为这里我们还没有设置,会返回 nil
local indicator = Entity(vehicle).state.indicator
print(indicator)
-- 为实体设置状态,其中 value 是你想设置的数值,最后一个参数 true 表示是否同步,false 是不同步
local value = 1
Entity(vehicle).state:set('indicator', value, true)
-- 此时我们再去获取一次 indicator 的值
print(Entity(vehicle).state.indicator)
-- 将会输出 "1"
玩家状态包
玩家也可以设置状态值:
-- 设置本地玩家的状态值
local value = 233
LocalPlayer.state:set('customdata', value, true)
-- 此时我们再去获取一次 customdata 的值
print(LocalPlayer.state.customdata)
-- 将会输出 "233"
设置完成后,在服务器上可以获取:
-- 要获取的玩家 ID
local player = 1
-- 获取玩家的状态值
local value = Player(player).state.customdata
-- 将会输出 "233"
print(value)
-- 设置一个新的值
Player(player).state:set('customdata', 666, true)
-- 再次获取
print(Player(player).state.customdata)
-- 将会输出 "666"
事件监听
你也可以通过监听事件来获取状态值的变化:
-- 监听玩家状态值变化
AddStateBagChangeHandler('customdata', nil, function(bag, key, value, _unused, replicated)
-- 如果数值为 nil 则返回
if not value then return end
-- 获取本次状态值变化的玩家,如果是实体则使用 GetEntityFromStateBagName
local player = GetPlayerFromStateBagName(bag)
-- 对玩家进行一些操作,这里我们只是简单的输出一下
print(player, value)
end)
全局状态包
此外,还有一个全局状态包,它在所有客户端和服务器之间都是共享的,但是只可以在服务端进行读取和修改,客户端是只读的。你可以通过 GlobalState
来访问它。
服务端:
-- 设置全局状态包的值
GlobalState.customdata = 2333
-- 输出
print(GlobalState.customdata)
-- 将会输出 "2333"
客户端:
-- 输出
print(GlobalState.customdata)
-- 将会输出 "2333"
状态包写入权限
- 服务端可以读写全局状态包,客户端只能读取
- Player 类型的状态包只能由服务器以及对应的客户端进行读写,其他客户端只能读取
- Entity 类型的状态包只能由服务器以及拥有该实体的客户端进行读写,其他客户端只能读取(实体所有者可以通过
NetworkGetEntityOwner
获取)
性能开销
状态包本身的性能开销很小,但是建议仅用于同步一些简单的数据,例如车灯信息、车锁信息等,不要用于同步大量的数据,会造成网络阻塞问题。
另外,如果你想在 0 Tick 循环中使用状态包,请使用以下方式:
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
-- 错误的用法,每次调用 LocalPlayer.state 都会进行一次反序列化操作
local value = LocalPlayer.state.customdata
-- 正确的用法,先将状态包反序列化为一个表,然后再使用
local state = LocalPlayer.state
local value = state.customdata
end
end)
更多关于状态包的用法可以参考 [链接登录后可见] 官方文档。