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.
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.
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
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
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
}
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
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