Skip to content

Networking

One player’s game is the server, the authority on what happens; the others are clients, connected to it. The server sends each client the state of the entities it should see.

function Lobby:host()
Net.startServer(7777)
end
function Lobby:join(address)
Net.connect(address, 7777)
end
  • startServer(port) hosts a game that this player also plays in; startDedicatedServer(port) hosts one with no player.
  • connect(host, port) joins one; disconnect() leaves.
  • isServer() and isClient() say which role this game has, and peerCount() how many connections it has.
  • canHost() says whether this build can host at all. A game in a web browser cannot, because a browser cannot listen for players, so offer Host only when it is true. Joining works in a browser.

startServer, startDedicatedServer and connect return false when they fail, for example when the port is already in use.

A player who joins from a title screen does not have the host’s map loaded yet, and the state the host sends needs the map’s entities to land on. The host calls waitForReady(true) when it starts. A player who joins then receives messages (for example an RPC that names the host’s map) but no state until it calls ready(), once that map has loaded. The state then arrives as one full snapshot. The Masquerade sample joins this way: it connects from its title screen, the host’s welcome message names the map, and the joining player loads it and reports that it is ready.

  • waitForReady(wait), on the host, sets whether players who join wait for their ready(). It is off by default, so state is sent as soon as a player joins.
  • ready(), on a client, tells the host that the host’s scene is loaded.

Desktop games talk over UDP. A host also listens on port + 1 (7778 above) for players in a web browser, who have no UDP. The web build makes the same connect call with the game port and reaches the host on port + 1 by itself.

An entity that every player should see the same gets two components:

  • Network Identity gives it an identity that every player shares. Replication assigns the identity; you do not set it yourself.
  • Networked Transform sends its position, rotation and scale from the server to the clients, which smooth the movement between updates.

Move the entity on the server, and the clients see it move.

A script reads an entity’s identity with NetworkComponent.of(entity) (NetworkComponent::of in AngelScript). Its authority says whether the server or a client owns it, owner is the owning player’s id, and id is its network id. All three are read-only.

The server can also add a networked entity while the game runs. Net.spawn(prefab, x, y, z) spawns the prefab as scene.spawn does, and every client spawns its own copy, including a player who joins later. Give the prefab’s root a Networked Transform so its movement follows. On a client, spawn creates nothing and returns an entity that is not valid.

Net.rpc(name) sends a message, Net.rpcNumber(name, value) sends one with a number, and Net.rpcText(name, text) sends one with text (Net::rpc and so on in AngelScript). A message from a client goes to the server, and a message from the server goes to every client.

Net.on(name, handler) receives them. The handler gets the sender’s player id, then the number or the text if the message carries one. On the server, the sender is the client that sent it; on a client, the sender is always the server (0). You can register handlers at any time, including before hosting or joining. Registering a second handler for the same name replaces the first.

Net.on("score", function(sender, value)
Log.info("player " .. sender .. " scored " .. value)
end)
Net.on("ping", function(sender) end)

In AngelScript a handler is a NetHandler (the sender only), a NetNumberHandler (the sender and a number) or a NetTextHandler (the sender and a const string &in). The engine declares these types, so a script does not.

A behavior’s property can be kept the same on every machine by marking it replicated. On a networked entity (one with a Network Identity, spawned by the server), the server sends the value whenever it changes, and each client’s copy of the behavior receives it, including the copy of a player who joins later.

List the names on the class:

Guest.replicated = { "activity", "costume" }

Numbers, booleans, strings, colours and Float3 values can be replicated. An entity or asset property cannot. Clients run the same behaviors, so change a replicated property only on the server (if Net.isServer()) and let clients only display it.

Each player has an id. The server is 0, and each client gets a number from the server when it joins. Net.localPlayer() returns your own id, or -1 while offline and until the server has welcomed you.

  • Net.onJoin(handler) and Net.onLeave(handler) call the handler with a player’s id. On the server they fire when a client joins or leaves. On a client, onJoin fires once with its own id when it has joined, and onLeave fires with 0 if the server goes away.
  • Net.rpcTo(player, name), Net.rpcNumberTo(player, name, value) and Net.rpcTextTo(player, name, text) send from the server to one client. They do nothing on a client.
  • Net.spawnFor(player, prefab, x, y, z) spawns as Net.spawn does, with the entity owned by that player. Net.isMine(entity) tells a player whether an entity is theirs, and NetworkComponent.of(entity).owner is its owner’s id on every machine.

Because a message’s sender is the player who sent it, the server can check that the player is allowed to do what the message asks before acting on it. When a player leaves, your onLeave handler decides what happens to the entities they owned.

  • Prediction. A client shows the server’s state as it arrives, with no prediction or lag compensation, so players feel the delay in fast action.
  • Encryption. Traffic between desktop games over UDP is not encrypted, and the host’s browser port speaks plain WebSocket. A web game served over https must join with a wss:// address, which needs a TLS proxy in front of the host.
  • Editor controls. Hosting and joining happen only from scripts; the editor has no networking settings. To test with two players, run the game with Play, then choose Game > Play New Instance to open a second Game tab with its own copy of the game, and host in one and join from the other.

Two Game tabs playing one match of the Masquerade sample: one hosting and one joined, each with its own view and HUD.