Hello, my name is Ideal, you might recognize me for my bad English skills and tutorials i’ve made in the past, for the last months i was able to work on few smaller projects and during this time i’ve learned about many hidden things in code that can be bad or good
As usual, i’ll improve this tutorial over time, so stay tuned
1. Coupling
Hello, today i want to teach you about the thing that can destroy your game, and it’s called Coupling
Hmm, it sounds like connections or smth like that?
Indeed, because Coupling tells how scripts are connected, it’s subjective tho, and to fully understand you have to see an example
Module A
local B = require(script.Parent.B)
local A = {}
function A.doSomething()
B.doSomething()
end
return A
Module B
local B = {}
function B.doSomething()
print("hello")
end
return B
Right… These are called modules…
You’re right, these are modules, but you can tell that they are Coupled
But how? like?
See, let’s say we wanted to change module B, we’ll add yielding, also we’ll change module A to understand it better
Module A
function A.doSomething()
B.doSomething()
print("A called out for B")
end
Module B
function B.doSomething()
task.wait(5)
print("hello")
end
Hmm, and?
And, the module A is dependant on module B, best way to show it was adding wait, as you can see if we want to process A’s logic, we need to wait for the B to perform it’s logic
Oh i see!, but you can simply add task.spawn
Imagine if we changed something else, if we removed the function? on this scale it’s nothing, but imagine 10 layers of abstractions, it wouldn’t be small overlook then
Hmmm, can you should an example?
Sure, here it is, let’s say we have OOP class and also objects list
local self = setmetatable({}, Class)
self.ID = UUIDGenerator.generate()
ObjectsList[self.ID] = self
return self
and of course destroying function
ObjectsList[self.ID] = nil
self.ID = nil
UUIDGenerator.deactivate(self.ID)
setmetatable(self, nil)
And? object can write itself to a list and then remove itself from this list, what’s wrong about that?
Imagine we wanted to make object outside this list, because let’s say this list is updated each frame by some game loop
local function onHeartbeat(DeltaTime: number)
--/ Update logic
for ID, Object in ObjectsList do
Object:update(DeltaTime)
end
end
Imagine we wanted to add static object, for some reason, that wouldn’t be updated, how to do it?
Oh… make an if statement inside class to make it add object onto the list
This is possible solution, but still, it’s short term, our problem isn’t about class itself, but it’s about class having access to objects list
Umm so what can we do?
Let’s create some manager that will take care of creating objects and adding them to the list
local ObjectsManager = {}
function ObjectsManager.createObject()
local Object = Class.new()
ObjectsList[Object.ID] = Object
end
return ObjectsManager
But how do we remove it?
There is simple solution, but i’ll show it in the next part, for now it’s use of signals, events or callbacks, i’ll use callback in destroy function
Class
function Class:destroy()
if self.onDestroying then
self.onDestroying()
end
--/ Destroy logic
end
and we can do this:
Object.onDestroying = function()
if not ObjectsList[Object.ID] then
return
end
ObjectsList[Object.ID] = nil
end
OHHHH! I UNDERSTAND NOW!
Nice to hear that, also don’t worry, as long as you nulify this onDestroying after it’s use, it’s 100% safe and wouldn’t cause any leaks
So now? what if i wanted to make class that’s outside the updates?
Simple… you use class alone, in our manager we added logic, our manager is like a screw, it connected our two components, but we can always remove it and we’ll have two separate parts to use, instead of one, or both if you have more screws and parts :}
Can you show one more example please? so i can understand it fully?
Of course, this time i’ll make different one
local ShelfManager = {}
function ShelfManager.getFruitsName(Shelf)
return Shelf.Fruit.Name
end
return ShelfManager
This is smaller coupling, but still, imagine if we changed fruit.Name for some reason?
Umm change a line here then
Sadly it’s harder to show on smaller examples, but still, if we used this name in 200 places it wouldn’t be nice? right?
So what to do?
Let’s say we only changed name for some reason, we can use getter method to get name, also we can add more things here later if we needed to
function Fruit:getName()
return self.Name
end
Then we do:
return Shelf.Fruit:getName()
Yhm, btw last question, is coupling really that dangerous for my game?
It depends, overall it’s better to avoid it, or else it might get overwhelming, in the next tutorials i’ll show concepts very connected to fighting with coupling and making your game even more scalable and easier to maintain
Ok, looking forward for next tutorials, thx!
Thx for reading, have a nice day, and good luck with your projects!
2. Communication
Hello, today i want to teach you about communication in programming and how scripts should co-operate to work well
So, first of all, how can scripts communicate? hmm?
Good question, so i’ll answer quickly, our scripts wouldn’t communicate, the code will, our system, it’s an abstract concept sort of, but…
Wait wait wait, what does it even mean?
Let’s jump into an example
local B = require(script.Parent.B)
local A = {}
function A.doSomething()
B.doSomething() --/ This is communication
end
return A
Umm you called a function xd
You’re absolutely right, calling a function is also communication, only on very small level, but still, communication between our code will be mostly made through module scripts, but there are other ways to do it
Like what ways?
Remember Coupling ???
Yh? it was in last tutorial
So, module calls are very likely to increase coupling, but as i said there are other ways for our code to communicate, the easiest one to understand is event-driven aproach
You want to use events to communicate between modules, so like bindables
Bindable events are one of the easiest ways, but signals would be better, there is plenty of signal modules which are faster and better than standard bindables, tho we wouldn’t discuss them here
Ok i understand, are there any other ways?
There are, one of them is update loop
What it does?
Lemme show you an example:
local TimePassed = 0
RunService.Heartbeat:Connect(function(DeltaTime: number)
TimePassed += DeltaTime
ManagerA:update(DeltaTime, TimePassed)
ManagerB:update(DeltaTime)
end)
So you call functions each frame? it’s bad for performance!
It depends, see, our update loop allows us to process everything without need of waiting, you can utilize it in systems such as pathfinding or projectiles to update them based off start time and current timestamp instead of task.wait that might fail
Still how does it help us?
Imagine you can now add these functions to each object we update
function Object:on()
self.IsActive = true
end
function Object:off()
self.IsActive = false
end
Hmm…
And at the end, let’s loop through all objects and update only active ones
for _, Object in ObjectsList do
if not Object.IsActive then
continue
end
Object:update(DeltaTime)
end
Oh i understand, you can tell when object should enable without starting updates loop, still is it really communication?
It is, or it’s more of a wireless communication for scripts, we communicate through safe method and avoid possibilities of leaks and bugs
I understand it know, it’s smart way
Possible ways to communicate between scripts:
- Use module scripts
- Use events or signals
- Use toggle switch & update loop aproach
- Use actors for parrarel luau
- You can use Roblox API events
Thanks for reading, have a nice day, bye!
3. Encapsulation
WIP
4. Abstraction
WIP
5. Iteration
WIP