Moud

Replicated values#

A replicated value is a value the server sets and clients read, with a signal when it changes, and no remote to set up. It is for game state that is not a property of anything: the round, the timer, the score of each team, a player's own coins.

One value for everyone#

A round timer is one value every player reads. The server counts it down, and each client's HUD shows it:

luau
-- server
local replicated = require("@moud/replicated")

local round = replicated.value("round", { phase = "waiting", seconds = 0 })

local function countdown(phase, seconds)
    for left = seconds, 1, -1 do
        round:set({ phase = phase, seconds = left })
        task.wait(1)
    end
end

task.spawn(function()
    while true do
        countdown("intermission", 15)
        countdown("playing", 120)
    end
end)
luau
-- client
local replicated = require("@moud/replicated")

local hud = game.world:add("ScreenGui", { name = "RoundHud" })
local label = hud:add("TextLabel", {
    position = udim2(0.5, -80, 0, 12),
    size = udim2(0, 160, 0, 28),
    textColor = color(1, 1, 1),
    backgroundTransparency = 1,
})

local round = replicated.value("round")

local function show(state)
    label.text = string.format("%s %d:%02d", state.phase, state.seconds // 60, state.seconds % 60)
end

round.changed:connect(show)
if round.loaded then
    show(round:get())
end
  • The name is the value's identity. The server and every client that asks for "round" get the same one.
  • The second argument is the starting value, read on the server.
  • set is server only. It sends the new value to every client and fires changed on the server and on each client with the new and the old value.
  • get is the value now. On a client it is nil until the first answer arrives; loaded turns true then, and changed fires for it. A client that joins halfway through a round gets the value the server holds, which is why the HUD reads loaded once as well as connecting to changed.

One value per player#

Coins are each player's own. Here the server loads them from a store, adds to them, and each player's HUD shows only their own:

luau
-- server
local replicated = require("@moud/replicated")

local coins = replicated.perPlayer("coins", 0)
local saves = store("coins")

game.players.joined:connect(function(player)
    coins:set(player, saves:get(player.id) or 0)
end)

local function give(player: Player, amount: number)
    coins:set(player, saves:increment(player.id, amount))
end
luau
-- client
local replicated = require("@moud/replicated")

local hud = game.world:add("ScreenGui", { name = "CoinHud" })
local label = hud:add("TextLabel", {
    position = udim2(1, -136, 0, 12),
    size = udim2(0, 120, 0, 28),
    textColor = color(1, 0.85, 0.3),
    backgroundTransparency = 1,
})

local coins = replicated.perPlayer("coins")
coins.changed:connect(function(amount)
    label.text = amount .. " coins"
end)
  • On the server set(player, value) and get(player) take the player. A player who was never set reads the starting value.
  • Each player is only sent their own value, so one player's coins never reach another player's client. A client reads its own value with get().
  • A player's values are forgotten when they leave, and may already be gone by the time your own leaving handler runs. Save as the value changes, as give does with increment.

What may be in one#

A value crosses the boundary like a remote's argument: numbers, text, booleans, vectors, frames, colours, instances, and lists or text-keyed tables of those. See What may cross.

Setting a table sends the whole table again, and changed fires every time a table is set, since the old and the new are different tables. A value that is the same number or text as before still goes out but does not fire changed.

The remote it uses#

The first require on the server adds a Remote named ReplicatedValues under game.world, and clients read every value through it. Leave it where it is.

Require the module from a server script that runs at start, so the remote exists before the first player's client asks for it.

For a value that belongs to an instance, use a value instance or an attribute instead; those replicate with the tree. See Shared state.