Architectures are the organization and structure of code, in this tutorial we'll be learning about the 2 common script architectures in Roblox.
Multi-script architecture uses multiple Script and LocalScripts to handle all game logic. This is what most people use when starting to script in Roblox Studio.
Fast development.
Easier to share scripts with others.
Easier to find and fix errors because they are contained within each script.
Easier copy-pasting to help you with complex game mechanics.
Forces you to use the properties of the DataModel for game state.
Promotes cloning of the same scripts which is inefficient and can give less performance.
Challenging control over code execution order which might create unpredictable behavior.
How to make Infinitely spawning violent spinning lasers with Multi-Script Architecture™
First, we need to make a very violent spinning laser model and prepare all the scripts needed.
The script named Handler handles all Laser logic and mechanics, damaging players and spinning the laser.
local Laser = script.Parent
local SPIN_SPEED: number = Laser:GetAttribute("Speed")
local DAMAGE: number = Laser:GetAttribute("Damage")
Laser.Touched:Connect(function(part) -- Handle damaging a character that touched the Laser
local Humanoid = part.Parent and part.Parent:FindFirstChildOfClass("Humanoid"); if Humanoid then
Humanoid:TakeDamage(DAMAGE)
end
end)
while true do -- Handle spinning the laser
Laser.CFrame *= CFrame.Angles(0, math.rad(SPIN_SPEED), 0)
task.wait() -- This is sugar for RunService.Heartbeat:Wait()
end
The script named LaserSpawner will initalize and spawn lasers at random positions.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserReference = ReplicatedStorage:FindFirstChild("Laser")
local MAX_SPIN_SPEED = 50
local MAX_DAMAGE = 50
local SPAWN_RADIUS = 250
local SPAWN_DELAY = 10
while true do -- Spawn and intialize the laser
local X, Z = math.random(-SPAWN_RADIUS, SPAWN_RADIUS), math.random(-SPAWN_RADIUS, SPAWN_RADIUS)
local Laser = LaserReference:Clone()
-- Set attributes
Laser:SetAttribute("Speed", math.random(0, MAX_SPIN_SPEED))
Laser:SetAttribute("Damage", math.random(0, MAX_DAMAGE))
Laser.Position = Vector3.new(X, Laser.Position.Y, Z) -- Position the Laser at a random position
Laser.Parent = workspace
Laser.Handler.Disabled = false -- Run the Handler script.
task.wait(SPAWN_DELAY)
end
Single-script architecture uses multiple ModuleScripts as "Systems", a single ServerScript and a single LocalScript to intialize/start those systems. Most experienced game developers has switched and studied this architecture at some point because it gives more modularity, reusability and more control over basically everything related to your code.
Control over code execution order.
Better control over code performance.
Can store game state in a million different ways other than the DataModel
Simplifies handling game logic.
It can be challenging to find and debug errors if they are not handled properly.
Challenging to learn, handle and use properly for new developers.
Much more difficult to share scripts for other developers.
How to make Infinitely spawning violent spinning lasers with Single-Script Architecture™
First, we need to make a very violent spinning laser model and prepare all the scripts needed.
The script called Server intializes all systems.
-- Run systems
for _i, system: ModuleScript in script:GetChildren() do
local success, err = pcall(require, system); if not success then
error(err .. "\n" .. debug.traceback()) -- Handle errors and print them to the console
end
end
The script called LaserHandler handles all Laser logic and mechanics, damaging players and spinning the laser.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LaserReference = ReplicatedStorage:FindFirstChild("Laser")
local Lasers: { Laser } = {} -- Store all lasers in an array
local MAX_SPIN_SPEED = 50
local MAX_DAMAGE = 50
local SPAWN_RADIUS = 250
local SPAWN_DELAY = 10
type Laser = {
SPIN_SPEED: number,
DAMAGE: number,
Touched: { Humanoid },
Reference: typeof(LaserReference),
}
local last_spawn = 0
task.spawn(function() -- Spawn a new thread so we don't yield the main thread.
while true do -- Handle Laser logic and mechanics
if os.time() - last_spawn >= SPAWN_DELAY then -- Check if it is time to spawn a new Laser
last_spawn = os.time()
local X, Z = math.random(-SPAWN_RADIUS, SPAWN_RADIUS), math.random(-SPAWN_RADIUS, SPAWN_RADIUS)
local Laser = LaserReference:Clone()
local index = #Lasers + 1
Laser.Touched:Connect(function(part) -- Queue damaging a character that touched the part.
local Humanoid = part.Parent and part.Parent:FindFirstChildOfClass("Humanoid"); if Humanoid then
table.insert(Lasers[index].Touched, Humanoid)
end
end)
Laser.Position = Vector3.new(X, Laser.Position.Y, Z) -- Position the Laser at a random position
Laser.Parent = workspace
-- Add new Laser to the Lasers table
table.insert(Lasers, {
SPIN_SPEED = math.random(0, MAX_SPIN_SPEED),
DAMAGE = math.random(0, MAX_DAMAGE),
Touched = {},
Reference = Laser,
})
end
for _i, LaserData in Lasers do -- Handle damaging characters and spinning lasers
LaserData.Reference.CFrame *= CFrame.Angles(0, math.rad(LaserData.SPIN_SPEED), 0)
for index = #LaserData.Touched, 1, -1 do -- Iterate from the end of the array so we don't break the loop whilst modifying the array.
-- Check if the Humanoid exists and is still alive
local Humanoid = LaserData.Touched[index]; if Humanoid:FindFirstAncestor("Workspace") and Humanoid:GetState() ~= Enum.HumanoidStateType.Dead then
Humanoid:TakeDamage(LaserData.DAMAGE)
end
table.remove(LaserData.Touched, index) -- Remove the Humanoid from the array because we already processed it.
end
end
task.wait() -- This is sugar for RunService.Heartbeat:Wait()
end
end)
return true -- Don't forget this is a ModuleScript.
Since we have come to the end of this tutorial, there's a few things I wanna mention: Modules (or packages whatever you call them). These are so useful and they make developing much easier. There are so much created by the community and some of them deserve a full tutorial post on its own, such as: