What does your code structure look like?

Recently I’ve been spending a bit too much time obsessing over how I should organize my code and what my code structure should look like for maximum efficiency. I’ve looked into Knit and determined that I don’t want to have a “barrier of entry” if someone else were to expand upon a project that I built though I really like the concept of using Services and Controllers to organize code.

I’ve also looked into many design patterns and though I haven’t familiarized myself enough with a lot of them enough to start actively using them within my code, it has given me a good insight into how large tech companies manage their code in such a massive infastructure.

With all of my previous projects, I’ve used a simple Single Script Architecture where all ModuleScripts have a .Init function that is called upon runtime and all methods inside of these ModuleScripts are exposed to every other ModuleScript within the game so they can communicate with eachother. I’ve found that doing this leads to a very tightly coupled codebase that causes me to spend a lot of time tracking down bugs. For example, if I were to have a ModuleScript that has a method inside of it that refreshes the character and I happen to run into a bug where the character is being refreshed at a point in time where it shouldn’t be being refreshed. I would have to find all instances within the entire codebase where this method is being called and track down the problem. Not fun.

So my question is, what does your code structure look like? Do you follow a certain architecture? Do you use OOP, why or why not? What do you do to organize your code?

1 Like

I don’t really know very well, I rather try to focus on writing good and working codes before optimizing/organizing my codes.
What I mainly do is, I put everything that is used by the server and client in the ReplicatedStorage, think of InGroup ChatTags/Overhead Guis, Sometimes I do use OOP, but I only do that when it comes to large-scale data, and not for a few little things.

Also i think it’s better to post this within #development-discussion

1 Like

For me i try to organize how mine looks.

Mine is filled with comments and spaces.
I try to make mine as easy to read as possible
Like as you can see this attack script for my game.

local attackEvent = game:GetService('ReplicatedStorage'):FindFirstChild('AttackEvents'):FindFirstChild('Gorilla')



local function detectPlayerFromChar(char)
	if game:GetService('Players'):GetPlayerFromCharacter(char) then
		print('Found the player')
		
		return game:GetService('Players'):GetPlayerFromCharacter(char)
		
	end
end



 -- create a part
local function makePart(size,color,po, destroy, t, transparency)
	local part = Instance.new('Part')
	part.Size = size
	part.BrickColor = color
	
	part.CanCollide = false
	part.CanTouch = false
	part.Anchored = true
	part.Transparency = transparency
	part.Parent = workspace
	
	
	if typeof(po) == "Vector3" then
		part.CFrame = CFrame.new(po)
	else
		part.CFrame = po
	end
	
	if destroy == true then
		game:GetService('Debris'):AddItem(part,t)
	end

	return part
end




repeat
	task.wait(1)
until script.Parent:FindFirstChild('EffectsModule')

local effectsModule = require(script.Parent:FindFirstChild('EffectsModule'))
local mod = require(script:FindFirstChild('ModuleScript'))



local visibleValue = 0.5


