Advanced Scripting Tutorial

Hello, my name is Ideal, i’ve made few tutorials about scripting in the past, but they mostly contained basic to intermediate knowledge and were discountinued

Because i never saw really more advanced topics on youtube, or at least not in single tutorial series, i decided to make this one

With time i’ll edit this post and add tutorials, it’s also easier to see how this doc is formatted


1. Module Scripts

In the first tutorial, you’ll learn about the key asset used to do anything, which is module script

Module Script is script which only runs when we require it, also it have to return something, otherwise it will error!

Example:

print("hi")
return nil --/ you need to add nil if you don't want to return anything!
require(script.ModuleScript) --> "hi"

As you can see, this is very usefull, because we can call the module from different scripts, without need of bindable events or events!

What else it can do?

Another thing it help us with, is practically the only way everyone else uses it, thanks to how table references work in Luau, we can use it to create something powerfull!

Because when we create a table, and try to create new variable based off that table, it wouldn’t copy it, it will give reference, this is why this:

local t1 = {Oranges = 5}
local t2 = t1

print(t1) --> {Oranges = 5}
print(t2) --> {Oranges = 5}

t1.Lemons = 2
print(t1) --> {Oranges = 5, Lemons = 2)
print(t2) --> {Oranges = 5, Lemons = 2}

As you can see, although we added lemons to table1, table2 also have lemons, because both variables point to the same reference, this is also why when we set t1 to nil t2 will still hold value, because those variables point to the table in memory, not copy it’s value!

And? What can we do with it?

Now we can use this to create shared tables, without Roblox’s global or shared, the only thing we will need to do is require module and return same table each time

local Table = {}
Table.Lemons = 2

return Table
local Table = require(workspace.ModuleScript)
print(Table) --> {Lemons = 2}

other script

local Table = require(workspace.ModuleScript)
print(Table) -> {Lemons = 2}

Now it’s very usefull i think

But we can not only read values from two different scripts, but we can also set them!

Table.Apples = 7
task.wait(1)
print(Table.Apples) --> 7

This is game changer, now not only can we share data between our scripts super fast, but we can also store important information outside of our main script, making it shorter and more redable

Note: Module Scripts share context with script that required them, this mean you can’t share data between client and server with them or to use parrarel Luau without main script and actors!


Example use case:

local LevelData = require(ReplicatedStorage.LevelData)

--/ Loading levels

Level.Timer.Value = LevelData.MaxTime
Level.Difficulty.Value = LevelData.Difficulty
Level.Reward.Value = LevelData.Reward

We assigned values to data saved in module, if we wanted to change it, we can simply change data in module, making it easy and fast


Now you understand that module scripts gave us a lot of opportunities, we’ll use them later to make your work go from impossible task to an relaxing and easy job

Thank you for reading, have a nice day, bye!

2. Code Organization

In the second tutorial, you’ll learn about simple tricks to make your code more redable on a logic level

Code Organization, combined with modular programming makes coding few times faster, refactoring became very easy, apart of that your game might benefit from lack of errors and performance boost

Example of bad code:

local RP = game.ReplicatedStorage
local c = game:GetService("CollectionService")
local scr = script
local p = script.Parent
local p1 = game.Workspace.Part
p1.Touched:Connect(function(p2)
if p2 then
if p2:FindFirstChild("Humanoid") then
if p2.Humanoid.Health > 0 then
if game.Players:GetPlayerFromCharacter(p2.Parent) then
game.Players:GetPlayerFromCharacter(p2.Parent).Character.Humanoid.Health = 0
end
end
elseif p2.Parent:FindFirstChild("Humanoid") then
if p2.Parent.Humanoid.Health > 0 then
if game.Players:GetPlayerFromCharacter(p2.Parent) then
game.Players:GetPlayerFromCharacter(p2.Parent).Character.Humanoid.Health = 0
else
print("")
end
end
end
end
end)

It must be very complex, this is why it looks like this

No, it’s not very complex, what this code does is set player’s health to 0 if he touched part in workspace, it works, but if we wanted to change anything, it’s impossible

First thing we’ll use is formatting, Luau does it automatically for you for most of the time, but if you still don’t use it, you need to press TAB after each scope opens and ends

