Best way to structure your code?

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I’m trying to efficiently structure my code
  2. 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)```

Can we see how your code is currently structured so we can help?

I thought I attached it
1 second

1 Like

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).

Heirarchy:

Further explanation

  • 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.

2 Likes

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

I do recall someone making a plugin that makes their Knit-like framework have intellisense.

So, it’s certainly possible to have the best of both worlds.

1 Like

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.

2 Likes

Of course.

If I were to use a framework, it’d be with a library that just wraps existing ModuleScripts, and require them like normal.

I linked your thread to show that it is possible (albeit, like you said, highly impractical lol)

This is actually something i’ve done and use occasionally; it gives ModuleScripts lifetime events. Just a slightly easier way of using RunService.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.