You can write your topic however you want, but you need to answer these questions:
What do you want to achieve? Keep it simple and clear!
I’m trying to efficiently structure my code
What is the issue? Include screenshots / videos if possible!
There aren’t really that many topics discussing this, I think the way I’m doing it is very flawed
Here’s the code I made for a sword system not too long ago:
SwordModule:
local SwordModule = {}
SwordModule.__index = SwordModule
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Modules = ReplicatedStorage.Modules
local Remotes = ReplicatedStorage.Remotes
local Animations = ReplicatedStorage.Animations
local SwordRemote = Remotes.SwordRemote
local DefaultSounds = script.DefaultSounds
local DefaultSettings = script.DefaultSettings
task.wait(1)
function SwordModule:Initialise(Tool: Tool)
if Tool:GetAttribute("Type") ~= "Sword" then
warn("Weapon is not a sword")
return
end
local Sword = setmetatable({}, SwordModule)
Sword.Tool = Tool
Sword.Player = Tool.Parent.Parent
Sword.Character = Sword.Player.Character or Sword.Player.CharacterAdded:Wait()
Sword.Settings = require(DefaultSettings)
Sword.Cooldown = false
if Sword.Tool:FindFirstChild("Settings") then
Sword.Settings = require(Sword.Tool.Settings)
end
Tool.Activated:Connect(function()
Sword:Slash()
end)
Tool.Equipped:Connect(function()
for index, object in ipairs(Tool.Parent:GetChildren()) do
if object:IsA("Model") and object:GetAttribute("Type") == "Shield" and object:GetAttribute("Using") == true then
SwordRemote:FireClient(Sword.Player, "UnequipTools", "")
end
end
Sword.Character = Tool.Parent
local Equip = DefaultSounds.Equip:Clone()
Equip.Parent = Tool
Equip:Play()
Equip.Ended:Connect(function()
Equip:Destroy()
end)
end)
Tool.Unequipped:Connect(function()
Sword.Character = Sword.Player.Character
end)
return Sword
end
function SwordModule:Slash()
local Sword = self
if not Sword.Tool or not Sword.Player or Sword.Cooldown then
return
end
local possibleAnims = {"Swing_1_Right", "Swing_1_Left", "Swing_1_Top"}
local pickAnim = possibleAnims[math.random(1, #possibleAnims)]
SwordRemote:FireClient(Sword.Player, "PlayAnimation", pickAnim)
Sword.Cooldown = true
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Exclude
Params.FilterDescendantsInstances = {Sword.Character}
local Origin = Sword.Character.UpperTorso.Position + Vector3.new(0, 0.1, 0)
local Direction = Sword.Character.UpperTorso.CFrame.LookVector * 10
local ray = workspace:Raycast(Origin, Direction, Params)
if ray and ray.Instance then
local target = ray.Instance.Parent
local humanoid = target:FindFirstChildOfClass("Humanoid")
if target and humanoid and humanoid.Health > 0 then
humanoid:TakeDamage(Sword.Settings.Damage)
local Slash = DefaultSounds.Slash:Clone()
Slash.Parent = Sword.Tool
Slash:Play()
Slash.Ended:Wait()
Slash:Destroy()
end
else
local Swing = DefaultSounds.Swing:Clone()
Swing.Parent = Sword.Tool
Swing:Play()
Swing.Ended:Wait()
Swing:Destroy()
end
task.wait(Sword.Settings.Cooldown)
Sword.Cooldown = false
end
return SwordModule
Client:
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Humanoid: Humanoid = Character:WaitForChild("Humanoid")
local Remotes = ReplicatedStorage.Remotes
local Animations = ReplicatedStorage.Animations
local SwordRemote = Remotes.SwordRemote
local loadedAnims = {}
SwordRemote.OnClientEvent:Connect(function(Action, Value)
if Action == "PlayAnimation" then
if not loadedAnims[Value] then
loadedAnims[Value] = Humanoid.Animator:LoadAnimation(Animations.Combat_Anims:FindFirstChild(Value))
loadedAnims[Value].Looped = false
end
loadedAnims[Value]:Play()
elseif Action == "StopAnimation" then
if loadedAnims[Value] then
loadedAnims[Value]:Stop()
end
end
end)```
If you could share the way you’re doing it, we could give some direct advice.
Answer
But in general, there is no “best” code structure. It all depends on how big your game is, whether you want to organize it modularly, whether you want intellisense, etc.
About frameworks
You often hear of “frameworks” like Knit which are a way of structuring your code made by other people. Knit’s syntax is easy to learn and has its own built-in way of managing RemoteEvents easier., but it sacrifices intellisense because of its module loader and is considered outdated.
There are also frameworks for UI like Fusion and Roact which you can look into. Though I hear the learning curve is a bit higher for Roact.
Other than Knit, I’m not too particularly knowledgeable in other frameworks, but definitely look into those.
My approach
I have a Modules folder in ReplicatedStorage and ServerScriptService where I keep all of my modules. A script/LocalScript requires all of them and calls start().
For networking (RemoteEvents, etc), I use Packet (really good).
The Shared folder is where I keep a Config module for all customizable settings in my game (intermission duration, for example), and a Packets module to hold RemoteEvents through the Packet library.
The Util folder is just accessory modules I’ve made specifically for the game
The Packages folder contains modules/libraries by other people (I.e, ProfileStore).
My framework is still in development. I’m still thinking of how to organize class modules, how to manage my UI, etc.
My opinion
I suggest trying out several frameworks first if you think of making your own. It’ll give you much insight into how a lot of people like to organize their code and you can apply those to yours.
The way to improve code structure is to take in parts of what other people have, learn from their mistakes, and apply it to your own.
Ah! this is exactly what I have been looking for, but I got one more question
How should I handle controllers on the client-side?
Let’s say there are 30 unique swords with their own modules, how should I detect and handle input without making the code messy?
I like to define as much as possible up top, including required modules. Helper functions come next, followed by the main loop. This reduces the loop’s complexity and keeps everything organized. Consistent variables and proven methods are used throughout each script. A personal library of snippets prevents constantly reinventing the wheel. Functions and modules are written generically so they can be reused with different passed variables. Standard areas in the file structure store everything, eliminating the need to search for anything.
That’s pretty much a standard approach. There’s also “package creation,” where everything related to a task is kept in its own area, even if some parts need to be cloned to where they should be. You can drop it into a different program easily, and it works the same way.
Code structure specifically seems to be alright, although I do have a few questions to your naming scheme and typing. SwordModule:Initialise() seems to be for creating a new Sword object, but name of the function would suggest otherwise: usually, Initialize, or Init functions are called to initialize the module itself, not objects. Usual naming for creating new objects is .new().
Also I would suggest creating a Sword type and assign it to objects created by that function so intellisense displays autocompletion properly.
Other than that the code is pretty sound and readable
Thanks for linking my post. It is theoretically possible, but not reasonable for most people who want to focus on making games. There isn’t any public intellisense provider for knit yet, even though knit is very old and the script editor API has been out for a long while, and that’s for a good reason. If you want intellisense for knit you basically have to write your own language server in luau (which is considerably slow for this use case), which means parsing and statically analyzing luau. Making a tool like this is an incredible amount of work. If you just wanna get into game developing, making a whole intellisense plugin probably isn’t the way to go. It’s something I did, but it took me a lot of time that I could’ve been working on games. It’s kind of a deminishing investment where you waste so much time working on the plugin that you could’ve just been working without intellisense.
It’s kind of like saying that it’s possible to use EditableImages to render your own game instead of using Roblox’s renderer, like yeah theoretically it is, but it’s not a reasonable option if your focus is to release a game anytime soon, and Roblox doesn’t really provide the best tools to do so anyway, as EditableImages are pretty slow.