Refactored code:

local RP = game.ReplicatedStorage
local c = game:GetService("CollectionService")
local scr = script
local p = script.Parent
local p1 = game.Workspace.Part
p1.Touched:Connect(function(p2)
	if p2 then
		if p2:FindFirstChild("Humanoid") then
			if p2.Humanoid.Health > 0 then
				if game.Players:GetPlayerFromCharacter(p2.Parent) then
					game.Players:GetPlayerFromCharacter(p2.Parent).Character.Humanoid.Health = 0
				end
			end
		elseif p2.Parent:FindFirstChild("Humanoid") then
			if p2.Parent.Humanoid.Health > 0 then
				if game.Players:GetPlayerFromCharacter(p2.Parent) then
					game.Players:GetPlayerFromCharacter(p2.Parent).Character.Humanoid.Health = 0
				else
					print("")
				end
			end
		end
	end
end)

Why is there so many stuff to change one property?

This time we can at least read what this code does, but we came across another problem, most variable names don’t tell us anything, this is common mistake beginners make, use of shortcuts and their own naming, see having very long variables name isn’t that bad, it makes your script a lot better in the long run

Refactor:

local ReplicatedStorage = game.ReplicatedStorage
local CollectionService = game:GetService("CollectionService")
local Script = script
local Parent = script.Parent
local Part = game.Workspace.Part
Part.Touched:Connect(function(Hit)
	if Hit then
		if Hit:FindFirstChild("Humanoid") then
			if Hit.Humanoid.Health > 0 then
				if game.Players:GetPlayerFromCharacter(Hit.Parent) then
					game.Players:GetPlayerFromCharacter(Hit.Parent).Character.Humanoid.Health = 0
				end
			end
		elseif Hit.Parent:FindFirstChild("Humanoid") then
			if Hit.Parent.Humanoid.Health > 0 then
				if game.Players:GetPlayerFromCharacter(Hit.Parent) then
					game.Players:GetPlayerFromCharacter(Hit.Parent).Character.Humanoid.Health = 0
				else
					print("")
				end
			end
		end
	end
end)

Now at least i know what instances are used

After giving our variables redable names, we can see that half of them are not even used, this is called You Ain’t Gonna Need That rule

Another thing we really don’t need is elseif statement, because Roblox’s characters have parts always under the main model, checking if any of those parts find Humanoid makes no sense

Last thing we don’t need is longer versions of getting some variables

Note: You should always aim to use same methood of getting variables, i couldn’t show it here that much, for instance ReplicatedStorage and CollectionService are required through different methoods, you should search which is best (In this case GetService is better)

Refactor:

local Part = workspace.Part
Part.Touched:Connect(function(Hit)
	if Hit then
		if Hit.Parent:FindFirstChild("Humanoid") then
			if Hit.Parent.Humanoid.Health > 0 then
				if game.Players:GetPlayerFromCharacter(Hit.Parent) then
					game.Players:GetPlayerFromCharacter(Hit.Parent).Character.Humanoid.Health = 0
				end
			end
		end
	end
end)

It’s 2x shorter now

But because we made it more redable, we can see more things we don’t need, like checking if Health is above 0 makes no sense, core scripts do it for you, or checking if hit exist, if you don’t destroy it, you can remove it too

Refactor:

local Part = workspace.Part
Part.Touched:Connect(function(Hit)
	if Hit.Parent:FindFirstChild("Humanoid") then
		if game.Players:GetPlayerFromCharacter(Hit.Parent) then
			game.Players:GetPlayerFromCharacter(Hit.Parent).Character.Humanoid.Health = 0
		end
	end
end)

Good job!

It’s not the end, we can make it a lot more cleaner and simplier, how you may ask?

We will use guard clausess instead of if statements, they are one line statements that return if something is false, another thing we’ll do is replace PlayerFromCharacter in changing property to reduce complexity

Refactor:

local Part = workspace.Part
Part.Touched:Connect(function(Hit)
	if not Hit.Parent:FindFirstChild("Humanoid") then
		return
	end
	
	if not game.Players:GetPlayerFromCharacter(Hit.Parent) then
		return
	end
	
	Hit.Parent.Humanoid.Health = 0
end)

