Class metatable index is acting very weird

Here is my module script:

function Module:NewBoid(Origin, Environment)
	local NewBoid = setmetatable(Classes.Boid, {})
	NewBoid.Environment = Environment
	NewBoid.Body = Templates.Body:Clone();
	NewBoid.Speed = 1;
	NewBoid.Range = 15;
	NewBoid.Fov = 0.5;
	NewBoid.Enabled = true;
	
	-- Body
	
	NewBoid.Body.Parent = NewBoid.Environment
	NewBoid.Body.Position = Origin
	
	
	return NewBoid
end

function Classes.Boid:Update()
	local LookDirection = self.Body.CFrame.LookVector
	local BoidsInRange = {}
	
	for BoidI, Boid in pairs(self.Environment:GetChildren()) do
		if Boid:IsA("BasePart") then
			local Sub = Boid.Position - self.Body.Position
			local Distance = Sub.Magnitude
			local LookDot = LookDirection:Dot(Boid.CFrame.LookVector)
			
			if Distance <= self.Range and LookDot > self.Fov then
				table.insert(BoidsInRange, Boid)
			end
		end
	end
	
	for BoidI, Boid in pairs(BoidsInRange) do
		local Sub = Boid.Position - self.Body.Position
		local Distance = Sub.Magnitude
		local Ratio = math.clamp(Distance / 4, 0, 1)
		
		LookDirection -= Ratio * Sub
	end
	
	self.Body.Position += (LookDirection / 4) * self.Speed
	self.Body.CFrame = CFrame.lookAt(self.Body.Position, self.Body.Position + LookDirection)
end

function Classes.Boid:Destroy()
	self.Body:Destroy();
	self.Range = nil;
	self.Enabled = nil;
	
	self = nil
end


return Module

If you can’t tell, I am trying to simulate boids/birds, It’s not finished though.

I think you got the parameters mixed up. The first parameter should be the table you are setting the metatable of, and the second parameter is what to set the metatable to.

local self = setmetatable({}, classHere)

Pretty sure I tried this before and it worked but I couldn’t use methods.

Well, it’s definitely the right way to use setmetatable(). Did your class inherit methods or not? Can you send an example of code you used?

you’re setting the metatable the wrong way round, it should be table you want to give a metatable to, and then the metatable you are going to give to that table

I am not sure what you mean.

Also this is my full Module Script:

local BaseModule = script

local Module = {}

local Classes = {
	Boid = {};
}


-- Services

local WorkService = game:GetService("Workspace")

local RunService = game:GetService("RunService")


-- Local

local Templates = BaseModule.Templates


-- Boid Class

function Module:NewBoid(Origin, Environment)
	local NewBoid = setmetatable(Classes.Boid, {})
	NewBoid.Environment = Environment
	NewBoid.Body = Templates.Body:Clone();
	NewBoid.Speed = 1;
	NewBoid.Range = 15;
	NewBoid.Fov = 0.5;
	NewBoid.Enabled = true;
	
	-- Body
	
	NewBoid.Body.Parent = NewBoid.Environment
	NewBoid.Body.Position = Origin
	
	
	return NewBoid
end

function Classes.Boid:Update()
	local LookDirection = self.Body.CFrame.LookVector
	local BoidsInRange = {}
	
	for BoidI, Boid in pairs(self.Environment:GetChildren()) do
		if Boid:IsA("BasePart") then
			local Sub = Boid.Position - self.Body.Position
			local Distance = Sub.Magnitude
			local LookDot = LookDirection:Dot(Boid.CFrame.LookVector)
			
			if Distance <= self.Range and LookDot > self.Fov then
				table.insert(BoidsInRange, Boid)
			end
		end
	end
	
	for BoidI, Boid in pairs(BoidsInRange) do
		local Sub = Boid.Position - self.Body.Position
		local Distance = Sub.Magnitude
		local Ratio = math.clamp(Distance / 4, 0, 1)
		
		LookDirection -= Ratio * Sub
	end
	
	self.Body.Position += (LookDirection / 4) * self.Speed
	self.Body.CFrame = CFrame.lookAt(self.Body.Position, self.Body.Position + LookDirection)
end

function Classes.Boid:Destroy()
	self.Body:Destroy();
	self.Range = nil;
	self.Enabled = nil;
	
	self = nil
end


return Module

Do you mean something like:

setmetatable(Class, Class)

I tried that and it didn’t work.

I don’t see you using any metamethods, so what do you mean by “methods don’t work”? You might need to define subprograms within the class:

local class = {
    ["Function1"] = function()
    end
}

but you also only have one class, so I don’t understand your Classes table at the top. Seperate classes should use seperate modules.

local boid = {}
boid.__index = boid
boid.__newindex = function(tbl, key, value)
    if rawlen(tbl) < 15 then
        rawset(tbl, key, value)
    else
        warn("New indexes are not allowed.")
    end
end

function Boid:MethodOne()
    --do stuff
end

Also, the correct syntax of setmetatable is as follows:

  • Parameter One is the table having its metatable set. This parameter is also returned.
  • Parameter Two is the table to assign as metatable to Parameter One. Parameter One will inherit everything from it, including metamethods.
