Any example on using Metatables and OOP? Specifically at creating a Tower Defense like game

Hey guys, I’m planning on remaking and basically recreating everything at my Tower Defense game.

So lately I’ve heard materials about Metatables and OOP. I did not completely learn about the course of both of them but I’m scheme to study soon after I finished my job at school which is like early May.

But I’m curious what how would I apply Metatables and OOP for a Tower Defense like game?.
Like in what mechanic/system will this 2 become useful and great?.

Me appreciate any help!!

Here is an example of how you would implement that which I made in dev. forum text box

Well, actually its an example of how it would look if you want to implement OOP in your tower defense game

local class = {}
class.__index = class

class.EnemyCollection = {}

--\\ Spawn enemy
function class.new(name, path)
     local Model = Path:FindFirstChild(name)
     if not Model then return end

     local self = setmetatable({}, class)

     self.Health = Model:GetAttribute("Health") --\\ Example
     self.Speed = Model:GetAttribute("Speed")

     self.Model = Model:Clone()
     self.Model.Parent = workspace

     self.Path = path

     class.EnemyCollection[Some Id] = self
     self:Start()

     return self
end

function class:IncrementHealth(amount)
    self.Health = math.max(0, self.Health + amount)
end

function class:Start()
      --\\ Some code to start moving enemy
end

return class

If you want I can explain you what parts of code do

1 Like

OOP is used to replace redundant code, so you don’t have to copy and paste scripts just to change one small thing, its useful when you work with lots of objects.

You can use OOP for different types of enemies and towers for example, because in a TD game theres usually a lot of towers and enemies, and OOP can improve the redundancy.

You could make a basic constructor of an enemy class, for example enemy.new(), with the basic stats like health e.t.c.

Then you can use polymorphism(one of the things in OOP, look it up of you don’t know what it is.) To make a subclass of the main enemy class, then specify different attacks, movement speed, e.t.c.

Probably not the best way, but that’s probably how I would do it.

2 Likes

Kinda learning metatables and oop here and im slowly understanding stuff but slooooowwwlyy.

By the code you made (thanks btw for providing example!!) and this one i quoted,
So i think this will add the enemy into that table (correct me if im wrong), and there will be like tons of enemies, so does that mean adding alot of enemy to the table may has the potential to lag the game or its fine?.

1 Like

It will lag if there is ton of them (Like 1K), but it will lag probably just because there’s a lot of models
it should have from no to little impact on performance (At least from what I seen)

And yes, it will add the enemy to the table and you can access the enemy from other script
Be sure to delete it from the table when enemy dies to avoid memory leaks

1 Like

By the way, If i come to create multiple enemies, does that means im gonna create multiple metatable as well? it might be a very stupid question im sorry im new at this thing xd

Yeah, every time you create a new enemy, you create a new metatable
As I remember, it sets metatable into that empty table which then you set into “class” table aka module itself, that empty table can be table with anything, if you apply it to a table with something, then you can use it by “self.Something”

I see, but will having alot of metatables cause lag tho?

1 Like

No, metatables are really optimal if you are doing OOP with lots of objects. Technically they take a little more processing than a regular table, but realistically there’s no difference (computers are REALLY fast).

The real optimization is it can save a lot of memory because you only need 1 function stored for the entire class instead of hundreds of identical functions for each object.

Consider local self = setmetatable(tbl, metatbl)
Whenever I index my object self[i] it will return tbl[i], unless it doesn’t exist it then it returns metatbl[i] instead. So you could…

  • store object-specific properties in self.
  • store class-specific functions in the metatbl.
1 Like

You might be interested in the module I just created called BaseClass.

Could you give me a very simple example of these 2 if you mind. My bad if im asking a lot of question!!11!!

What @greenboo5 described is just what the OOP metatables do in roblox. He is refering to the 2 tables newcar for properties and data, and Car the metatable holding the functions.

Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

Here is an example without metatables and what is technically faster but stores more memories as a function is created for each table. Some devs prefer this but performance wise does not matter noticeable relative to the real performance killers such as spawning 10000 instances and particles.

function Car.new(position, driver, model)
    local newcar = {}

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

newcar.Boost = function(self) --no metatable trickery store functions directly in the property table.
self.Speed += 5
end
    return newcar
end


2 Likes

Here Full tutorial about Metatables

In each table you can create a special table for the player and this is better for controlling the players better (e.g I have a specific player that I must store in Metatable and Checking for his Health)

--This is the main module table. It will hold both the constructor (.new) and any methods (like DoSomethingPlease).
local Module = {}

function Module.new(player: Player)
	--setmetatable(..., Module) sets the metatable of self to Module, allowing method lookups like self:DoSomethingPlease() to work.
	local self = setmetatable({
		player = player,	
		Character = player.Character or player.CharacterAdded:Wait()
		
	}, Module)
	
	print(self) --> shows the object and its contents
	
	--Because Lua will look for missing keys (like methods) in the metatable (which is Module), this is how self gains access to DoSomethingPlease
	--prints the function reference
	print(self:DoSomethingPlease) --> 100 Health
	
	return self
end

function Module.DoSomethingPlease(self): number
	--It returns the current health of the player humanoid
	return self.Character.Humanoid.Health
end

return Module

__index

index__ The indexing access operation table[key] This event happens when table is not a table or when key is not present in table. The metavalue is looked up in the metatable of table. The
:disappointed_relieved: metavalue for this event can be either a function, a table or any value with an __index metavalue

--count will be used to track how many "checks" (missing key accesses) are made
local oldtable = {count = 0}

local table = setmetatable(oldtable, {	
	__index = function(self, _k)
		--The __index function is triggered whenever you try to access a key that doesn’t exist in the table
		self.count = self.count + 1
		return self.count
	end,
})

