Skip to content

Places without a server

A game is not always one place. A showroom build shares the real game’s scripts but should never talk to anyone; a test place loads the client scripts with no server behind them. By default neither works: the client module waits for the server’s remotes with no timeout, so every script that requires it stops for good, and the server module creates the remotes the moment it is required, so a build that must stay silent cannot require it at all.

Two options cover both halves.

Game.blink
option ClientConnectTimeout = 10
option AutoStart = false
event Ready {
From: Client,
Type: Reliable,
Call: SingleSync,
Rate: 5,
Data: u8
}

With ClientConnectTimeout set, requiring the client module waits at most that many seconds for the remotes and then returns either way. Check Connected to know which module you got:

Network.client.luau
local Net = require(ReplicatedStorage.Network.Client)
if not Net.Connected then
-- No server in this place. Everything below still runs, and does nothing.
end
Net.Ready.Fire(1)

A module that did not connect has the edit-mode stubs: Fire does nothing, listeners are never called, Iter ends at once, and Invoke fails at once. It stays that way: remotes that turn up after the timeout are ignored, because require has cached the module and a client joining half way would have missed what came before. Reload to try again.

Pick a timeout that a slow join will not reach. The client waits only for the server to have required its module, so in a real game the wait is usually a frame or two; ten seconds covers a server that is still loading.

With AutoStart = false, requiring the server module creates no remote and connects nothing. Call Start() where the game should talk:

Network.server.luau
local Net = require(ServerScriptService.Network.Server)
if not workspace:GetAttribute("Showroom") then
Net.Start()
end

Shared code can require the module and register listeners at the top level, in both builds, without a lazy require to keep the remotes from appearing. Before Start, a Fire is dropped with a single warning and an Invoke of a client fails at once; see the option for why they are not queued. Start may be called from several scripts: the second call does nothing.

Both modules export the remotes’ names, so nothing outside them spells out <RemoteScope>_BLINK_RELIABLE_REMOTE:

local Net = require(ReplicatedStorage.Network.Client)
print(Net.Remotes.Reliable, Net.Remotes.Unreliable)

Both options may sit under a profile, so only the build that needs them gets them:

@profile("test")
option ClientConnectTimeout = 2
event Ping {
From: Client,
Type: Reliable,
Call: SingleSync,
Rate: 5,
Data: u8
}