One of my biggest criticism towards Roblox as a game development platform is how it doesn't push people to write code like it'd be done in the "real world". You can still do this. Sadly, there isn't a proper repository system nor a standard on how modules should be written and distributed.
You should only have one script for each end -- the server and the client. That script is responsible for the initialization process. Dependencies initialized by the "main" script then depend on other dependencies, while making sure cyclic dependencies do not happen. This is a great talk (1st of 3 parts) regarding dependencies and how to design the structure, and although it's presented in cppcon, it still applies to all projects.
Here's the structure for my most recent project.
And here's the dependancy tree associated with it.
The project is still so early in development that the tree will get a lot bigger, which just shows how large these programs can get. That's why it's really important to keep track of what's going on.
For example, here's what my "main" script looks like on the client:
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local get = require(ReplicatedFirst.get)
local function init()
print("Client initialization started")
local startTick = tick()
for _, module in next, get "all" do
coroutine.wrap(function()
if module.init then
module:init()
end
end)()
end
local endTick = tick()
print(string.format("Client initialization complete, took %.4f seconds", endTick - startTick))
end
init()
The server "main" is identical except it requires the "get" module from the ServerScriptService.
"get" is a function similar to other languages' include/require/whatever functions. The reason I'm not using Roblox's require function directly is because with my own function I can add extra functionality to it.
Here's an example code snippet of my "cameraInputManager" module, which also shows the usage of "get":
local get = _G.get
local camUtil = get "sys/camera/cameraUtil"
local input = get "sys/inputWrapper/input"
local camInpMngr = {}
local function rotateViewpoint(inputObject)
if inputObject.Delta.Magnitude ~= 0 then
local camera = camUtil:getCamera()
if camera then
local pitch, yaw = 0, 0
local delta = inputObject.Delta * -1
pitch = delta.Y / camera.viewportSize.Y * 5
yaw = delta.X / camera.viewportSize.X * 5
camera:addPitch(pitch)
camera:addYaw(yaw)
end
end
end
function camInpMngr:connect()
input:createListener("inputChanged", rotateViewpoint, Enum.UserInputType.MouseMovement, Enum.UserInputType.Touch)
end
function camInpMngr:init()
self:connect()
end
return camInpMngr
So in short: have one script per client/server and design with modularity (use functions properly) and dependencies in mind. Many people put way too much stuff into one function block which should not be done as that's not modular design.
There are quite a lot of results covering these topics on the internet and you should really look them up as they're way too wide to cover here. Just because they aren't done using Roblox or even Lua, doesn't mean it won't apply to them.
2 comments
ive never developed an actual complete game but NEVER put everything into a single big script
Keep in mind cohesion and coupling. You will end up using a lot of moules which is a good thing. Modules allow you to use lazy initialization if setup correctly.