I mainly use OOP for the majority of my systems, though I am a bit stumped on something. I’m currently making an entity system for a project of mine and I wanna be able to handle everything in a single entity, which OOP has made easy for me. But I have found a problem.
local ActionHandler = {}
ActionHandler.__index = ActionHandler
type MoveDataType = {
Context : Context,
MoveType : MoveType,
Moveset : string,
Function: Function,
}
function ActionHandler.new(mainData: {})
local self = setmetatable({}, ActionHandler)
self.Actions = {}
self.Connections = {}
self.Character = mainData.Character
self._Signal = mainData.Signal
print(mainData)
return self
end
function ActionHandler:Add(Name, ActionInfo: MoveDataType, Time : number, Data: {}, CheckInfo: {}, Info: {})
assert(ActionInfo, "ActionHandler: No MoveInfo supplied on this Move.")
self.Actions[Name] = {
["Type"] = ActionInfo.Context,
["Style"] = ActionInfo.MoveType,
["Moveset"] = ActionInfo.MoveSet,
["Function"] = ActionInfo.Function,
["Timer"] = nil,
["Time"] = Time,
["CheckInfo"] = CheckInfo,
["Info"] = Info,
["Cancel"] = Data.Cancel,
["Run"] = Data.Run,
["Connection"] = nil
}
self.Actions[Name].Cancel = Data.Cancel
self.Actions[Name].Run = Data.Run
self:Run(Name)
end
function ActionHandler:Run(Name)
if not self.Actions[Name] then
warn("Action doesn't exist, add it first.")
return;
end
if self.Actions[Name].Connection then
warn("Action already began running.")
return;
end
if self.Actions[Name] then
self.Actions[Name].Run(self.Character, self)
end
if self.Actions[Name].Time == nil or self.Actions[Name].Time == 0 then
return;
end
self.Actions[Name].Timer = TimerModule.new(Name, self.Time, function()
warn("Timer time is official over.")
self:Cancel(Name)
end)
local selectedAction = self:GetAction(Name)
selectedAction.Connection = RunService.Heartbeat:Connect(function()
end)
end
function ActionHandler:GetAction(Name)
if self.Actions[Name] then
return self.Actions[Name]
end
end
function ActionHandler:Cancel(Name, stunValue)
if self.Actions[Name] then
self.Actions[Name].Cancel(self.Character, self)
self.Actions[Name].Connection:Disconnect()
end
end
function ActionHandler:RunAllActions()
for i,v in pairs(self.Actions) do
self:Run(i)
end
end
function ActionHandler:DestroyAction(Name)
if self.Actions[Name] then
self:Cancel(Name)
self.Actions[Name] = nil
end
end
function ActionHandler:StopAllActions()
for i,v in pairs(self.Actions) do
self:Cancel(i)
end
end
function ActionHandler:DestroyAllActions()
for i,v in pairs(self.Actions) do
self:DestroyAction(i)
end
end
function ActionHandler:CustomActionHandler(s: "Cancel" | "Destroy", Type: "Context" | "MoveType" | "MoveSet" | "Function" | {}, Value)
if typeof(s) == "string" then
for i,v in pairs(self.Actions) do
if s == "Cancel" then
if v[Type] == Value then
self:Cancel(i)
end
elseif s == "Destroy" then
if v[Type] == Value then
self:DestroyAction(i)
end
end
end
else
for _,T in pairs(Type) do
for ind,val in pairs(self.Actions) do
if s == "Cancel" then
if val[T] == Value then
self:Cancel(ind)
end
elseif s == "Destroy" then
if val[T] == Value then
self:DestroyAction(ind)
end
end
end
end
end
end
return ActionHandler
-Base class
function PhysicsCharacterController:GetComponent(nameOfComponentModule)
return self._MovementComponents[nameOfComponentModule]
end
function Running.new(data : PhysicsCharacterController)
local self = setmetatable({}, Running)
local model = data._Model
function PhysicsCharacterController:AddComponent(componentModuleOrString : ModuleScript?)
local componentModule : ModuleScript
if typeof(componentModuleOrString) == "string" then
componentModule = script:FindFirstChild(componentModuleOrString, true)
assert(componentModule, "Component module cannot be found! Please check Components folder")
end
if typeof(componentModuleOrString) == "Instance" then
componentModule = componentModuleOrString
end
local componentInitializer = require(componentModule)
local component = componentInitializer.new(self)
component.Name = componentModule.Name
component.ShouldUpdate = true
self._MovementComponents[componentModule.Name] = component
end
You’re trying to access elements from a table while the elements are being created. This is essentially what you’re doing:
-- Both will print an empty table
local function A(Input)
print("A", Input)
return 1
end
local function B(Input)
print("B", Input)
return 2
end
local Table = {}
Table.Stuff = {
MyA = A(Table),
MyB = B(Table)
}
You will need to add your elements manually in the order they depend on each other for them to work.
self._handlers = {}
-- States comes first
self._handlers.States = require(script.Handlers:FindFirstChild("States")).new(self)
-- Actions needs States
self._handlers.Actions = require(script.Handlers:FindFirstChild("Actions")).new(self)
-- etc.
Not to look selfish and not insult goodsignal but its kinda outdated now.
If you are interested you could use NormalSignal made by me instead, which is a fork of another signal module (don’t look this rabbit hole or you will regret)
No disrespect to GoodSignal once again it gave good inspiration and were first to start signal modules.
So to clear some confusion as to how metatable OOP (the one you are using) works:
You have a table module that contains a bunch of values (often function but don’t have to be only them specifically) and it has __index (which is a metatable) Metatables | Documentation - Roblox Creator Hub set to itself
For now it doesn’t mean anything to us really:
Now we have a function .new in this module (table) and when we call it, it creates this table,assigns metatable “module” to it and returns that.
setmetatable() ALSO returns the same exact table we passed in first argument so we are not getting any “new” table or something
What heppens behind the scenes?
When we index value in our newly created class and it is not here then it looks up for its metatable and finds value in metatable with the same key
In your example lets say you index:
local module = {}
module.__index=module
function module.new()
return setmetatable({},module)
end
function module:Run()
end
local hi = module.new()
hi:Run()
-- checks does hi has value "Run"?
-- nope, hi doesn't have value "Run":
-- searching value "Run" in hi's metatable (module)!
-- Found!
-- returns module.Run
Tip:
if you are not using any methods from class inside the constructor would be more optimized
As since table without metatable .index doesn’t have overhead for indexing anything and it does gain metatable in the end of constructor only as you may see!
function module.new()
local self = {}
return setmetatable(self,module)
end