I’m creating a OOP script, and for some reason self keeps returning nil. I’m using part of code samples provided by Roblox in CollectionService documentation. I have no idea of what’s wrong, every other topic I found didn’t solve the issue or was a different problem.
This is what is being outputted:
nil
nil
nil
...: Attempt to index nil with 'GetDescendants' [...]
This is some part of the code, I’m not providing the entire code.
local CollectionService = game:GetService('CollectionService')
local TweenService = game:GetService('TweenService')
local Door = {}
Door.__index = Door
Door.TAG_NAME = "Door"
Door.OPEN_TIME = 5
function Door.new(door)
local self = {}
setmetatable(self, Door)
self.door = door
self.debounce = false
self.open = false
self.locked = false
self.clearance = door.Configuration.Clearance.Value
self.combativeAllowed = door.Configuration.CombativeAllowed.Value
self.openPos = {CFrame = door.Union.CFrame * CFrame.new(0,2.5,0)}
self.closePos = {CFrame = door.Union.CFrame}
self.keycard1Connection = door.Reader1.Attachment.ProximityPrompt.Triggered:Connect(function (...)
Door:OnInteract(...)
end)
self.keycard2Connection = door.Reader2.Attachment.ProximityPrompt.Triggered:Connect(function (...)
Door:OnInteract(...)
end)
return self
end
function Door:OnInteract(player)
print(self.debounce) -- nil
print(self.open) -- nil
print(self.clearance) -- nil
for index, inst in pairs (self.door:GetDescendants()) do -- Error! Attempt to index nil with 'GetDescendants'
if inst:IsA('ProximityPrompt') then
inst.Enabled = false
end
end
end
In that case I’m overriding the self global variable. But that’s not the issue though. I changed the variable name to test, and I still with the same issue.
My code is being executed in a ServerScript, not in a ModuleScript. I tested with the code sample provided by Roblox on the CollectionService wiki page, and it works. I really don’t know what is wrong, I just added more properties to the Door class and that just started happening.
-- Code that I already shown is above this part, this is at the same script
local doors = {}
local doorAddedSignal = CollectionService:GetInstanceAddedSignal(Door.TAG_NAME)
local doorRemovedSignal = CollectionService:GetInstanceRemovedSignal(Door.TAG_NAME)
local function onDoorAdded(door)
if door:IsA("Model") then
doors[door] = Door.new(door)
end
end
local function onDoorRemoved(door)
if doors[door] then
doors[door]:Cleanup() -- Don't worry, cleanup is defined
doors[door] = nil
end
end
for _,inst in pairs(CollectionService:GetTagged(Door.TAG_NAME)) do
onDoorAdded(inst)
end
doorAddedSignal:Connect(onDoorAdded)
doorRemovedSignal:Connect(onDoorRemoved)
This part of the code has been entirely taken from Roblox’s code samples, but I changed from “BasePart” to “Model” in onDoorAdded.
I started re-writing the code entirely, and this managed to fix the error I mentioned in that function. I don’t really know what the issue was. I’m still using a good part of the code that was returning errors, but now the same error that I first mentioned jumped into another function.
The function:
function Door:SetOpen(isLocked)
print(self.open) -- nil
if not isLocked then
if self.open then
local tween = TweenService:Create(self.door.Union, tweenInfo, self.closePos) -- Error! attempt to index nil with 'Union'
tween:Play()
tween.Completed:Wait()
self.open = false
wait(0.25)
self.debounce = false
for index, inst in pairs (self.door:GetDescendants()) do
if inst:IsA('ProximityPrompt') then
inst.Enabled = true
end
end
else
local tween = TweenService:Create(self.door.Union, tweenInfo, self.openPos) -- Error! attempt to index nil with 'Union'
tween:Play()
tween.Completed:Wait()
self.open = true
wait(0.25)
self.debounce = false
for index, inst in pairs (self.door:GetDescendants()) do
if inst:IsA('ProximityPrompt') then
inst.Enabled = true
end
end
end
end
end
This function is being called from inside of the OnInteract function as Door:SetOpen(false).
I think I found the original problem.
All instances of Door:OnInteract(...) need to be object:OnInteract(...) or self:OnInteract(...) depending on where they are located of course and what you are doing.
Particularly here, they need to read self:OnInteract(...)
If you call with . it wont pass the table itself as self. You should also call it on the object itself, not the module.
Its also considered good practise to have the constructor disconnected from the functions, to prevent it leaking into the public interface, better encapsulating the object.
local Cat = {}
Cat.__index = Cat
function Cat:Meow()
print("Meow")
end
return function()
local o = setmetatable({}, Cat)
--this is the constructor
return o
end
Did you read my post? self doesn’t define itself automatically, neither is it a global variable. You need to define self in the parameters, change the method declaration character from : to . and call the method using :.
table:method() is syntax sugar for table.method(self), calling a table with : automatically passes self into the interface, where self is the table itself.
There’s an exception to this with Roblox Instances as they have a __namecall metamethod, but that’s not relevant here.
No one has addressed the original problem so I will explain it, and no, it is not about declaring self as a local variable, which is totally fine.
The Door.new function successfully changes the fields’ values of the local self table, but it also sets the metatable Door to it, which has an __index metamethod, meaning that trying to index it will redirect the index to another value (in this case, the metatable Door itself). Since there is no __newindex metamethod, setting new indexes will not be redirected, and will be set for the table itself (self).
So basically, when you are trying to get the field values, you are getting redirected (by __index) to the Door table, when the values are actually in the self table itself.
There is a straightforward fix for this: Avoid using Door as a metatable and do not use a metatable at all. I simply do not see why a metatable is useful in this case. At least start using it once you are completely sure of how metatables work, then there should be no confusion in case there are issues like this.
I believe the OP is basing this off the example provided in Lua’s docs, which doesn’t create a very good constructor setup, the way Roblox does it is much better.
Roblox have it setup so that the functions for each method is only ever declared once, aswell as an __index metamethod pointing to itself.
Code samples extracted from ChatChannel, one of Roblox’s core chat scripts
local methods = {}
methods.__index = methods
function methods:SendSystemMessage(message, extraData)
local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
if not success and err then
print("Error posting message: " ..err)
end
self:InternalAddMessageToHistoryLog(messageObj)
for i, speaker in pairs(self.Speakers) do
speaker:InternalSendSystemMessage(messageObj, self.Name)
end
return messageObj
end
function module.new(vChatService, name, welcomeMessage, channelNameColor)
local obj = setmetatable({}, methods)
obj.ChatService = vChatService
return obj
end
The file has been heavily truncated and likely wont work on it’s own.
This way of doing it is better for two reasons:
You’re not leaking the constructor into the object itself, for better encapsulation (i mentioned this in a previous post)
The functions are only ever declared once, and then each table references the same memory pointer.
The issue with the OP was that they were calling the Door table instead of the object itself, as again, metioned above,
There is only one good reason to use metatables at all: Exposing it as public API, making it easier for users. I do not think that is the case for the OP.
Change Door:OnInteract to self:OnInteract and put it inside the Door.new function just before the return self line.
That’s because the object you’re creating and returning with Door.new constructor is not a Door object but a new object assigned to variable self in your constructor function. In connection with that, self in Door:OnInteract function will point to Door, not the new object you created.