Understanding Roblox Architecture – How Roblox games work

understanding roblox architecture - how roblox games work

Understanding Roblox architecture is important for beginners when creating Roblox games. Software architecture is a lot like building architecture. It defines the blueprint for how a software system works. With architecture we want to understand the relationship between each sub system such as how each player’s device communicates with other players.

You’ll learn how different Roblox functions and Roblox events work together. These are vital for your game to work right.

We’ll also learn how Roblox services allow your scripts to tap into the Roblox engine and make your games fun!

Questions to think about

  • How does a player trigger events in a game?
  • How do we reduce the risk of players cheating or exploiting?
  • Why are my scripts slow sometimes?
  • What kind of functions or events are best?
  • How do all players see the same game state?

Client side scripts

Roblox architecture uses client side scripts to send commands or actions to the server. These client side scripts also give the player immediate feedback. 

In Roblox, you’ll use LocalScript to write client side scripts.

For a LocalScript to work, it must be a child or descendent of:

  • A Character model
  • PlayerGui
  • Backpack
  • PlayerScripts
  • Tool
  • ReplicatedFirst
understanding roblox architure - roblox client and server scripts

Diagram steps

  • In this diagram, a player presses the t button. 
  • A LocalScript receives an input event and detects that it was pressed.
  • The LocalScript then sends a command to the server to add a block to the world.
  • A server side script then validates that a block is allowed and passes this on to the game world.
  • The game world then updates itself with the new block and all players see the new block.

This design makes sure that all player’s can see these updates but it does introduce some lag or latency. You may see odd behavior or glitches when your home network is running slow or Roblox’s servers experience a moment of lag.

Server side scripts

A server-side script uses the Script type and must be a child or descendent of:

Replication

Replication copies data from its source to those that need the data. Roblox uses replication to copy game events to the server and all players currently on the server. 

Events such as the player’s jumping and moving get replicated immediately. A LocalScript is not able to update another player’s character, the server must do that.

Any sounds from the player also get replicated to all other players.

Any parts that have physics enabled, animation tracks, and input events from a device also replicate immediately to all clients.

Validation

A RemoteEvent is used to safely communicate events from a player to the server. If Roblox allowed a LocalScript to directly change the game state, then a malicious player could cheat. Luckily this isn’t the case.
Using a RemoteEvent is especially important when player’s purchase in-game items.

roblox script validation

Diagram steps

  • A player clicks the buy button from the purchase Gui
  • A LocalScript sends a RemoteEvent to buy the item
  • The server receives the event and triggers a Script
  • The Script validates that the player has enough Robux
  • If a player does not have enough Robux, we send an error event back to the player

If a player does have enough Robux, a Script gives the player the item and then updates the player’s Gui

Network Ownership

When working with physics simulated parts, we must keep in mind who owns the part. Roblox calculates each part’s physics using the client (player’s device) and the server. 

Problems occur when a player is not in range of some parts and can not assist with these physics calculations. Roblox solves this by passing ownership of the parts to another player or the server itself.

This diagram shows two separate events.

In the first event, the closest player takes the car’s seat and gains ownership of the vehicle.

In the second event, the car’s owner leaves the car’s seat and returns ownership back to the server.

For instance, a player may take ownership of a vehicle and begin driving. When the player is driving, the player has ownership of the vehicle. When the player leaves the vehicle, the server must take ownership so that the server can pass control to the next player that comes along.

In our scripts, you must use the SetNetworkOwner function to control ownership. As explained earlier, these actions can only occur in a server Script. A player’s LocalScript will use a RemoteEvent to pass on this command to the server to allow or deny the event. 

The server is the ultimate authority for maintaining the game state for all players. If multiple players tried to take control of a vehicle, the server can not give ownership to them all. In this case, the server would give ownership to the first event received and deny all others. Also, if a player were to log out, we may have logic in our game to immediately pass ownership to the server or wait a certain amount of time to give the server control again.

Event based design

While designing our game logic, understanding Roblox architecture concepts helps you with:

  • Preventing exploits and cheats
  • Giving player’s immediate feedback in game
  • Keeping game state consistent for all players
  • Creating the best player experience

It is best to keep all our heavy-duty logic in our Scripts while our LocalScripts trigger our server side logic. 

Scripts on the server handle game logic such as timers for game rounds, player stats and items, and a lot more. 

Using debounce logic is another key concept to use in your scripts. Debouncing prevents unexpected game behavior when player’s spam certain actions. A player may spam an attack or smash a purchase button. By using debouncing we can put in a little delay for each event our server Scripts receive. 

Debouncing controls how often events can occur.

