Need help with OOP!

I’m working on a character model, I have put together a base class, but when I create a sub-class, I am unable to call the methods from the subclass

BASE CLASS:

--[[

- CHARACTER ATRIBUTES:
	- Character
	- Species
	- Level
	- EXP
	- Stamina
	- MaxStamina
	- Oxygen
	- Max Oxygen

]]

local Character = {}
Character.__index = Character

function Character.new(species)
	local self = setmetatable({}, Character)
	
	self.Charcater = nil
	self.Species = species
	self.Level = 5
	self.Exp = 0
	self.MaxExp = 25
	self.MaxExpRate = 25
	self.Stamina = 100
	self.MaxStamina = 100
	self.Oxygen = 100
	self.MaxOxygen = 100
	
	return self
end

function Character:Connect(model)
	self.Character = model
	
	self._OnDied = self.Character.Humanoid.Died:Connect(function()
		self:Died()
	end)
end

function Character:Died()
	self:Destroy()
end

function Character:IncreaseExp()
	self.Exp += 5

	while self.Exp >= self.MaxExp do
		self:LevelUp()
		self.MaxExp = math.floor(self.MaxExp * 1.5)
	end
end

function Character:LevelUp()
	self.Level += 5
end

return Character

SUB-CLASS:

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local CHARACTER_CLASS = require(ReplicatedStorage.MODULES.CLASSES.CHARACTER)

local MermaidClass = {}
MermaidClass.__index = MermaidClass
setmetatable(MermaidClass, CHARACTER_CLASS)

function MermaidClass.new()	
	local self = setmetatable(CHARACTER_CLASS.new(), MermaidClass)
	
	return self
end

function Mermaid:IncreaseOxygen()
	---stuff
end

return MermaidClass

Real solution: don’t use inheritance and OOP inside a language that does not support/optimize either.

You should think instead of using an ECS-like system.

local Players = game:GetService("Players")
local levels:{[Player]:number} = {}

Players.PlayerAdded:Connect(function(plr):()
	--datastore thing probably
	local dataStoreValue = nil
	levels[plr] = dataStoreValue or 1
end)
Players.PlayerRemoving:Connect(function(plr):()
	--cleaning up and saving to datastores idk
	levels[plr] = nil
end)
1 Like

I’m not looking to set this up as a data store type system, it’s to build my characters and provide functions for them to be able to customize them, using OOP.

Luau does not support true OOP. The closest you can get is closures or tables with/without metatables, but they’re fragile, verbose, and scale horribly - you’ll go insane before you get anything complex working reliably, and even then it all will be a waste because of the runtime overhead they have and the compiler never attempting to unwrap them into something sane.

An Entity-Component-System pattern fits Luau far better. It’s basically functional programming with state, which aligns perfectly with the language’s design. You get modularity, customization, and performance without fighting metatables or inheritance chains.

1 Like

I appreciate the advice, but I posted this in hope’s to receive a solution to my problem, not a paragraph on why you don’t like Roblox’s OOP system, I have been experimenting with this format from a tutorial I saw for some time now trying to learn different ways to use it so I don’t think they would’ve posted it if it was defective, I don’t mean for this to come off as rude but I am not looking for a lecture here, I’m looking for a direct resolve to my issue.

3 Likes

Correct me if I’m wrong, but I believe self will be nil in your .new() functions. self only exists in methods using the semicolon operator (:). self is actually syntax sugar for just passing it in the first argument of the function.

I think the right option is to do this:

function Character.new(species)
	local character = setmetatable({}, Character)
	
	character.Charcater = nil
	character.Species = species
	character.Level = 5
	character.Exp = 0
	character.MaxExp = 25
	character.MaxExpRate = 25
	character.Stamina = 100
	character.MaxStamina = 100
	character.Oxygen = 100
	character.MaxOxygen = 100
	
	return character
end

There is no such thing as a “Roblox OOP”, By “Roblox OOP,” you may refer to instance API Roblox lets you call through Luau.
The thing you might want to refer to is called Prototype OOP, or, as I like to call it, “cope OOP”.
If you are going to write proto OOP, then at least write it properly.

--[[

- CHARACTER ATRIBUTES:
	- Character
	- Species
	- Level
	- EXP
	- Stamina
	- MaxStamina
	- Oxygen
	- Max Oxygen

]]

local Character = {}
Character.__index = Character


function Character:Connect(model)
	self.Character = model

	self._OnDied = self.Character.Humanoid.Died:Connect(function()
		self:Died()
	end)
end

function Character:Died()
	self:Destroy()
end

function Character:IncreaseExp()
	self.Exp += 5

	while self.Exp >= self.MaxExp do
		self:LevelUp()
		self.MaxExp = math.floor(self.MaxExp * 1.5)
	end
end

function Character:LevelUp()
	self.Level += 5
end

return {Character=Character,new=function():()
	return setmetatable({
	self.Charcater = nil,
	self.Species = species,
	self.Level = 5,
	self.Exp = 0,
	self.MaxExp = 25,
	self.MaxExpRate = 25,
	self.Stamina = 100,
	self.MaxStamina = 100,
	self.Oxygen = 100,
	self.MaxOxygen = 100}, Character)
end}
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local CHARACTER_CLASS = require(ReplicatedStorage.MODULES.CLASSES.CHARACTER)

local MermaidClass = {}
MermaidClass.__index = MermaidClass
setmetatable(MermaidClass, CHARACTER_CLASS.Character)



function MermaidClass:IncreaseOxygen()
	---stuff
end

return function():()
	local self = setmetatable(CHARACTER_CLASS.new(), MermaidClass)

	return self
end

You create method for non existing variable in MermaidClass

2 Likes

setmetatable() simply specifies a metamethod, and if you use this function while the array has another metamethod, the metamethod will be overridden.

for example :

local ThisTable = {}

setmetatable(ThisTable, {__index = function() return "hi" end})
print(ThisTable.b)

setmetatable(ThisTable, {})

print(ThisTable.b)

output :
hi
nil

__index accepts a table as an argument, and it is much easier for VM to handle rather than having an entire function bound to it.

Also, it’s not an array; it’s a dictionary (or a plain table).

local ThisTable = {
	["hi"] = 5
}

local TestTable = {
	a = 5
}

setmetatable(ThisTable, {__index = TestTable})
print(ThisTable.a)

setmetatable(ThisTable, {})

print(ThisTable.a)

same work

For VM?
No not the same work at all.
This one is much, much cheaper for VM to handle.

i dont understand your says.

test your output and alarm me.

You just misspelled the class name:
function Mermaid:IncreaseOxygen()function MermaidClass:IncreaseOxygen()
Everything else looks good

1 Like

Is the ECS system implemented using Dictionaries?
I havent fully done OOP on roblox, but it feels a bit difficult to do, languages like
Java has a really nice OOP, but lua feels more difficult to do OOP.

Looks like you just misspelt the name of your class. I tested your code and everything works pretty fine.

Yea I did see that, Idk how I missed it, I also noticed the functions do work but for some reason they just don’t pop up in the list when typing it out

1 Like

That not fair i mentioned it first.
IT SHOULD’VE BEEN ME THAT NOT FAIR

1 Like

Because you can’t do OOP in Luau at all, there are no opcodes for true OOP, nor are there any compiler instructions.


That also depends; ECS is usually implemented either via an array with a unique number or via a dictionary.
Realistically, it’s always dictionaries and maybe buffers and even rarer arrays.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.