attackEvent.Event:Connect(function(player,typeOf,anims)
	print(player.Name .. 'Attacked!','Used attack '..typeOf)
	
	
	local char = player.Character
	
	
	local mod = require(char:FindFirstChild('CooldownMod'))
	
	if mod.Stuff.Attacking == false then
		
		
		
		if typeOf == 'ForwardAttack' and mod.Stuff.onCDFW == false then
			print('HARD ATTACK!')
			
			
			
			-- anim
			local anim = anims.forward
			mod.Stuff.onCDFW = true
			mod.Stuff.Attacking = true

			-- detect for a player
			local origin = char.PrimaryPart.Position -- the first po
			local root = char.PrimaryPart
			
			
			
			
			-- where the ray fires to
			local po2 = root.CFrame * CFrame.new(Vector3.new(0,0,mod.AttackDetails.forwardAttack.detectDistance))

			local part = makePart(Vector3.new(1,1,1), BrickColor.new('Lime green'), root.Position, true, 5, visibleValue) -- the fire point

			local part2 = makePart(Vector3.new(1,1,1), BrickColor.new('Baby blue'), po2, true, 5, visibleValue) -- where it should fire to. Point in the direction
			
			
			
			
			

			print('Sending a raycast')

			-- send the raycast
			local params = RaycastParams.new()

			-- direction
			print(po2)
			local direction = part2.Position - origin

			-- send the raycast
			local raycastResult = workspace:Raycast(origin,direction)


			
			

			if raycastResult and raycastResult.Instance then
				
				
				
				print('Casted')
				local target = raycastResult.Instance
				print('Hit '.. target.Name)

				local showPart = makePart(Vector3.new(1,1,1),BrickColor.new('Really red'),raycastResult.Position, true, 5, visibleValue) -- where it hit
				
				
				
				
				
				
				if target.Parent:FindFirstChild('Humanoid') then
					print('There is a hum')
					
					
					
					
					
					
					-- hit items
					local hitChar = target.Parent
					local Hithum = hitChar.Humanoid
					local Hitroot = hitChar.PrimaryPart

					-- move the char in front of the hitChar
					local charCframeToGoTo = Hitroot.CFrame * CFrame.new(Vector3.new(0,0,-2))

					char.PrimaryPart.CFrame = charCframeToGoTo

					
					
					
					
					-- point the hit char towards the player who attacked
					Hitroot.CFrame = CFrame.new(Hitroot.Position,char.PrimaryPart.Position)
					char.PrimaryPart.CFrame = CFrame.new(char.PrimaryPart.Position,Hitroot.Position)

					
					
					
					
					-- play the anim
					anim:Play()
					local rootAttacking = Hitroot

					anim:GetMarkerReachedSignal('End attack'):Connect(function()

						-- making sure it is the right function 
						if rootAttacking == Hitroot then

							print(rootAttacking)
							-- add knockback
							effectsModule.KnockBack(Hitroot,char,mod.AttackDetails.forwardAttack.knockback,mod.AttackDetails.forwardAttack.direction)
							print('Attack over')
							rootAttacking = nil

							effectsModule.Ember(Hithum,100,20,00.3)
						end


					end)
					
					mod.Stuff.Attacking = true
					
					-- attack is over!




				else
					print('No Hum')
				end
			else
				print('Hit nothing')
			end
			wait(mod.Stuff.forwardCD)
			mod.Stuff.onCDFW = true
			


			-- down attack
		elseif typeOf == 'DownAttack' then
			
			
			
			
			

			local downMod = mod.AttackDetails.downAttack

			-- anim
			local anim = anims.forward


			-- detect for ground
			local origin = char.PrimaryPart.Position -- the first po
			local root = char.PrimaryPart

			-- where the ray fires to
			local po2 = root.CFrame * CFrame.new(Vector3.new(0,downMod.detectDistance,0))

			local part = makePart(Vector3.new(1,1,1), BrickColor.new('Lime green'), root.Position, true, 5, visibleValue) -- the fire point

			local part2 = makePart(Vector3.new(1,1,1), BrickColor.new('Baby blue'), po2, true, 5, visibleValue) -- where it should fire to. Point in the direction


			print('Sending a raycast')

			-- send the raycast
			-- direction
			print(po2)
			local direction = part2.Position - origin

			-- send the raycast
			local raycastResult = workspace:Raycast(origin,direction)

			if raycastResult and raycastResult.Instance then
				local hitItem = raycastResult.Instance
				local hitCFrame = hitItem.CFrame

				char.PrimaryPart.CFrame = hitCFrame
			end

		end
	end
	
	
	
	
	
	
	
	
	
	
end)

I mean hey If i can read it then it works!

too many whitespaces :frowning: (charrrrsss)

1 Like

Lol I so agree. At least im not working with anyone so they dont gotta read my code lol

I like to group things and separate the next steps with an empty line

1 Like

Yes I can agree thats why I try to do! ofc I fail to remember most of the time . . .

But i don’t think i am the best person to tell you how to optimize ur code, since i rather have working code than good looking code :I