Note: If your code is very simple, there is no need to make special variable for character or player, if there were more uses for them, you should definitively go for that

Good job now

We have last thing to polish, which is annonymous function, in more advanced systems those kind of functions might only help in very few scenarios, here we can create function and name it, this way we can eventually re-use it later

Another thing we’ll add is type check in function, this makes coding easier due to auto-fill and also tells you exacly what the variable should be

Polishing:

local Part = workspace.Part
local function onTouched(Hit: BasePart)
	if not Hit.Parent:FindFirstChild("Humanoid") then
		return
	end
	
	if not game.Players:GetPlayerFromCharacter(Hit.Parent) then
		return
	end
	
	Hit.Parent.Humanoid.Health = 0
end

Part.Touched:Connect(onTouched)

Great job, now i can read this function!

Thanks

Note: I used camelCase for function name and PascalCase for other names, this makes code look better as every variable don’t have random upper and lower cases in them


Before:

local RP = game.ReplicatedStorage
local c = game:GetService("CollectionService")
local scr = script
local p = script.Parent
local p1 = game.Workspace.Part
p1.Touched:Connect(function(p2)
if p2 then
if p2:FindFirstChild("Humanoid") then
if p2.Humanoid.Health > 0 then
if game.Players:GetPlayerFromCharacter(p2.Parent) then
game.Players:GetPlayerFromCharacter(p2.Parent).Character.Humanoid.Health = 0
end
end
elseif p2.Parent:FindFirstChild("Humanoid") then
if p2.Parent.Humanoid.Health > 0 then
if game.Players:GetPlayerFromCharacter(p2.Parent) then
game.Players:GetPlayerFromCharacter(p2.Parent).Character.Humanoid.Health = 0
else
print("")
end
end
end
end
end)

After:

local Part = workspace.Part
local function onTouched(Hit: BasePart)
	if not Hit.Parent:FindFirstChild("Humanoid") then
		return
	end
	
	if not game.Players:GetPlayerFromCharacter(Hit.Parent) then
		return
	end
	
	Hit.Parent.Humanoid.Health = 0
end

Part.Touched:Connect(onTouched)

Organizing your code, especially in bigger projects is a must, it boosts your coding speed and efficiency by a lot, making bugs rare and allowing you to quickly change it

Thanks for reading, have a nice day, bye!

3. Metatables

In the third tutorial you’ll learn about metatables

Metatable is a table attached to another table, it can contain special events called metamethoods, those can run the code when action is performed on a table

But before we will dive into metamethoods, you need to know how to create those metatables

local Table = {}
local MetaTable = {}

setmetatable(Table, MetaTable)
--/ Alternative: local Table = setmetatable({}, MetaTable)

Note: remember that table can have only one metatable!
Another Note: always remove metatable by using setmetatable(Table, nil) to avoid memory leaks!

To get metatable you can use this function:

local MetaTable = getmetatable(Table)

What are those metamethoods you’ve talked about?

Metamethoods are like events, they fire when
Here is the list of all metamethoods: Metatables | Documentation - Roblox Creator Hub

In this tutorial i’ll only give you example based off mostly used one, which is __index

local MetaTable = {__index = function(Table, Key)
    print("Key:", Key, "not found in table:", Table)
end}

local Table = setmetatable({}, MetaTable)
local a = Table.a --> Key a not found in table {}

what happened?

See, when we perform any action on a table, it goes in this direction:

  1. Action is performed
  2. Compiler checks if action can be performed
  3. Compiler checks if there is metamethood that use this action
  4. Compiler returns value or an error

Each metamethood have __ prefix, and can be set to function or value

Note: __index fires when we index table with non-existant key, it can call function or return us value

In our case, we used a function, but you can also return value, another bonus is that if value is a table, it will perform a check on it, and if there is another __index, it will also fire if conditions are met


Next thing we need to talk about is rawset, rawget, rawequal and rawlen, those are lua functions that allow us to change or retrieve table’s index without firing metamethoods

Example:

local Table = setmetatable({}, {__index = 4}) --/ will return 4 if __index is called
local a = Table.a
local b = rawget(Table, "b")

