Arrow = nil error

I have a question about a confusing bug in my game. I’ve been learning how to use classes but I dont fully understand the syntax yet.

if arrow:missCheck() == true then
    missScore = missScore + 1
end

This is in main and calls the function missCheck() in my arrow class which is below

function Arrow:missCheck()
    local ret_val = false
    
         -- Checks if the arrow missed
    if arrowX >= WIDTH and arrowFlying == true then
        arrow:reset()
        ret_val = true
    end
    return ret_val
end

When I run it it says (when it try and call missCheck) that arrow doesn’t exist. In setup I create an arrow with

arrow = Arrow()

I dont know if I included enough code. Please tell me if I havent and I’ll be sure to include it.

I think the problem was that it just wasn’t inside of a function, so they din’t know what an arrow was.

Here’s my main and arrow class if you want to see them.

The only problem is that when an arrow hits a ballon, it no longer knows what arrowX is.

function setup() 
    displayMode(FULLSCREEN)
    supportedOrientations(LANDSCAPE_ANY)
  
    arrow = Arrow()
    
    ballon1 = Ballon(math.random(120,WIDTH - 200))
    ballon2 = Ballon(math.random(120,WIDTH - 200))
    
    -- Random Y starting place
    
    last_points = 0
    best_points = 0
    
    MENU = 0
    PLAYING = 1
    SCORING = 2
    
    resetGame()
    
end


function resetGame()
    gameState = MENU
    missScore = 0
    points = 0
    
end
    
    
function draw()

   if gameState == MENU then
        background(0, 0, 0, 255)
        
        pushStyle()
        font("AmericanTypewriter")
        fontSize(100)
        fill(187, 25, 27, 255)
        text("Ballon Shoot",WIDTH/2,HEIGHT/2)
        popStyle()
        
        pushStyle()
        fontSize(50)
        fill(195, 92, 20, 255)
        text("Tap to begin (:",WIDTH/2,HEIGHT/2 - 100)    
        popStyle()
        
        pushStyle()
        fontSize(20)
        fill(195, 92, 20, 255)
        text("Last Score: " .. last_points, 0 + 100, HEIGHT - 20)    
        popStyle()
        
        pushStyle()
        fontSize(20)
        fill(195, 92, 20, 255)
        text("Best Score: " .. best_points, WIDTH - 100, HEIGHT - 20)    
        popStyle()

    elseif gameState == PLAYING then
        background(35, 91, 31, 255)
    
        arrow:draw()
        
        ballon1:draw()
        ballon1:update()
        ballon2:draw()
        ballon2:update()
        
        ballon1:speedCheck()
        hittCheck()
        endCheck()
        arrow:fly()
        showPoints()
        showMisses()
    
    elseif gameState == SCORING then
        background(0, 0, 0, 255)
        pushStyle()
        fill(166, 156, 204, 255)
        font("Arial-ItalicMT")
        fontSize(100)
        text("Your Final Score:"..points, WIDTH/2,HEIGHT/2)
        popStyle()
    end
    
    if arrow:missCheck() == true then
        missScore = missScore + 1
    end

end   -- end draw



-- Shows points
function showPoints()
    pushStyle()
    fontSize(48)
    fill(255, 255, 255, 255)
    font("Arial-BoldMT")
    text("Points:"..points,0 + 100,HEIGHT - 50)
    popStyle()
end

-- Shows misses
function showMisses()
    pushStyle()
    fontSize(48)
    fill(255, 255, 255, 255)
    font("AmericanTypewriter-Light")
    text("Misses:"..missScore,WIDTH - 100,HEIGHT - 50)
    popStyle()
end



function hittCheck()
       -- Checks to see if the ballon has been hit
    
    if arrow:getFlying() then
        if ballon1:hittCheck( arrow:getPos() ) or 
           ballon2:hittCheck( arrow:getPos() ) then
            sound(SOUND_EXPLODE, 14044)
            print("Hitt!")
            
            points = points + 1
            arrow:reset()
        end
    end

end


function endCheck()     
           
    -- Checks to see if the miss count is to high
    if missScore >= 3 then   
        gameState = SCORING
        last_points = points
        
        if points > best_points then
            best_points = points
        end
    end 
end  
          
function touched(touch)
            
    if touch.state == BEGAN then
        
        if gameState == MENU then
            gameState = PLAYING
            
        elseif gameState == PLAYING then
           arrow:touched(touch) 
            
        elseif gameState == SCORING then
            resetGame()
        end
        
    end
end

Now the arrow class

Arrow = class()

function Arrow:init()
    -- you can accept and set parameters here
    self.arrowFlying = false 
    self.arrowY = 100
    self.arrowX = 0
    self.ARROW_X = self.arrowX
    self.arrowSpeed = 12
    self.resetXvalue = 0

end