1 Like

I use modules, OOP and folders, a ton. In most cases, I have one main server / local script that’s nothing but this:

require(script.something)
require(script.somethingElse)
-- and you get it

Sometime I include stuff like waiting for things to load, basically keeping that script small while keeping it as the main “runner” of the system.
Example:

image

My handler code:

require(script.timerHandler)
require(script.settingsHandler)
require(script.uiHandler)
require(script.misc)

functions code:

return {
	animator = require(script.animator),
	changeOtherPlayersVisiblity = require(script.extraFunctions.changeOtherPlayersVisiblity),
}

my settingsHandler code:

local framesUi = script.Parent.Parent:WaitForChild("framesUI")
local settingsFrame = framesUi:WaitForChild("settings")
local holder = settingsFrame:WaitForChild("holder")

local mainUi = script.Parent.Parent:WaitForChild("mainUI")
local settingsOpener = mainUi:WaitForChild("settings")
local settingsClose = settingsFrame.close

local functions = require(script.Parent.functions)
local frameAniamtor = functions.animator.frameAnimator

settingsOpener.MouseButton1Click:Connect(function()
	(settingsFrame.Visible == true and frameAniamtor.closeFrame or frameAniamtor.openFrame)(settingsFrame)
end)

settingsClose.MouseButton1Click:Connect(function()
	frameAniamtor.closeFrame(settingsFrame)
end)

require(script.lightningSettingsHandler)
require(script.soundsSettingHandler)

return nil

As you can see, none of these loaders have any vital information, if you were to open my lightningSettingsHandler, it’s exactly 69 lines :wink: and it handles everything related to lightening, independently, it also has this:

return nil

So it runs alone but it needs the loader.


I always have 2 folders, “classes” and “modules”, I have them once under ServerStorage and once under ReplicatedStorage, for ServerStorage it holds things like DataStore2 or ProfileService and things that must not be viewed by the client whereas ReplicatedStorage holds things that just make the UX and UI better, a sound player, my SliderClass, etc.

Example:

image

image


I hope that helps with organizing your code and creating a good structure!
- msix

2 Likes

Same here, intigrate alot of OOP and modules into my code, makes it alot easier to use and cleaner

OOP’s a big helper, but only do it when it’s worth it - I’ve seen so many scripts people wrote where it’s messier and harder doing OOP. I only use it when it’s worth making the class for it, especially considering an OOP structure consumes more memory. If I have to group together a lot of related functions that are for one purpose and not of a class, I’ll do a normal module for it instead.

As for the script structure:

  • Services
  • Globals/Constants
  • Subprograms
  • Main code
  • Connections

It’s also good to have a lot of whitespace and commenting.

I tend to follow this ‘rule’:

  • Your code should be well enough organised and commented that another programmer should be able to read it and know what the code is trying to achieve without much effort.

(I’m pretty sure this belongs in #development-discussion )

Indeed. Not everything should be OOP though, ofc.


^ An average project of mine might look like this

Some keynotes:

  • I like to use Remo, ReactLua and more recently have been working with Reflex
  • I use a knit-like structure for my code, with controllers and services and a init and start lifecycle hook. I don’t use Knit itself as i find it restricting for networking and easy to forget the impact of shared states on performance, so I opt to follow a similar structure but not the entire framework. I’d look in to this article if you’d like to do the same
  • I like to manage and share states using a state management, however before that I would frequently use Roblox objects such as attributes.

You can view an example of some of my code here

But take everything I say as a reference and not a guide, ultimately your code should be forged in your own way. I was similar to you, obsessing over micro-optimizations and always trying to find the perfect way to do things, but the truth is that there is no perfect way to program. Overtime, I learned to realize that everyone’s brain works differently, and that the real challenge is finding your own style, that resonates with your thinking and become unique.

If you enjoy OOP, or maybe a modular codebase, then go for it. If it isn’t your thing yet, then do something else. Remember that the only quality that will differentiate you from other programmers is your way of executing things.

1 Like