print(a) --> 4
print(b) --> nil, __index wasn't fired and didn't returned 4

Is it usefull?

The use case is occasional, mostly for __index you’ll use set and get, but if you use other metamethoods 2 other might come in handy


Example:

local MetaTable = {__add = function(Table1, Table2)
    local Sum = 0
    for _, number in ipairs(Table1) do
        Sum += number
    end

    for _, number in ipairs(Table2) do
        Sum += number
    end

    return Sum
end}

local Table1 = setmetatable({4,5,2}, MetaTable)
local Table2 = setmetatable({1,3,8}, MetaTable)

print(Table1 + Table2) --> 23

Metatables are one of the things we will use later, to create more advanced stuff and make tables better tool to work with

Thanks for reading, have a nice day, bye!

4. Object Oriented Programming - Basics

In the 4th tutorial you’ll learn about Object Oriented Progragramming paradigm

Object Oriented Programming (OOP) is paradigm (Sort of a pattern, way of coding) in which all logic is hiden behind objects

What does it mean?

To make it simple, you can look at instances in Roblox, for example a Part

Part have propertiess like color, transparency, material, position, ect… but also behaviors like :Destroy(), :FindFirstChild(), :Resize() and so on

But what Part is?
it’s an Object


Because Luau isn’t OOP based langugae like C++ or Java, you don’t need to use it, it’s also not a magic tool to fix your code, it can help a lot, but only when used correctly!


So, if Part is an Object, what defines it, what really makes Part being object?

Each object is like a container, each methood and property that belongs to it is encapsulated (isolated) within the object

???

To make it cleaner for beginners, imagine very large storage with control panel, you can’t enter it because there are too many machines and all of that technical stuff, but you can access items via panel, and also perform actions

Let’s say you want to place crate of basketballs inside this storage, you place crate on conveyor belt and then press Store() on panel, crate is stored on some shelf, and is saved in panel’s database

Now let’s say you want to get this crate back, you press Get() on panel and enter Basketballs crate, after a while you can see it on conveyor belt next to you

But maybe you want to count all items of the same type? you press Count() on a panel and after a while you see number on display

Ok, what does it really mean?

We used methoods to interact with warehouse, we also changed it’s data by adding and removing crate of basketballs, apart of that we used methood to use the data inside our storage


Finally, it’s time to understand another concept

if Objects have propertiess and methoods, how do we create them?

Classes is an answer, they are patterns we use to create Objects

Important Note: Objects’ propertiess can vary, but behaviors of every object related to a single class are always the same!


How to make Object based off pattern? it’s probably bad to make thousands of new functions

Indeed it’s very bad to make a lot of new functions, because even the same two functions are separate things, we can use inheritance

Inheritance

First base for OOP is inheritance, it means that classes or objects can inherit propertiess or behaviors from other classes, to understand it better let’s look at Part and BasePart

Every Part is a BasePart, but not every BasePart is a Part, it’s like square and rectangle
Another thing they share in common, is that everything we can do with BasePart, we also can do with Part, MeshPart and Part share commonalities like Position, Color, Transparency and more!

Why Is A was highlighted?

Because this is what inheritance means, that something is a some other thing

And because Part is a BasePart, it should have all methoods and propertiess each BasePart have, this is what we can do, each Object should have Behaviors of it’s Class, and this way we can also later set propertiess via some special init methood

Best way to create inheritance, is to use metatables with __index

but how?

There is interesting behavior called Table cyclic reference, it means that table1 references table2 that references table1 that…

Because we can return any value via __index, we can return MetaTable itself

local MetaTable = {}
MetaTable.__index = MetaTable

If you remember, in last tutorial we’ve learned that if returned value is a table, compiler will search through that table to check if there is index or if there’s not, and even firing __index if we added it!

So we can return Class table with methoods, and when we index Object with a methood it will search through Class, and find it?

local Class = {}
Class.__index = Class

function Class.test()
    print("hi")
end

local Object = setmetatable({}, Class)
Object.test() --> "hi"

Now it seems very usefull


In OOP we can use special function called constructor that creates Object, each Class have this kind of methood, usually it’s called .new() or .create()

Example:

local Class = {}
Class.__index = Class