function Arrow:draw()
    -- Codea does not automatically call this method
    pushStyle()
    smooth()
    lineCapMode(ROUND)
    stroke(65, 59, 33, 255)
    strokeWidth(10)
    line(self.arrowX,self.arrowY,self.arrowX + 100,self.arrowY)
    sprite("Tyrian Remastered:Arrow Right",self.arrowX + 100,self.arrowY)
    popStyle()
end

function Arrow:update()
    if self.arrowX >= WIDTH then
        self:reset()
    end
end

function Arrow:fly()
    -- Moves arrow if it's been relesed 
    if self.arrowFlying == true then
        self.arrowX = self.arrowX + 8
    end
end

-- Sets arrow back to starting posuiton
function Arrow:reset()
    self.arrowFlying = false
    self.arrowX = ARROW_X
    self.arrowY = math.random(50,HEIGHT - 161)
end

function Arrow:getPos()
    return vec2(self.arrowX, self.arrowY)
end

function Arrow:missCheck()
    local ret_val = false
    
    -- Checks if the arrow missed
    if self.arrowX >= WIDTH and self.arrowFlying == true then
        self:reset()
        ret_val = true
    end
    return ret_val
end
    
function Arrow:getFlying()
    return self.arrowFlying
end

function Arrow:touched(touch)
    
    if touch.state == BEGAN then 
        self.arrowFlying = true
        print("arrow is flying")
    end
end

The error is on the

if arrow:missCheck == true then

line

In one place you have Arrow, in another you have arrow.

I see a few things that look dangerous to me in Arrow:missCheck(). Without the full code then I can’t say for sure, but this is where I’d look:

function Arrow:missCheck()
    local ret_val = false

         -- Checks if the arrow missed
    if arrowX >= WIDTH and arrowFlying == true then
        arrow:reset()
        ret_val = true
    end
    return ret_val
end

First, arrowX and arrowFlying are not instance variables. They look like they are globals. I would have expected these to be particular to an arrow, in which case they should be instance variables.

Second (and this is probably where the error is), the call to arrow:reset() calls the reset method on a particular object, arrow. This almost certainly isn’t what you intended. I suspect that you intended it to be called on the instance of the class that the missCheck function is called on.

Here’s what I would expect that function to look like:

function Arrow:missCheck()
    local ret_val = false

         -- Checks if the arrow missed
    if self.x >= WIDTH and self.flying == true then
        self:reset()
        ret_val = true
    end
    return ret_val
end

When I created the arrow I called that arrow, arrow. Changing the case dint change it. Cause doest Arrow mean the code for the arrow and arrow means 1 specific arrow?

Inside a method, you refer to the instance that the method was called on as self. I don’t know why it is complaining that arrow doesn’t exist - for that I’d need to see the full code (or at least the setup, draw, and full Arrow class). Try putting in at least the self:reset() instead of the arrow:reset() and see if the error message changes.

What are instance variables?

When you have

Arrow = class()

then Arrow is a class.

When you write

arrow = Arrow()

then arrow is an instance of that class.

When you call

arrow:doSomething()

then you are calling the method doSomething on the instance arrow.

When doSomething is defined then it won’t know what name you’ve given to the particular instance, but the magic of classes means that the variable self points to the same instance as it was called on.

So in:

Arrow = class()

function Arrow:doSomething()
    print(self)
end

arrow = Arrow()

print(arrow)
arrow:doSomething()

then both print statements will return the same table ID showing that inside Arrow:doSomething() then self points to the same thing as arrow does outside.

Instance variables are… Well… The line where you said arrow = Arrow(). That’s creating a new “instance” of your class named arrow. With that, whenever you refer to arrow, and are referring to the single instance of the class named arrow. This can make it so if you have multiple instances of one type of class, each one has its own variables.

I’m guessing that the error is in some code that isn’t shown here. As you said you weren’t sure about class syntax yet, I think that why it says arrow is an instance of nil, it’s because you initialized it wrong. To first create a class call classname = class(). When it starts up (and to initialize its variables) it calls classname:init(variables). The “variables” is to supply extra information when creating a new instance of a class. i.e. if you called

function Arrow:init(x, y)
    self.x = x
    self.y = y
end

That would mean you could call arrow = Arrow(x position, position) and in that instance it would have two variables: x and y, the ones you specified when you created the instance. I’m guessing instead of function Arrow:init() you said function Arrow()? Or maybe function init()? Or function Arrow.init()?

Edit: Haha, @Andrew_Stacey got there before me…

Here’s my arrow class. The game was working fine before I tried to move missCheck over from main.

Arrow = class()

function Arrow:init()
    -- you can accept and set parameters here
    self.arrowFlying = false 
    self.arrowY = 100
    self.arrowX = 0
    self.ARROW_X = arrowX
    self.arrowSpeed = 12
    self.resetXvalue = 0

end

