You’ll be surprised that the “industry standard” framework for Roblox is: none!
You see, scripting is simply just a way for developers to interact with Roblox’s engine for their games to do stuff. Sure, there are some nitty gritty details about the language and API that you got to look out for, but generally speaking every property, method or signal you interact with in Roblox is already pretty abstracted away for you.
This is why frameworks are fundamentally flawed. Abstracting already abstracted APIs like RemoteEvent
s and ModuleScript
s in the name of modularity and keeping convention. It’s quite pointless, and worse, hurts productivity and runtime performance. Just see how many mistakes Stephen makes in his tutorial.
The second and more drastic thing I’d like to mention is the unhealthy tendency for frameworks and OOP to make you splinter state and logic across multiple files. You split everything up into nice Singletons and Classes that expose interfaces that lets them talk to each other.
But wait! What if PlayerService.lua
tries edits a field from AttributeService.lua
? Well, now you’ve shared mutable state between multiple scopes. But isn’t that the same reason you’re told to avoid global variables? It is, since now PlayerService could screw with AttributeService and any other script that reads/writes from it. I would write more about this specific problem, but here’s a great video that goes more in-depth about this.
So, how are you supposed to organize your scripts then? Everyone has their own style, but the way I like doing stuff is having all logic packed into one Server.lua
and Client.lua
which have different segments of code for each element of the game.
do end
blocks work fabulously with this.
-- Player
do
local Players = game:GetService("Players")
local Data = require(script.Data)
local Analytics = require(script.Analytics)
local function OnPlayerAdded(Player: Player)
if not Data:CanGetData(Player) then
Analytics:Log(`Failed to load data for Player {Player.UserId}`)
end
Data:Load(Player)
end
local function OnPlayerRemoving(Player: Player)
Data:Save(Player)
end
Players.PlayerAdded:Connect(OnPlayerAdded)
Players.PlayerRemoving:Connect(OnPlayerRemoving)
end
This way, modules like Data
and Analytics
simply act as extra abstraction for your logic. They don’t store or interact with state, so your flow of code is much more predictable.
Edit: The creator of Knit also actually made a blog about what he thought was fundamentally wrong with his framework! I recommend you check it out.