Skip to content

How Rally games work

A Rally game is a world made of objects, plus TypeScript scripts that bring it to life. You build it in Rally Studio, right in your browser. When you publish it, people play it at rally.place in their browser: Rally runs your game on its own servers, and each player’s browser shows the world and runs the part of your code that belongs on their device.

This page explains the ideas everything else builds on. If you’ve made games on Roblox, most of it will feel familiar: the same tree of instances, the same services, and the same split between server and client code — written in TypeScript.

Every part, light, sound, script, piece of UI and player in a game is an instance: an object with a class (what it is, such as Part or TextLabel), a name, properties, and a parent. Instances form a single tree. Its root is game, and directly under it are fixed containers called services:

game
├─ Workspace the world: parts, models, spawn points, characters
├─ Players one Player object for each person in the server
├─ Lighting sky color, sun, ambient light and fog
├─ ReplicatedStorage what every player's device also gets: remote events, shared code
├─ ServerScriptService server scripts (never sent to players)
├─ ServerStorage server-only objects: templates, server code
├─ StarterGui on-screen UI, copied to each player when they join
└─ StarterPlayer character settings
└─ StarterPlayerScripts scripts that run on every player's device

In Studio, the Explorer panel shows this tree. An object is part of the game while it sits somewhere under game; scripts create, move and remove objects by changing their parent.

Here’s a script that adds a platform to the world when the server starts and greets everyone who joins:

ServerScriptService/Hello.server.ts
import { Color3, Part, Players, print, Vector3, workspace } from '@rally/sdk';
// Runs once, on the server, as soon as the server starts.
const platform = new Part();
platform.name = 'WelcomePlatform';
platform.size = new Vector3(12, 1, 12);
platform.position = new Vector3(0, 8, 0);
platform.color = Color3.fromHex('#2c7afd');
platform.parent = workspace; // now it's in the world, for every player
// Runs every time someone joins.
Players.playerAdded.connect((player) => {
print(`${player.displayName} joined`);
});

How games are organized explains what goes in each service, and Instances and the tree covers working with instances in code.

A running game is one server plus one client for each player: the browser tab they’re playing in. The server and every client each have their own copy of the tree. The server’s copy is the real one; each client’s copy follows it.

┌──────────────────────────────────┐
│ Game server │
│ the real tree (the truth) │
│ Scripts, physics, game rules, │
│ saved data │
└──────────────────────────────────┘
│ ▲ │ ▲
world updates, │ │ movement, │ │
20 per second │ │ remote events │ │
▼ │ ▼ │
┌────────────────────┐ ┌────────────────────┐
│ Player A's browser │ │ Player B's browser │
│ a copy of the tree │ │ a copy of the tree │
│ LocalScripts │ │ LocalScripts │
│ input, camera, UI │ │ input, camera, UI │
└────────────────────┘ └────────────────────┘
The server Each player’s device (client)
Runs Scripts LocalScripts
Has the whole tree a copy of what it’s allowed to see
In charge of rules, scores, spawning, physics, saved data input, the camera, on-screen UI, purely visual effects
Only available here saved data (DataStoreService), touched events keyboard, mouse and touch input, renderStepped, the camera

Scripts are TypeScript. They import the engine’s API from @rally/sdk, and other scripts’ code from @game/... paths. Where a script sits in the tree decides where it runs:

Kind Runs Put it in
Script on the server, when the server starts ServerScriptService, or inside an object in Workspace
LocalScript on each player’s device, when they join StarterPlayer › StarterPlayerScripts, or inside a ScreenGui in StarterGui
ModuleScript only when a script imports it anywhere — ReplicatedStorage is the usual place for shared code

A script anywhere else doesn’t run, and Studio marks it won’t run here in the Explorer. This small game uses all three:

ReplicatedStorage/Settings.ts
// Shared settings: both the server and players' devices import this.
export const GREETING = 'Welcome to the island!';
ServerScriptService/Greeter.server.ts
import { Players, print } from '@rally/sdk';
import { GREETING } from '@game/ReplicatedStorage/Settings';
Players.playerAdded.connect((player) => {
print(`${GREETING} (${player.displayName} joined)`); // the server's output
});
StarterPlayer/StarterPlayerScripts/Hello.client.ts
import { print, task, waitForLocalPlayer } from '@rally/sdk';
import { GREETING } from '@game/ReplicatedStorage/Settings';
task.spawn(async () => {
const me = await waitForLocalPlayer();
print(`${GREETING} You are ${me.displayName}.`); // shows up on this player's device
});

