Class variable issues in codea - nil value when attempting arithmetic - help

Hi, I’ve been programming in lua/codea for a week now and finding it brilliant to learn especially with Ignatz’s two learning PDFs as well as excellent examples from you all.

I’m making headway with my game with code currently in main. Now I’m branching out and starting to build classes and this one consistent issue is causing me to want to throw my iPad against something made of brick.

Each time I write a class and use variables within it and not pulled in from main, they come up as nil as defined and the program won’t run.

Here is my code (please note groundspeed is a variable defined and used in main)

Pond = class()

function Pond:init(pondX,pondY,imagepond)
    -- you can accept and set parameters here
    self.pondX = 1240
    self.pondY = 248
    self.imagepond = readImage("Documents:pond")
end

function Pond:draw()
    self.pondX = self.pondX - groundspeed
    sprite(imagepond,self.pondX,248)
end

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

Here is the error:

error: [string "Pond = class()..."]:12 attempt to perform arithmetic on field 'pondX' (a nil value)

I’ve tried moving and adding various ‘self.’ In front of things and to no avail.

I’m thinking its something simple I’ve done wrong or missed like the time I was evaluating a variable string without speech marks and when I put them in speech marks it all started working.

Can you guys help me?

I’ve tried it in different classes and its not working.

Thank you
The major

@Majormorgan - the sprite command should be

sprite(self.imagepond,self.pondX,248)

Your image is stored in self.imagepond, which is not the same as imagepond (which doesn’t exist and will create an error).

@Ignatz hey thank you. I’ve corrected that and now get the same error for line 11:

    self.pondX = self.pondX - groundspeed

I’ve tried with with and without the self. in front if it but no avail. It makes me think I messed up somewhere else.

By the way, your two code beginner guides have helped me get leaps and bounds in this - thank you!

@Majormorgan -

Perhaps you are calling the draw function like this

P.draw()  --where P is whatever you called your class

when it needs to be

P:draw()  --use a semi colon not a full stop

Failing that, I suspect your variable groundspeed either is not a global variable (so it can’t be seen inside the class) or else it hasn’t been initialised.

(& thanks for the nice feeback, it makes the effort worthwhile).

It sounds as if you are calling the draw function with the class name instead of the instance name.

if your instance is created with something like myPond = Pond() you need to call draw with myPond:draw() not Pond:draw()

This may not be the problem but will result in the error you are getting.

Just curious. You have an init function that accepts 3 parameters but you are setting the class properties manually?

@pops I’ll check into that - thanks. The Init class is where my understanding is limited, so if I’m setting the parameters manually I take it I can remove them from inside the init() ?

With regards to the instance class, this sounds like the bit I’m missing. To answer @Ignatz at the same time, in main in the draw function I’m using:

    Pond:draw()

Which sounds like I’m calling the class as you say and not the instance. So my question is how and where I define the instance.

Is it in function setup() in main or somewhere else?

The game is one where the background is scrolling by, using an X measurement to loop a background image so it looks continuous (think of the scrolling ground in flappy bird).

The pond is an obstacle you have to jump over so it tracks its X position and deducts ground speed defined in main.

I know groundspeed is global as I managed to test it in a fudged class of pond done a couple of versions ago that also passed its x and y position defined in main. But I wanted to keep the x and y in the class hence why I’ve stripped that out.

Lol. Once I crack this I imagine I’ll be making classes much easier!

Thank you all for your help.

@Majormorgan, try this in the setup function

p=Pond(1240,248,"Documents:pond")

Then in Pond:init

function Pond:init(pondX,pondY,imagepond)
    self.pondX = pondX
    self.pondY = pondY
    self.imagepond = readImage(imagepond)
end

Then in your draw function

p:draw()

or maybe better still, to give you more flexibility

p:draw(groundspeed) --pass ground speed to class

--and then
function Pond:draw(gs) --groundspeed passed as parameter
    self.pondX = self.pondX - gs
    sprite(self.imagepond,self.pondX,248)
end

This means your class doesn’t need to know anything about the outside world, which is a good thing

That’s it! Thanks @Ignatz and @Pops

It was the fact I was calling the class straight and not defining an instance. Oh god that make a whole lotta sense now.

Thank you both for your help and patience!

Cracking one class now opens the door to creating a range of classes now I know how they work.

So calling the classes is now sorted and Pond is working fine. For a sprite its cool. But when it comes to sprite sheets and meshes, I’ve run into some confusion what to have with self in front of.

This new class has code for a sprite sheet that someone on the forums posted, which I got to work well in main for the hero character, but putting it into a new class causes some issues,

Ive used the Init function that @Ignatz wrote for me above in the new class Fountain. I’m passing the x,y and file name to load in read image from main like in pond.