local something = {}
function something.hi()
end

local self = setmetatable({}, something)
print(self) --> {["hi"] = "function"}
1 Like

Sorry, I really don’t understand metatables very well, I am not sure what meta methods are.

But I am pretty sure that I am using meta methods in this, like for example I have a method called “Update” that updates the boid’s position, isn’t that a method?

I know I am only using one class but the classes table just makes things more organized, also I don’t understand why every class has to be in a different module, can you explain please?

I tried searching up about metatables and what they do but all I saw was that meta tables are just fancy tables which was very confusing.

  • The classes table is pointless if there is only one class. All your other classes (not Boid) should be in different modules - the whole point in an Object-Oriented approach (which we are doing) is to simplify and declutter code. Multiple classes per script means the code becomes more cluttered.

Metamethods are methods that can change the way tables behave. Here is a post in Community Tutorials explaining more about it:
Metatables and Metamethods - Community Tutorials




Try just using one class per module, like this:

local module = {}

function module.MethodOne()
end

function module.MethodTwo()

function module.new()
    local self = setmetatable({}, module)
end
1 Like

I updated my code to this:

local BaseModule = script

local Module = {}

-- Services

local WorkService = game:GetService("Workspace")

local RunService = game:GetService("RunService")


-- Local

local Templates = BaseModule.Templates


-- Boid Module

function Module:New(Origin, Environment)
	local NewBoid = setmetatable({}, Module)
	NewBoid.__index = NewBoid
	NewBoid.Environment = Environment
	NewBoid.Body = Templates.Body:Clone();
	NewBoid.Speed = 1;
	NewBoid.Range = 15;
	NewBoid.Fov = 0.5;
	NewBoid.Enabled = true;

	-- Body

	NewBoid.Body.Parent = NewBoid.Environment
	NewBoid.Body.Position = Origin


	return NewBoid
end

function Module.Update(self)
	local LookDirection = self.Body.CFrame.LookVector
	local BoidsInRange = {}

	for BoidI, Boid in pairs(self.Environment:GetChildren()) do
		if Boid:IsA("BasePart") then
			local Sub = Boid.Position - self.Body.Position
			local Distance = Sub.Magnitude
			local LookDot = LookDirection:Dot(Boid.CFrame.LookVector)

			if Distance <= self.Range and LookDot > self.Fov then
				table.insert(BoidsInRange, Boid)
			end
		end
	end

	for BoidI, Boid in pairs(BoidsInRange) do
		local Sub = Boid.Position - self.Body.Position
		local Distance = Sub.Magnitude
		local Ratio = math.clamp(Distance / 4, 0, 1)

		LookDirection -= Ratio * Sub
	end

	self.Body.Position += (LookDirection / 4) * self.Speed
	self.Body.CFrame = CFrame.lookAt(self.Body.Position, self.Body.Position + LookDirection)
end

function Module.Destroy(self)
	self.Body:Destroy();
	self.Range = nil;
	self.Enabled = nil;

	self = nil
end


return Module

But it gives me an error saying:

attempt to call missing method 'Update' of table

I think it’s to do with your notation. Colon notation implicitely defines self. You do not need it for the New function.

You should also set NewBoid.__index to Module, but by simply doing

Module.__index = Module

at the top of your script it will inherit the __index metamethod.

__index is a table Lua will search for a missing method when a missing method is called. Here’s more information about self and notation:

What is self and how can I use it? - Help and Feedback / Scripting Support - Developer Forum | Roblox

1 Like

My code is working now, Thanks for helping me.
I don’t understand a lot of things but it’s working which is the important part lol.

1 Like

It’s also very important to understand them. You should try to learn about them.

1 Like

I am aware this post is solved but, what is the difference between using self and local?

1 Like

@SirKhalidBlox

From what I know, self is a variable that returns the table of a function.

For example:

local TestTable = {}

TestTable.Value = 5

function TestTable:Print() -- The self variable in this function is TestTable.
    print(self.Value) -- Should print 5.
end
1 Like

That is true but what stops us from just doing this

local value = TestTable.Value

function TestTable:Print() 
    print(value)
end

Because the value variable is a property of the TestTable, It just makes things more readable I guess, Here is a better example:

local Car = {}

Car.Name = "Lamborghini"
Car.Speed = 50
Car.Passenger = game.Players.zeyan200_gotdeleted

function Car:Activate()
	-- Code that activates the car.
end

function Car:ToggleLights()
	-- Code that toggles the lights of the car on and off.
end

function Car:Boost()
	-- Code that gives the car a speed boost.
end

If we just did:

local Name = "Lamborghini"
local Speed = 50
local Passenger = game.Players.zeyan200_gotdeleted

It wouldn’t really make much sense because the speed variable is for this specific car,
Sorry I am not the best at explaining, I am still new to classes too.

1 Like

self is a variable used in OOP and OOP related programming to refer to the object you are currently using, since in OOP everything related to the object is in the object. You can refer to the post I made linking someone else’s explanation of it.

2 Likes