Example:

local hasHealed = false
local function healPlayer
  if not hasHealed then
    hasHealed = true
    player.health = player.health + 10
    wait (1) -- wait 1 second
    hasHealed = false -- allow healing after delay
  end 
end

In this example, the script waits for one second before allowing the player to get healed. Without the one second delay, the script would run every time it is triggered.

Bindable events

A BindableEvent allows a Script to communicate one-way with other Scripts and does not work with LocalScript. Another limitation is that only one other Script can bind itself to a specific BindableEvent. 

Bindable events do not stop when a connected function fails. Using Bindable events is great for any game logic that follows a sequence or needs a time delay.

roblox bindable events

In some cases, you may need to run some logic when a certain event occurs or doesn’t follow a specific sequence. For example, a player can have a grenade. When the player decides to throw the grenade, we can set a five second delay before it explodes. We could include logic in the delayed event to have the grenade use a random value to determine if the grenade fails to explode or not.

Another example can be used when setting up the beginning of a race. The race will start with a pre race setup event. When the pre-race setup event occurs, we can then trigger another event to update the countdown for all players. Once the countdown hits zero, we then trigger another event to allow all players to hit the gas and start driving. Here we can have a function update the Gui for all players and start the race at the same time.

Code Example

local event = script.Parent -- this is a BindableEvent node
local raceState = “counting_down”
local countdown = 3

local function startRace
  if raceState == “counting_down”
      if countdown == 0
        raceState = “started”
        -- fire an event to all clients to start race
      else
        wait(1) -- wait one second
        countdown = countdown - 1
        -- fire event to update countdown gui for all clients
        -- fire event to start all engines
      end
  end 
end

event.Event:Connect(startRace)
event:Fire() -- start race sequence

This example kicks off the race using a state variable. This state variable allows the script to take different actions such as updating the player’s GUI and starting all engines.

Bindable functions

Bindable functions only work with server-to-server or client-to-client scripts. When a script calls a bindable function, the calling script gets blocked until the function gets handled or returns. This is different from a BindableEvent, where it will continue on with the script. 

  • BindableFunction is synchronous
  • BindableEvent is asynchronous

Synchronous vs Asynchronous code

Synchronous means that a function call must complete for the rest of the script to continue. When we’re dealing with synchronous code, it executes line by line.

Asynchronous means that a function does not have to wait and will continue to the next line. For those new to the asynchronous concept, you will use what’s known as a callback function. Asynchronous code does not execute in a predictable order since it depends on when the function will return a value to your callback.

Here’s a more relatable example to help understand the asynchronous concept. 

Let’s say we have a function named sendAMessageToFriend. Now we want to send multiple messages to our friend using this function.

In our messaging script we call sendAMessageToFriend three times with three different messages. Our callback to sendAMessageToFriend waits for a reply from our friend. Our friend may then reply to any of these three messages at any time. 

Example:

local function sendAMessageToFriend(friend, msg, responseCallback)
  local response = sendMsg(friend, msg) -- a yielding / synchronous function
  responseCallback(response)
end

-- prints out response when received
local function responseCallback(response)
  print(response)
end

-- connect our event to our messaging function
event.Event:Connect(sendAMessageToFriend)

-- fire off our messages to our friend
event:Fire(“Tandem Coder”, “hello!”, responseCallback)
event:Fire(“Tandem Coder”, “how are you?”, responseCallback)
event:Fire(“Tandem Coder”, “I’m hungry!”, responseCallback)

Things to be aware of with BindableFunction

  • Data parameters get copied and do not pass a reference
  • Table parameters only pass in values indexed by a number and do not include values indexed by key 
  • Non-string indices get converted to a string
  • Functions do not get copied

Pass by reference means that a function receives the actual variable stored in memory. Pass by copy will copy the data within the scope of the function and prevent a function from changing the data directly.

Remote events

RemoteEvent is used for client-to-server communication from a Script to a LocalScript. These events can go from the server to one client, one client to server, or server to all clients.

RemoteEvent scripts should get placed in ReplicatedStorage.

The server script defines the name of the event that the LocalScript waits for. This also works in the reverse direction, with the LocalScript defining an event that the server script waits for.

Client to server event

In our LocalScript we’ll get the RemoteEvent defined on the server from ReplicatedStorage. We’ll then fire off an event using the FireServer function. Functions on the server receive data to identify the player that started the event plus any additional parameters. 

Code Example

-- LocalScript
local replicatedStorage = game:GetService(“ReplicatedStorage”)
local buyItemEvent = ReplicatedStorage:WaitForChild(“BuyItemEvent”)
buyItemEvent:FireServer()