function Class.create(Name: string)
    local Object = {}
    Object.Name = Name

    setmetatable(Object, Class) --/ We inherit methoods
    return Object
end

function Class:printName()
    print(self.Name)
end

return Class
local Class = require(script.Class) --/ Module scripts are best way to store classes

local Object = Class.create("Special Part")
Object:printName() --> "Special Part"

What is self and why not dot but semicolon?

This isn’t strict OOP, but what semicolon makes, is that if function is stored in a table, and we call Table:Function(), we can use self argument, which is simply Table that references this function, in our case Object is this table, because we used it to reference printName()

Non-OOP Example:

local Table = {}
function Table.test(self) --/ If you defined function via dot, you need to put self as first argument
    print(self.Text)
end

Table.Text = "Object Oriented Programming"
Table:test() --> "Object Oriented Programming"
function Table:test() --/ You don't need to put self here
    --/ Code
end
--/ Code
Table:test() --> "Object Oriented Programming"

OOP is great way to organize your code, it may cost a little bit more memory to use, but really this isn’t the case, in next tutorial i’ll dive deeper into when and how to use it correctly

Thanks for reading, have a nice day, bye!

5. Object Oriented Programming - Concepts

In the 5th tutorial, you’ll learn about OOP principles

Note: This tutorial can be skipped as it’s not required for OOP to work, but it may benefit you with usefull knowledge to make your code better

Abstraction:

Abstraction is process of simplifying your interaction with code by hiding unnecesary stuff

What does it mean?

It means that everything you don’t need to know, is not visible when you use it

Example:

function Class:findFirstObject(Name: string): Object?
    for _, Object in self.Objects do
        if Object.Name ~= Name then
            continue
        end
        return Object
    end
end
local Car = Class:findFirstObject("Car")
Car:drive(10)

In this example, we want to find object with name of Car, but we really don’t need to know how we will find it, we only need to know that we give Name and it should return Object or nil

Polymorphism:

Polymorphism means that one thing can have many forms/variations of it

Umm?

This one is probably strangest one, but it’s also very usefull, let’s say we have 3 classes, one is Car, second is Tank, third is Vechicle

Vechicle class have methood called Drive(), but tank and vechicle need to use it differently, and by differently in behavior, not data like speed or something else

Let’s say that Tank can drive only when turret’s angle relative to movement direction need to be less than 20 degree

Note: Class from which other class inherits is called super-class

This is where we can use combo of Inheritance and Methood Overwriting to achieve polymorphism, see __index fires when index is nil, and this is how we can implement our inheritance, because if Class don’t have our methood, but it’s super-class have one, metamethood will return it

Example:

function Vechicle:drive(Distance: number)
    self.Distance += Distance * self.Speed
end
function Tank:drive(Distance: number)
    if self.TurretAngle > 20 then --/ Different behavior than in :drive() methood
        return
    end
    self.Distance += Distance * self.Speed
end

And now we can create two vechicles

local Car = Car.new(5) --/ Speed 5
local Tank = Tank.new(4) --/ Speed 4

Tank:rotateTurret(25)

Car:drive(10) --/ Car moved 50 studs
Tank:drive(10) --/ Tank stayed in place

Nice

Important Note: Remember that polymorphism should only be used when behavior differs, not propertiess! if you have methood to add 10 points and other methood to simply add 20 points, simply make variable addAmount

Encapsulation:

Encapsulation means that we can’t directly access object, but rather use interface to do it

What? Like you can’t edit objects?

Not exacly, encapsulation tells that we shouldn’t directly change anything, rather use methoods to do that, in some languages, especially Java it’s even more advanced, as you have to create getters and setters for everything

Note: Getters and Setters are methoods that usually return or set value of our object, they are very good to have if we use object in many places, as one change require us to change one module instead of every script with reference to it

Example of getters and setters:

function Class:setA(Value: number?)
    self.A = Value
end

function Class:getA(): number?
    return self.A
end

Those 3 concepts + inheritance are called 4 principles of OOP, they are important as they allow programmers to create powerfull tools and make work a lot easier

Thanks for reading, have a nice day, bye!

6. Composition

In the 6th tutorial, you’ll learn about Composition