function Arrow:draw()
    -- Codea does not automatically call this method
    pushStyle()
    smooth()
    lineCapMode(ROUND)
    stroke(65, 59, 33, 255)
    strokeWidth(10)
    line(self.arrowX,self.arrowY,self.arrowX + 100,self.arrowY)
    sprite("Tyrian Remastered:Arrow Right",self.arrowX + 100,self.arrowY)
    popStyle()
end

function Arrow:update()
    if self.arrowX >= WIDTH then
            arrow:reset()
    end
end

function Arrow:fly()
    -- Moves arrow if it's been relesed 
    if self.arrowFlying == true then
        self.arrowX = self.arrowX + 8
    end
end

-- Sets arrow back to starting posuiton
function Arrow:reset()
    self.arrowFlying = false
    self.arrowX = ARROW_X
    self.arrowY = math.random(50,HEIGHT - 161)
end

function Arrow:getPos()
    return vec2(self.arrowX, self.arrowY)
end

function Arrow:missCheck()
    local ret_val = false
    
         -- Checks if the arrow missed
    if self.arrowX >= WIDTH and self.arrowFlying == true then
        self:reset()
        ret_val = true
    end
    return ret_val
end
 
    
function Arrow:getFlying()
    return self.arrowFlying
end

function Arrow:touched(touch)
    -- Codea does not automatically call this method
end

@Goatboy76 And is that still not working? The code for missCheck in the immediately above is not what you originally posted, so I’d like confirmation that the error is still there. And if it is, I’d like to see setup and draw as well.

Yes the error is still here.

Here’s my setup

ballon = nil

function setup() 
    displayMode(FULLSCREEN)
    supportedOrientations(LANDSCAPE_ANY)
    table = {"strength"}
    print(table[1])
    
    arrow = Arrow()
    
    ballon1 = Ballon(math.random(120,WIDTH - 200))
    ballon2 = Ballon(math.random(120,WIDTH - 200))
    
    -- Random Y starting place
    
    last_points = 0
    best_points = 0
    
    MENU = 0
    PLAYING = 1
    SCORING = 2
    
    resetGame()
    
end

Here’s my draw

function draw()

   if gameState == MENU then
        background(0, 0, 0, 255)
        
        pushStyle()
        font("AmericanTypewriter")
        fontSize(100)
        fill(187, 25, 27, 255)
        text("Ballon Shoot",WIDTH/2,HEIGHT/2)
        popStyle()
        
        pushStyle()
        fontSize(50)
        fill(195, 92, 20, 255)
        text("Tap to begin (:",WIDTH/2,HEIGHT/2 - 100)    
        popStyle()
        
        pushStyle()
        fontSize(20)
        fill(195, 92, 20, 255)
        text("Last Score: " .. last_points, 0 + 100, HEIGHT - 20)    
        popStyle()
        
        pushStyle()
        fontSize(20)
        fill(195, 92, 20, 255)
        text("Best Score: " .. best_points, WIDTH - 100, HEIGHT - 20)    
        popStyle()

    elseif gameState == PLAYING then
        background(35, 91, 31, 255)
    
        arrow:draw()
        
        ballon1:draw()
        ballon1:update()
        ballon2:draw()
        ballon2:update()
        
        ballon1:speedCheck()
        hittCheck()
        endCheck()
        arrow:fly()
        showPoints()
        showMisses()
    
    elseif gameState == SCORING then
        background(0, 0, 0, 255)
        pushStyle()
        fill(166, 156, 204, 255)
        font("Arial-ItalicMT")
        fontSize(100)
        text("Your Final Score:"..points, WIDTH/2,HEIGHT/2)
        popStyle()
    end

end   -- end draw

Where’s the call to arrow:missCheck()?

It was outside of draw. Moving it into draw fixes it. I dint know that my arrow din’t exist outside of draw. huh. Thanks.

Any other rules I should know about this?

It should have existed outside of draw…

No, it might not have existed outside draw because it was only defined in setup. It depends on exactly what “outside of draw” means. We’re only guessing here because we can’t see the full code.

Not knowing what arrowX is after you hit a balloon is due to your Arrow:reset() function trying to set self.arrowX to the global variable ARROW_X when it should be self.ARROW_X.

I’m also pretty confused why you have that ARROW_X variable, since when you initialize an arrow, you hard code arrowX to 0 then set ARROW_X to arrowX which would also be 0. It might be a bit less confusing to throw away ARROW_X and simply set arrowX to 0 on reset.

I’m uncertain if ARROW_X is used elsewhere in your project, but if it is only used in the Arrow class, then you could most likely just remove it.

Edit: I noticed that your arrow update and missCheck do relatively the same check, you could combine them so that you don’t have redundant checks. I suggest combining into update and removing missCheck

function Arrow:update()
    if self.arrowX >= WIDTH then
        if self.arrowFlying == true then
            missScore = missScore + 1
        end
        self:reset()
    end
end

Then you can remove all instances of missCheck and remove that last if statement in your draw function.

That fixes it. Thanks.

Ill work on the simplifying it now.