-- server Script
local replicatedStorage = game:GetService(“ReplicatedStorage”)
local buyItemEvent = Instance.new(“RemoteEvent”, replicatedStorage)
buyItemEvent.name = “BuyItemEvent”

local function onBuyItem()
  -- validate robux balance
  -- complete purchase
  -- update player on purchase status
end

buyItemEvent.OnServerEvent:Connect(onBuyItem)

Server to client event

A server Script will react to a Service event such as a new player joining the server. While handling this event, we’ll then trigger an event for one client or all clients. 

To send an event to a single client, we’ll use the FireClient function on the event.

To send an event to all clients, we’ll use the FireAllClients function on the event.

Code Example

-- server Script
local players = game.GetService(“Players”)
local playerJoinedEvent = Instance.new(“RemoteEvent”)
playerJoinedEvent.parent = game.ReplicatedStorage
playerJoinedEvent.name = “PlayerJoinedEvent”

local function onPlayerJoined(player)
  playerJoinedEvent:FireClient(player)
End

players.PlayersAdded:Connect(onPlayerJoined)

-- client LocalScript
local players = game:GetService(“Players”)
local replicatedStorage = game:GetService(“ReplicatedStorage”)

local player = players.LocalPlayer
local playerJoinedEvent = replicatedStorage:WaitForChild(“PlayerJoinedEvent”)
local playerGui = player:WaitForChild(“PlayerGui”)

local playerInfoScreen = Instance.new(“ScreenGui”)
playerInfoScreen.parent = playerGui
local playerInfo = Instance.new(“TextLabel”)
playerInfo.Size = UDim2.new(0, 100, 0, 25)
playerInfo.parent = playerInfoScreen
playerInfo.Visible = false
playerInfo.text = “add player info here”

local function onPlayerJoinedFired()
  playerInfo.Visible = true
end

playerJoinedEvent.OnClientEvent:Connect(onPlayerJoinedFired)

Remote functions

Remote functions use a synchronous communication flow. 

Client to server communication

For client to server communication our LocalScript will create an instance of our server-side defined RemoteFunction from ReplicatedStorage. Once that’s done, we’ll then call InvokeServer to initiate the function. In our Script, we’ll set the callback function using OnServerInvoke.

Code Example

-- LocalScript
local replicatedStorage = game:GetService(“ReplicatedStorage”)
local buyItemEvent = ReplicatedStorage:WaitForChild(“BuyItemEvent”)
buyItemEvent:FireServer()

-- server Script
local replicatedStorage = game:GetService(“ReplicatedStorage”)
local buyItemEvent = Instance.new(“RemoteEvent”, replicatedStorage)
buyItemEvent.name = “BuyItemEvent”

local function onBuyItem()
  -- validate robux balance
  -- complete purchase
  -- update player on purchase status
end

buyItemEvent.OnServerEvent:Connect(onBuyItem)

Server to client communication

For server to client communication, our Script will react to a service event. While handling the service event, it will call the RemoteFunction in our LocalScript. The RemoteFunction gets called using InvokeClient.

Code Example

-- client LocalScript
local replicatedStorage = game:GetService(“ReplicatedStorage”)
local someRequest = replicatedStorage:WaitForChild(“SomeRequest”)

local newRequest = someRequest:InvokeServer()

-- server Script
local replicatedStorage = game:GetService(“ReplicatedStorage”)
local someRequest = Instance.new(“RemoteFunction”)
someRequest.Parent = ReplicatedStorage
someRequest.Name = “SomeRequest”

local function onSomeRequest()
  -- create some object
  -- return some object
end

someRequest.OnServerInvoke = onSomeRequest

Things to be aware of with Remote functions

  • Use InvokeClient sparingly as it can lead to game breaking errors
    • Any client-side errors will also occur on the server
    • If client never responds, the server will hang waiting for a response
  • InvokeClient gives the client more control (potentially exploitable)
  • Best used when we need a response from a server function
understanding roblox architecture - services

What is a Service?

Services allow us to tap into Roblox game engine features. These features include the physics engine, Game Passes, Teleporting players, animations (tweening), debugging code, and storing player game state data. 

A service is globally available within client and server scripts. 

Most common services you’ll use include:

What’s next?

Now that you have a good understanding of Roblox architecture, we can dive into building our creative ideas. 

In the next few posts we’ll learn how to teleport players to different parts of your game, how to create a sequence of events, how to make game passes, how to create a round based game using timers, and how to add temporary sprinting for players. 

Also, make sure you understand Lua for Roblox scripting before you continue on your journey!

Stay curious!

Leave a Reply