Why Does My Class Constructor Init Method Never Get Called Right? (OOP Inheritance)

I would say just use ECS, but that has its own problems mostly just organizational (You still end up with a lot of scripts and components to organize and debug).

Problem 1, the base.new() functions calles init first, then the metatable change happens.

Solution: You can create a factory module to autoinitialize your objects for you. Not sure if this is the best but its my most immediate idea.

local autoInit(object)
local obj = object.new()
obj:Init()
end

return autoInit

Problem 2 seems to be the same root problem as problem 1 with the init function inside the constructor.

1 Like

as mentioned above, luau is a language where I would recommend composition over inheritance. Architectural patterns that benefit from Data Oriented Design such as ECS are a good fit for it, specially since metatables are our way to imitate class inheritance functionality, given we don’t actually have classes.

1 Like

I dont understand the point of it in first place?
Why cant you just initialize class inside its constructor?
Anyways metatable OOP is outdated consider using this: Strictly typed <<Object Oriented Programming>> (Better than metatable OOP)
Its more optimized

reason of not initialize class inside constructor because i want override init in my subclasses.
thanks but im not looking for optimization and classic module provides me a very good class i can get same results with typescript classes my 2. problem isn’t just about lua classes its a general oop question so im not looking for classes without metatables

2 Likes

The 2. problem isn’t just about luau classes, in my codebase i actually don’t know how can i use composition over inheritance


Just for example;
Let’s say i have/need really extensible classes for bullets
Example inheritance chains:

Base -> Hitscan -> Laser
Base -> Projectile -> Bomb
...

How can i use composition over inheritance or a data oriented design on here

This guy got a good module that solves this problem really well:

I’d say you should give it a shot.

It’s not fixes 2. problem, i already using classic for 1. problem

why dont you just have same method check what class do you use and apply initiation acordingly?
Since its metatable OOP it will end up as a better solution anyways.

The overwriting and constructor mechanism of the module I mentioned is far cleaner. I suggest giving the constructor mechanism of the 2.0 in the documentation a try. When the first constructor (base class) is called, the values in the child class already exist and has been overwritten, therefore the base class constructor will always have the object with its values already updated.

I was tried class++ like 2 days ago and results was same i dont want achieve same thing with more complexity i already have solutions i just looking for simpler/cleaner ones the 2. problem also happens in typescript classes something in logic is wrong but i can’t figure it out

Probably researching self.type (or smth for identify class) would just add more complexity than task.defer as i think

I say you should give the new version a go then. It’s not complex at all. It solves what you’re trying to do in a more sequential order, without depending on task.defer(). Though, your choice.

I think class++ just adds limitations and i dont know is it stable or not, while the classic module solves my problem with a single metamethod and one logic trick and classic module is made 11 year ago and a common stable utility for lua scripters. If I had to choose, I’d go with dthecoolest’s solution over task.defer

Your use case is actually a really good one for ECS.

In this example, what if you want to create a hitscan laser that also explodes on impact like a bomb? Under inheritance this complicates things.

Under ECS however, classes only hold data (properties) and all behavior is handled externally through “systems”

local bullet = Entity.new()
Entity.addComponent(bullet, DamageComponent.new(5)) --now deals 5 damage
Entity.addComponent(bullet, BombComponent.new(15)) --now explodes on a radius of 15
Entity.addComponent(bullet, HitScanComponent.new(50)) --now acts under hitscan

BulletSystem.fireBullet(bullet) --performs behavior based on components
code implementation example
--entity.luau
function newEntity()
	local e = {
		components = {}
	}
	return e
end

function addComponent(e, c: {[string]: any} & {type: string})
	e.components[c.type] = c
	return c
end

function getComponent(e: Entity, type: string)
	return e.components[type]
end

return {
	new = newEntity,
	getComponent = getComponent,
	addComponent = addComponent,
}
--HitscanComponent.luau
return {
	new = function(range: number)
		return {
			type = 'Hitscan',
			range = range
		}
	end
}
--ProjectileComponent.luau
return {
	new = function(velocity: number)
		return {
			type = 'Projectile',
			velocity = velocity
		}
	end
}
--BulletSystem.luau
function fireBullet(e: Entity)
	if e.components['Projectile'] then
		--create a projectile moving in Projectile.Velocity direction
	elseif e.components['Hitscan'] then
		--create a raycast using Hitscan.range
	end
end

function bulletImpact(e: Entity)
	if e.components['Bomb'] then
		--blow up in a radius and apply damage from components['Damage']
	end
end

return {
	fireBullet = fireBullet,
	bulletImpact = bulletImpact
}
2 Likes

I probably will implement :Hit method of bomb to laser just it (for large codebase i would create behavior modules for store methods waiting for get implemented by classes)

I actually was used this logic but this part of system i cant understand because a projectile needs store fields and lifecycles in this logic and this pushed me to use OOP

these fields are found within the entity’s components, that’s where you may find the “state” you wish to modify/update

1 Like

Thanks i understand now. Also you mentioned composition over inheritance do you have any ideas on how to use composition over inheritance on here?

^ this process is called composition

see how bullet doesn’t truly inherit from anything? but rather the object is built from the sum of its parts?

This logic is good but about extensibility i dont know really. as an example what will happen if a bomb and some other components should move different including projectile, or bomb should move a part of bomb model on every projectile step/update

From what you are saying, you may not be applying separation of concerns here. In classic inheritance OOP you are led to see behavior and properties as parts a of a whole, an “Object”.

I think a better name for “BombComponent” would instead be “ExplodesComponent”, by which I mean that this component is only concerned with providing information for the bullet to explode on impact through BulletSystem.bulletImpact().

The Bomb component should NOT be concerned whatsoever with the handling of a model or updating a projectile. For that purpose you should consider a ModelComponent and/or OnRenderStepped Component.

I think this could be a good watch to unlearn inheritance based thinking into more data-oriented ECS architectural thinking

1 Like