Composition means that code is composed off, can use something, has something

So what it does?

Composition means that we can use function/methood to process our code, but our code don’t rely on this function, making it a lot more flexible solution than alternatives

Example:

function openCarDoors(Car: Car)
    for _, Door in Car.Doors do
        Door:open()
    end
end

Looks like OOP without self

Composition is somewhat detached OOP, but it can be combined with it, creating interesting effect


To understand it better, we’ll compare Composition to Inheritance

First of all, Inheritance works on metatable __index, when Composition is simply function detached from the Object we currently use

Because we can have only one metamethood per table, this mean we can have only one super-class

Let’s try to create 3 classes, Car, Tank and Vechicle

Vechicle:

  • Can drive
  • Have engine
  • Need fuel

Car:

  • Can drive
  • Have engine
  • Need fuel
  • Have doors

Tank:

  • Can drive
  • Have engine
  • Need fuel
  • Have turret

It’s easy, use polymorphism + inheritance

Good point, but let’s say we had an update that added boats to our game

Boat:

  • Can swim
  • Have sail

Use inheritance, it wouldn’t be that bad if we called swimming function Drive()

And here you start seeing problem, all of those vechicles have different variations, both in core methoods and addons, of course inheritance and polymorphism can help but when you add more and more, it will become unredable and hard to change

This is why composition is mostly superior to inheritance, now imagine we do this:

Car:

  • Can move
  • Have engine
  • Have doors

Tank:

  • Can move
  • Have engine
  • Have turret

Boat:

  • Can move
  • Have sail

Engine:

  • Need fuel

Doors:

  • Can be opened

Sail:

  • Can be unfurled or furled

Now we have 2x more classes

We created 3 new classes, they have their own functionalities, and now instead of inheriting engine or doors or sail from Vechicle class, we simply made each vechicle have needed parts

Note: I know i could have made it unclear, so to make it shorter, at the beginning each class inherited components from super-class Vechicle, but when we added more vechicles with different functionality it became harder to implement new methoods, this is why we created classes for
each component we needed, apart of that we turned Can sail and Can drive methoods into Can move to make it more redable

Note: You can merge inheritance and composition, it’s nothing wrong, i’ve did it here with using inheritance for Can move and composition for vechicle components


Example:

local Functions = require(script.Functions)
local Car = {}

Car.move = Functions.move
return Car
local Car = require(Modules.Car)
Car:move() --/ If move in Functions was declared with semicolon you can use self

Example:

function Car.new(Speed: number): Car
    local self = setmetatable({}, Car}
    self.Distance = 0
    self.Speed = Speed
    self.Engine = Engine.new()
    self.Door1 = Doors.new()
    self.Door2 = Doors.new()
    self.Engine:refuel()

    
    return self
end

function Car:move(Distance: number)
    if not self.Engine:isOn() then
        return
    end

    self.Distance += Distance * self.Speed
end
local Car = Car.new(5)
Car.Engine:turnOn()
Car:move(10) --/ Moved 50 studs

Composition is great tool to make your code a lot more scalable and easier to use

Thanks for reading, have a nice day, bye!

7. Memory Leaks

In the 7th tutorial, you’ll learn about Memory Leaks

Memory Leak is a piece of memory (data saved by program) that cannot be removed from registry (place where memory is stored, there are milions of them in each computer), causing performance issues

A lot of technical stuff

To make it simplier imagine you have a tank, and water is flowing into it, one day you forget to turn off tap, after a while water will simply fill the box and start to pour out

Of course we want to prevent water pouring out, so we will turn off water tap or make complex mechanism to remove water from a box, but the problem stays the same

Memory leak happens, when we create something and don’t remove it properly, by removing, i mean clearing all Strong References, in Luau case, there are only strong references, apart of weak tables made by using __mode metamethood

They usually happen with Roblox’s Objects, as they need to be properly removed, by calling :Destroy() or :Disconnect() and have no reference in table

Example:

local Part = workspace.Part
ModuleScript[1] = Part --/ Inserting Part to a module script

Part:Destroy() --/ Part is destroyed, but it still have reference in ModuleScript

--/ Scope ends, but Part cannot be Garbage Collected, reference still exists!