--These keys (Hi, Testing, 155) do not exist in the table

print(t.Hi)		 -- 1
print(t.Testing) -- 2
print(t[155])	 -- 3

Some example here about index :relaxed:

local tab1 = {foo = 'bar'}
local tab2 = setmetatable({}, {__index = tab1})

print(tab2.foo) --> 'bar'

__newindex

__NewIndex is my favorite and I prefer to use it most of the time because it checks if there is anything new or not and if there is something new it will send it to the function but I heard about it as a hacker loophole I don’t know :thinking:

local Table = setmetatable({}, {	
	
	--Instead of actually storing the key-value pair in the table
	__newindex = function(self, _k, v)
		print(_k) --> Hi or YSH or Hello
		
		--rawset bypasses the __newindex call and stores the key-value pair directly in the table
		rawset(self, _k, v)
	end,
})


Table.Hi 	= 1
Table.Hello = 2
Table.YSH	= 3

print(Table.Hi) --> 1

Calculation operators

local A = {1}
local B = {1}
--both A and B are simple tables holding one numeric value

local vectorMt = {
	--defines what happens when two tables using this metatable are added together +
	__add = function(a, b)
		--the addition (+) operation. If any operand for an addition is not a number, Lua will try to call a metamethod
		
		print("Add")
		return {a[1] + b[1]} --> 1 + 1
	end,
	
	__sub = function(a, b)
		--the subtraction (-) operation. Behavior similar to the addition operation
		
		print("Sub")
		return {a[1] - b[1]} --> 1 - 1
	end,
	
	__mul = function(a, b)
		--the multiplication (*) operation. Behavior similar to the addition operation
		
		print("Mul")
		return {a[1] * b[1]} --> 1 * 1
	end,
	
	__div = function(a, b)
		--the division (/) operation. Behavior similar to the addition operation
		
		print("Div")
		return {a[1] / b[1]} --> 1 / 1
	end,
	
	__mod = function(a, b)
		--the modulo (%) operation. Behavior similar to the addition operation
		
		print("Mod")
		return {a[1] % b[1]} --> 1 % 1
	end,
	
	__pow = function(a, b)
		--the exponentiation (^) operation. Behavior similar to the addition operation
		
		print("Pow")
		return {a[1] ^ b[1]} --> 1 ^ 1
	end,
	
	__idiv = function(a, b)
		--the floor division (//) operation. Behavior similar to the addition operation

		print("idiv")
		return {a[1] // b[1]} --> 1 // 1
	end,
}

setmetatable(A, vectorMt)
setmetatable(B, vectorMt)

print((A + B)[1]) --> 2
print((A - B)[1]) --> 0
print((A * B)[1]) --> 1
print((A / B)[1]) --> 1
print((A % B)[1]) --> 0
print((A ^ B)[1]) --> 1
print((A // B)[1]) --> 1

Bitwise operators (others not important)

  • __bor(a, b): the bitwise OR (|) operation. Behavior similar to the bitwise AND operation.
  • __bxor(a, b): the bitwise exclusive OR (binary ~) operation. Behavior similar to the bitwise AND operation.
  • __bnot(a): the bitwise NOT (unary ~) operation. Behavior similar to the bitwise AND operation.
  • __shl(a, b): the bitwise left shift (<<) operation. Behavior similar to the bitwise AND operation.
  • __shr(a): the bitwise right shift (>>) operation. Behavior similar to the bitwise AND operation.

pairs, ipairs

Same as __pairs() , except for the __ipairs() function

local t = setmetatable({}, {
	__pairs = function(tbl)
		local function iter(array, index)
			index = index + 1
			local value = nil
			if index <= #array then
				value = 1
			end
			if nil ~= value then return index, value end
		end

		return iter, tbl, 0
	end
})

--This stores a key-value pair in the table If you used the default pairs() this would show up in the loop
t.HelloWorld = 1


for i, v in pairs(t) do
	
	--i = HelloWorld
	--v = 1
	print(i, v)
end

__eq

Lua only calls __eq if both tables being compared share the same __eq function (or at least the same metatable) :relaxed:

local A = {1}
local B = {1}

local vectorMt = {
	--the equal (==) operation. Behavior similar to the addition operation
	--except that Lua will try a metamethod only when the values being compared are either both tables or both full userdata and they are not primitively equal
	--The result of the call is always converted to a boolean
	__eq = function(a, b)
		
		print("Eq")
		return {a[1] == b[1]}
	end,
}

setmetatable(A, vectorMt)
setmetatable(B, vectorMt)

print((A == B)) --> True

__It

local A = {1}
local B = {1}

local vectorMt = {
	--he less than (<) operation. Behavior similar to the addition operation
	--except that Lua will try a metamethod only when the values being compared are neither both numbers nor both strings
	--Moreover, the result of the call is always converted to a boolean
	
	__lt = function(a, b)
		
		print("It")
		return {a[1] < b[1]} -- It doesn't matter if it's (<) or (>)
	end,
}

setmetatable(A, vectorMt)
setmetatable(B, vectorMt)

print((A > B)) --> True

__le

By default Lua doesn’t know how to compare tables like table1 <= table2, so it throws an error unless you define a metatable with __le :face_with_spiral_eyes:


local A = {1}
local B = {1}

local vectorMt = {
	__le = function(a, b)
		
		print("le")
		return {a[1] <= b[1]} -- It doesn't matter if it's (<=) or (>=)
	end,
}

setmetatable(A, vectorMt)
setmetatable(B, vectorMt)

print((A >= B)) --> True

for more information here!

I talked about the important things, you can search for more. This is what I could help with.
https://www.lua.org/manual/5.4/manual.html#2.4

2 Likes

You have to see @YSH122331 post

1 Like