Even more advanced scripting tutorial

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 :confused:

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

6 Likes