Ok so like we need to clear all references?

Now, let’s see another part of code

--/ Different script
task.wait(2)
ModuleScript[1] = nil --/ Part's reference is removed, not it can be freed

--/ Garbage Collector freeds part, memory leak will not happen

Note: Remember that when variable is going out of scope there is no reference to it in that scope, this mean that it can be freed by GC


Connections can also leak memory, here is example:

local Connection = game:GetService("RunService").Heartbeat:Connect(function()
    --/ code
end)

Because connection isn’t disconnected, it will hang around, if there will be a lot of those connections, they’ll cause memory leak over time

Fix:

Connection:Disconnect()

Note: You can use :Once() to make function disconnect after being called once


Best practices

  1. Add :destroy() methood to OOP objects
function Object:destroy()
    self.Property1 = nil
    self.Property2 = nil

    table.clear(self.Array)
    self.Array = nil

    setmetatable(self, nil)
end
  1. Always disconnect connections when not needed anymore
local Clicks = 0
local Connection
local function clickTenTimes()
    if Clicks >= 10 then
        if Connection then
            Connection:Disconnect() --/ We don't need this connection anymore
        end
        return
    end
    Clicks += 1
end

Connection = workspace.Part.ClickDetector.MouseClick:Connect(clickTenTimes)
  1. Use :Destroy() on instances you don’t need anymore
  • Destroy Automatically calls itself on every instance
  • Destroy disconnects connections related to destroyed instance
  1. Test and measure, it’s the best way to find out about memory leaks
  • Use Luau Heap
  • Use Memory Category

Notes:

  1. There are some under-the-hood features that might look like memory leak, usually they are optimizations, you shouldn’t worry about them much if you find one

  2. Remember that tables are the hardest part to deal with if we talk about memory leaks, you should use modules like

  3. If your game design makes long-term memory leaks impossible, then don’t try to fix them if it’s vey hard, games with separate places, like tower defenses shouldn’t have much problem handling small leaks


There are few topics that discuss more about Garbage Collector and Memory Leaks, links below:
A Beginner's Guide to Lua Garbage Collection

Garbage Collection and Memory Leaks in Roblox - What you should know

How to use a Maid class on Roblox to manage state


Memory leaks are usually hard to deal with and overtime can ruin your game, after this tutorial you should know what are they, and how to deal with them without shutting down servers or limiting experience

Thanks for reading, have a nice day, bye!

8. State Machines

In the 8th tutorial, you’ll learn about State Machines

State Machine is system in which logic is based off current states, states can change depending on given factors

So like if statements?

If statements are main part of those systems, but the idea is slighty different

function Class:use()
    if self.State == "using" then
        return
    end

    if self.State == "idle" then
      self:doThat()
    end

    if self.State == "working" then
        self:doThis()
    end
end

function Class:doThat()
    self.State = "using"
    task.wait(1)
    self.State = "working"
end

function Class:doThis()
    self.State = "idle"
end

Oh it’s simple, like we check if we can do something and we decide based off state

States are very simple concept, they allow you to control your game, combined with OOP and Events, they become great to sort logic


Thanks for reading, have a nice day, bye!


Summary

Thank you for reading this tutorial, i wish you have learned a lot from it and now your code a lot better, i might be working on other tutorials in the future

Thank you again, have a nice day, and good luck!

4 Likes

hold up. you are saying module scripts is advanced? I thought its the most basic thing one should know

4 Likes

I wanted to explain what are they, because i’ll use them later

3 Likes

local Script = script :sob:

3 Likes

i use this line of code on a daily basis, in EVERY script in my game.

very useful

I think it showed bad code well right?

It’s not a “bad” guide so to speak, but the topics you talk about here are not what i’d define as “advanced”. When I see a tutorial described as advanced i’d expect something like, I don’t know, metatables being talked about. Perhaps you could let modules slide in but code formatting sounds like a very basic topic. Maybe if you talked about more topics it wouldn’t be a big issue.

I shouldn’t have to say this but don’t feel discouraged by my reply, rather take my opinion as a way to improve next time

2 Likes

Ik, but metatables and magic algorithms can’t help if someone don’t know basics, and no one really talks about those, without them i can’t continue the tutorial