Eventually the fountain will have a working state (0) and a cracked state (1) where the fountain is broken. In the working state are 3 frames of a water fountain animating.

I’ve got all these variables set up in fountain with self. in front of them but when I come to draw and use this rectangle to extract the part of the sprite sheet it flags up an error.

So bear in mind this code is all within the class Fountain

Fountain = class()

function Fountain:init(fountainX,fountainY,imagefou)
    -- you can accept and set parameters here
    self.fountainX = fountainX
    self.fountainY = fountainY
    self.fou=mesh()
    self.imagefou=readImage(imagefou)
    self.fou.texture=imagefou
    self.fourows=1 --number of rows in the sprite sheet
    self.foucols=3 -- number of columns in the sprite sheet
    --moaction=0
    self.fousizex=324
    self.fousizey=200
    --constants
    self.fouworking=0
    self.foucracked=1
    --arrays to hold the animation
    self.fouanimdelay=0
    self.fouamimdelaymax=3
    self.fouanimx={}
    self.fouanimy={}
   -- self.foumovex={}
  --  self.foumovey={}
    --define the frames
    self.fouanimx[0]={0,1,2}
    self.fouanimy[0]={0,0,0}
 --   self.foumovex[0]={0,0,0}
 --   self.foumovey[0]={0,0,0}
    --fountain cracked
  --  pondanimx[1]={0,0,0}
 --   pondanimy[1]={0,0,0}
  --  pondmovex[1]={0,0,0}
  --  pondmovey[1]={0,0,0}
    --defining sprite
    self.fouy=200 --coordinate of fountain sprite
    self.foux=WIDTH/3 -- coordinate of fountain sprite
    self.foucurFrame=1
    self.foustate=self.fouworking
end

function Fountain:draw()
    -- Codea does not automatically call this method    
    self.fou:clear() --clear the mesh
    self.fountainX = self.fountainX - groundspeed
    --cycle through the animation
--add a delay to solw the animation down
    self.fouanimdelay = self.fouanimdelay + 1
        if self.fouanimdelay>3 then
    --move to the next frame in the animation
        self.foucurFrame = self.foucurFrame + 1
    --check to see if this frame is past the end of the animation array for the current state and if so, reset
    if self.foucurFrame>#self.fouanimx[self.foustate] and self.foustate==self.fouworking then
   self.state=self.fouworking
        self.foucurFrame=1
end
local idx=self.fou:addRect(self.fountainX,self.fountainY,self.fousizex,self.fousizey)
    self.fou:setRectTex(self.idx,(self.foanimx[self.state][self.foucurFrame])/self.foucols,(self.fouanimy[self.state][self.curFrame])/self.fourows,1/self.foucols,1/self.fourows)   
    --draw the mesh
    self.fou:draw()
    --reset the animation delay counter
    self.fouanimdelay=0
    end
end

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

The line that flags a problem is the line below local idx. I may well have too many ‘self.’ Or it may even be local is not the right thing here, as I said before the code ran well in main, but bringing it into class Fountain might be the issue.

@Majormorgan, because the local idx=… and self.fou.setRectTex… lines are after the end statement that closes the Fountain:init function, I think that self then has no reference to any instance of the class.

It looks like you are adding the rectangle and setting the texture as part of the instance initialisation, so these statements should be inside your init function.

Also, it appears you have lost the function Fountain:draw() line before --draw the mesh.

@time_trial thanks for looking at the code. There was a lot to go through so I appreciate you all looking.

I’ve been tinkering all afternoon and got rid of the errors by rebuilding the code bit by bit.

I see what you’re thinking, but the function Fountain:draw() is there in the code above and I’ve got it working without reordering.

What I did do was simplify all the names to make it easier to track the changes in the code.

It is a challenge!

I’ve almost got it working fully. Currently it does exactly what I need in the new code except there is one frame that turns off so it flashes briefly.

Once I comb through I’ll fix that issue.

Thanks all for your help,

And the last bit is now fixed. I had to draw the mesh after the last frame calculation.

So now I have two types of classes. One is the sprite class for static non animated graphics and one is a sprite sheet class for animation.

Thank you all for your help,

Ah, sorry @Majormorgan, I think the line wrapping confused me. I should have learnt by now not to try and debug code without running it. Doh! Glad you nearly have it working.

@Majormorgan good to see you got it sorted.

@time_trial not at all. You looked at the code and thought you spotted something from afar and for that I’m extremely grateful. That people look and bounce ideas is ace. Regardless of wether you run test it. I love that everyone’s experiences come together and spot things!

Thanks @Pops!

@pops you and @Ignatz gave me the breakthrough earlier with your help on the first problem and the confidence to push on with the second problem.