arrayslualove2d

attempt to call method 'draw_self' (a nil value) error - when trying to call function of a table within an array


I just started learning Lua and LOVE2D, I'm trying to make text input boxes, I've got 4 instances within an array and I'm trying to call their draw function within the love.draw()

TextBox = {
    x = 0,
    y = 0,
    width = 256,
    height = 64,
    text = '',
    active = false,
    colors = {
        background = { 186, 163, 182, 220 },
        text = { 20, 20, 20, 255 },
        border = { 48, 44, 50, 230 },
        activeborder = { 236, 234, 115, 240 }
    }
}

function TextBox:init(o, x, y)
    local o = {}
    o.parent = self
    o.x = x
    o.y = y
    return o
end

function TextBox:draw_self()
    -- Draw the box
    love.graphics.setColor(unpack(TextBox.colors.background))
    love.graphics.rectangle('fill',
        textBoxArray[i].x, textBoxArray[i].y,
        TextBox.width, TextBox.height)
    ...
end

main.lua :

function love.load()    
    ...

    textBoxArray = {}
    for i=1,4 do
        ... -- Declaring xx and yy here
        textBoxArray[i] = TextBox:init(nil, 40+xx, 40+yy)
    end
end

function love.draw()
    for i=1,4 do
        textBoxArray[i]:draw_self()
    end    
end

I get an error when I try to call :draw_self(), :init() works just fine and I couldn't figure out what the issue might be. What's the proper way to call a method when the instance is within an array?


Solution

  • What's the proper way to call a method when the instance is within an array?

    textBoxArray[i]:draw_self() is the correct way to call a "method" if textBoxArray[i] has been set up properly: obj:method(...) is (roughly) just syntactic sugar for obj.method(obj, ...). That is, indexing obj with the string "method" must return a method which takes obj as the first parameter.

    This isn't the case though; your constructor doesn't set a metatable. Let's take a look:

    function TextBox:init(o, x, y)
        local o = {}
        o.parent = self
        o.x = x
        o.y = y
        return o
    end
    

    After this, o has fields x, y and parent. Accessing any other field will yield nil. In particular, textBoxArray[i].draw_self will be nil for all i. You're thus trying to call a nil value, which throws an error. o.parent doesn't have any special effect (assuming you're not using an OOP library). Instead, the standard pattern is to do self.__index = self followed by setmetatable(o, self). (There are various ways to vary this pattern. For example you might want to separate the metatable and the class table, and you might not want to do an "unnecessary" self.__index = self in every constructor call. A nice property of this "standard pattern" is that it makes inheritance easy though.)

    Side note: The unused o parameter doesn't make sense. If you want to allow passing an existing table, use o = o or {} rather than local o = {}. Otherwise, just remove the parameter.

    Let's take a look at draw_self now:

    function TextBox:draw_self()
        -- Draw the box
        love.graphics.setColor(unpack(TextBox.colors.background))
        love.graphics.rectangle('fill',
            textBoxArray[i].x, textBoxArray[i].y,
            TextBox.width, TextBox.height)
        ...
    end
    

    The "method" is called draw_self, yet you're operating on the global TextBox table except for the usage of textBoxArray[i]. Both of these should be completely replaced with usage of self. Currently you're using method syntactic sugar (function TextBox:init(...) is equivalent to TextBox.init = function(self, ...) - it adds an implicit self parameter) here despite not using self at all.

    Your love.load is fine. Your love.init is also fine except for the o quirk; depending on how you refactor your constructor, you'll probably want to remove the unused nil value.