Script Architectures

A long tutorial about script architectures & how to use them effectively

by lisachandra

Author Avatar

Intro

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

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.

Pros & Cons:

Infinitely spawning violent spinning lasers with Multi-Script Architecture

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

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.

Pros & Cons

Infinitely spawning violent spinning lasers with Single-Script Architecture

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:

View in-game to comment, award, and more!