Experimental Mode
Experimental Mode
There are many legacy places and models that were created when “Experimental Mode” was available, but the setting has since been disabled. This article outlines some common design practices that will not work in the current Roblox architecture, along with ways to fix them.
Creating Parts
Sometimes a player action means new parts should be inserted into the game world. In Experimental Mode, a single LocalScript
could create a new part, tell the server about it, and the server would update the other clients.
Again, this code worked fine in Experimental Mode, but now it will only spawn the car for the player locally — no other player be able to see or interact with it!
A LocalScript
should fire a RemoteEvent
and a server Script
should manage the actual spawning of the car.
-- LocalScript (client) local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage")local player = Players.LocalPlayer
local playerGui = player:WaitForChild(“PlayerGui”)
local screenGui = playerGui:WaitForChild(“ScreenGui”)
local spawnCarButton = screenGui:WaitForChild(“SpawnCarButton”)local spawnCarEvent = ReplicatedStorage:WaitForChild(“SpawnCarEvent”)
local function onClick()
spawnCarEvent:FireServer()
endspawnCarButton.MouseButton1Click:Connect(onClick)
-- Script (server) local ReplicatedStorage = game:GetService("ReplicatedStorage") local ServerStorage = game:GetService("ServerStorage") local spawnCarEvent = ReplicatedStorage.SpawnCarEvent local carTemplate = ServerStorage.Car local function onSpawnCarFired(player) local car = carTemplate:Clone() local character = player.Character local humanoidRootPart = character:FindFirstChild("HumanoidRootPart") if humanoidRootPart then local spawnPosition = humanoidRootPart.CFrame + Vector3.new(0, 10, 0) car:SetPrimaryPartCFrame(spawnPosition) car.Parent = game.Workspace end end spawnCarEvent.OnServerEvent:Connect(onSpawnCarFired)
Moving Parts
Many player actions can cause parts to move. In most cases, such as a player’s character pushing or moving a part around, this automatically replicates to the server due to Articles/Network Ownership|network ownership
. However, this doesn’t apply to anchored parts which are always owned by the server.
In the following example, buildings are moved around a grid map by clicking and dragging on them. In Experimental Mode, the player’s client could move a building and the server would automatically update to show all players the new placement.
With Experimental Mode disabled, only the player who’s dragging the building will see it move.
This example must fire a RemoteEvent
when the player is done dragging the building. When the event fires, the client tells the server which building was moved along with its new DataType/CFrame|CFrame
.
-- LocalScript (client) local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local RunService = game:GetService("RunService")local player = Players.LocalPlayer
local mouse = player:GetMouse()local map = game.Workspace:WaitForChild(“Map”)
local buildings = game.Workspace:WaitForChild(“Buildings”)
local currentBuilding = nillocal moveBuildingEvent = ReplicatedStorage:WaitForChild(“MoveBuildingEvent”)
mouse.TargetFilter = map
local function onRenderStep()
local cell = mouse.Target
if cell and cell:IsDescendantOf(map) then
local newPosition = cell.Position
local base = currentBuilding.PrimaryPart
local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = base.CFrame:components()
local newCFrame = CFrame.new(newPosition.X, newPosition.Y, newPosition.Z, R00, R01, R02, R10, R11, R12, R20, R21, R22)
currentBuilding:SetPrimaryPartCFrame(newCFrame)
end
endlocal function onMouseDown()
local part = mouse.Target
if part and part:IsDescendantOf(buildings) then
currentBuilding = part.Parent
mouse.TargetFilter = buildings
RunService:BindToRenderStep(“DragBuilding”, Enum.RenderPriority.Input.Value, onRenderStep)
end
endlocal function onMouseUp()
if currentBuilding then
RunService:UnbindFromRenderStep(“DragBuilding”)
mouse.TargetFilter = map
moveBuildingEvent:FireServer(currentBuilding, currentBuilding.PrimaryPart.CFrame)
currentBuilding = nil
end
endmouse.Button1Down:Connect(onMouseDown)
mouse.Button1Up:Connect(onMouseUp)
-- Script (server) local ReplicatedStorage = game:GetService("ReplicatedStorage") local moveBuildingEvent = Instance.new("RemoteEvent") moveBuildingEvent.Name = "MoveBuildingEvent" moveBuildingEvent.Parent = ReplicatedStorage local function onMoveBuildingFired(player, building, newCFrame) building:SetPrimaryPartCFrame(newCFrame) end moveBuildingEvent.OnServerEvent:Connect(onMoveBuildingFired)
Server Manipulating Player’s Interface
Sometimes the server needs to update a player’s interface, like showing a popup menu. In Experimental Mode, the server could access the contents of the player’s PlayerGui
and make changes. Consider the following example which displays a message to any player joining the game:
Without Experimental Mode, the player’s GUI is not visible to the server. Also, changing what’s shown in a player interface should generally be managed by the client itself.
A RemoteEvent
should be used. The client can then use a LocalScript
to change the interface.
-- Script (server) local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage")local welcomeMessageEvent = Instance.new(“RemoteEvent”)
welcomeMessageEvent.Name = “WelcomeMessageEvent”
welcomeMessageEvent.Parent = ReplicatedStoragelocal function onPlayerAdded(player)
welcomeMessageEvent:FireClient(player)
endPlayers.PlayerAdded:Connect(onPlayerAdded)
-- LocalScript (client) local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local player = Players.LocalPlayer local playerGui = player:WaitForChild("PlayerGui") local welcomeScreen = playerGui:WaitForChild("WelcomeScreen") local welcomeMessage = welcomeScreen:WaitForChild("WelcomeMessage") local welcomeMessageEvent = ReplicatedStorage:WaitForChild("WelcomeMessageEvent") local function onWelcomeMessageFired() welcomeMessage.Visible = true wait(5) welcomeMessage.Visible = false end welcomeMessageEvent.OnClientEvent:Connect(onWelcomeMessageFired)
Tools
Tool events fire on both the client and the server, so it’s important to remember the role of each when adapting an Experimental Mode game. Any LocalScript
associated with a tool should be responsible for interface elements or local effects, along with player input. The server should be responsible for making changes to the game world, such as creating parts or changing game values.
The following example allows the tool user to create a sparkle effect on any other character that they click on. In Experimental Mode, this could be accomplished with just a LocalScript
:
Without Experimental Mode, this effect will not be replicated to the server (or other clients) because it’s created locally.
This example should fire a RemoteEvent
, telling the server to create the effect and which part it should create the effect on (the server has no idea where the player’s mouse is).
-- LocalScript (client) local Players = game:GetService("Players")local tool = script.Parent
local player = Players.LocalPlayer
local mouse = player:GetMouse()local createSparklesEvent = tool.CreateSparkles
local function onActivated()
local target = mouse.Target
local character = target.Parent
local humanoidRootPart = character:FindFirstChild(“HumanoidRootPart”)
if humanoidRootPart and not humanoidRootPart:FindFirstChild(“Sparkles”) then
createSparklesEvent:FireServer(humanoidRootPart)
end
endtool.Activated:Connect(onActivated)
-- Script (server) local tool = script.Parent local createsSparklesEvent = tool.CreateSparkles local function onCreateSparklesFired(player, humanoidRootPart) if humanoidRootPart and humanoidRootPart.Name == "HumanoidRootPart" and not humanoidRootPart:FindFirstChild("Sparkles") then Instance.new("Sparkles", humanoidRootPart) end end createsSparklesEvent.OnServerEvent:Connect(onCreateSparklesFired)