Scripts and modules covers scripts in depth.

The server sends each client a copy of the tree when they join, then keeps it up to date 20 times a second: new objects, changed properties and attributes, moved and destroyed objects. This is called replication. A few things are never sent to players:

  • ServerScriptService and ServerStorage, and everything inside them.
  • Server Scripts, wherever they are, and ModuleScripts. A module’s code is built into the scripts that import it, so it only reaches players as part of a LocalScript that uses it.
  • Other players’ UI: each player’s PlayerGui goes only to that player.
  • A few server-only properties, such as a player’s respawnLocation.

Because objects arrive over the network, a LocalScript can start before everything it needs is there. Client code waits for objects with waitForChild instead of assuming they exist.

A client can change its own copy of the tree — that’s how LocalScripts drive the UI, the camera and local effects — but those changes stay on that device. The server never sees them and other players never see them. If the server later changes the same property, the server’s value replaces the local one.

Clients send the server only two things: their own character’s movement (which the server checks) and calls on RemoteEvent and RemoteFunction objects (which your server code checks). The easiest way to share state with every player — including players who join later — is to keep it in the tree: value objects like NumberValue, and attributes on any instance, replicate automatically.

Anyone can change the code that runs in their own browser. If a player’s device could decide “I hit you” or “I have 1,000 coins”, cheating would be trivial. So in every Rally game:

  • The server owns the game. It runs the rules, keeps the scores, saves data, spawns characters, simulates physics and fires touched events. Everyone sees the same world because there’s only one real one.
  • Clients ask; the server decides. A LocalScript sends what the player wants (“open this door”, “buy the speed boost”) through a RemoteEvent. The server checks that it’s allowed and makes it happen. Never trust anything a client sends without checking it.
  • Characters are the one exception, for responsiveness. Each player’s device moves their own character right away, and the server checks every move against the character’s walkSpeed and jumpPower. A move that’s impossible is rejected and the player is snapped back. Scripts change speed, health and position on the server.

Multiplayer shows how to send requests to the server and check them.

Rally hosts your game on dedicated game servers. You don’t set anything up:

  • When someone presses Play, Rally puts them on a server that’s running your game’s current version and has room — the fullest one first, so players end up together — or starts a new one.
  • Each server holds up to your game’s Max players per server (1 to 100, set on the game’s page). Every server is its own copy of the world: players on different servers don’t see each other. To keep something across servers and visits, save it with data stores.
  • A server shuts down about a minute after its last player leaves. Anything you haven’t saved goes with it.
  • When you publish a new version, new servers run it. Servers that are already running keep the version they started with until everyone has left.
  • If a player joins the same server again from another tab or device, their earlier session there ends.
  • Each server runs your game 30 steps a second, in a sandbox with limits on memory and time. See Sandbox and performance and Limits.

A game is 3D or 2D. You choose when you create it, and you (or the AI) can switch it later; see Switch between 2D and 3D. Both use the same API. In a 2D game everything lives on a flat X/Y plane, viewed from the side: +Y is up, position.z only decides what’s drawn in front, parts rotate around Z only, and characters run left and right and jump. 2D games covers the differences.

Every published game is played full screen in the browser at https://rally.place/play/<game id> — the Play button on the game’s page opens it. Players sign in to Rally, or play an unlisted or public game as a guest without an account (see Guests); there’s nothing to install. Without writing any code, every game gets:

  • A character for each player with built-in controls: WASD or the arrow keys to move, Space to jump, the mouse to look around in 3D (hold the right button and drag, or click the game to lock the pointer; Esc unlocks it), and the scroll wheel to zoom. Touch screens get an on-screen joystick and a Jump button.
  • Name tags over other players’ characters, with a health bar when they’re hurt.
  • A player list in the top-right corner that shows each player’s leaderstats values.
  • A full-screen button, and a developer console: press ` (backtick) to see what your scripts print on your device. When you play your own game, the server’s output shows up there too — see Debugging.

If a script freezes the game in a player’s browser, Rally shows This game isn’t responding and offers to leave. Playing games covers the player’s side.

  • Studio saves as you go. What you edit is the game’s draft; players never see it.
  • Test with Play in Studio, or start a private test server with several players. See Testing.
  • Publish turns the draft into a new numbered version. That’s what players get.
  • Visibility decides who can play: Private (only you and your collaborators), Unlisted (anyone with the link) or Public (listed in Games for everyone).

Publishing and versions covers all of it.