Transition animation class issue with nil value

I’ve been trying to create a class to handle basic transition animations between different scenes/states in my apps, as I found myself re-writing the same bit of code multiple times for each individual animation. My goal is to simply recreate an image of the current screens assets using setContext() and then sprite that image to slide in and out of the different scenes as I normally have.

The code seemed to function just fine outside of the class, but I’m having a hard time getting things working the same from within. I end up with a nil value in the class draw that has me slightly confused. Any help would be greatly appreciated.

My full project is way too big to post, so I’ve created a simple version of my issue…


--# Main
function setup()
    SCENE_MENU = 1
    SCENE_INFO = 2
      
    cScene     = 1
    
    transAni = Anima(WIDTH,HEIGHT,vec2(WIDTH/2,HEIGHT/2),vec2(-WIDTH/2,HEIGHT/2), SCENE_MENU,SCENE_INFO)
    transAni.asset  = function() testPage() end
    transAni.action = function() testAnimateDone() end
    
    parameter.watch("transAni.img")
    parameter.watch("transAni.posA")
    parameter.watch("transAni.active")
    parameter.watch("transAni.strtA.x")
end

function draw()
    background(40, 40, 50)
    if transAni.active == false then
        testPage()
    end
    
    transAni:draw()
    
end

function testPage()
    pushStyle()
    fill(255, 255, 255, 255)
    sprite("Cargo Bot:Opening Background",WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    text("MENU",WIDTH/2,HEIGHT/1.2)
    text("tap screen to start transition animation",WIDTH/2,HEIGHT/2)
    popStyle()
end

function testAnimateDone()
    print("Transition Animation Complete")
    cScene = SCENE_INFO
end

function touched(touch)
    if touch.state == ENDED then
        transAni:createImg()
        transAni:imgAnimate()
    end
end

--# Anima
Anima = class()

function Anima:init(cellW,cellH,posA,posB,sceneA,sceneB)

    self.cellW  = cellW     -- img width for setContext
    self.cellH  = cellH     -- img height for setContext
    self.img    = nil       
    self.posA   = posA      -- 1st position of image
    self.posB   = posB      -- 2nd position of image
    self.sceneA = sceneA    
    self.sceneB = sceneB
    self.asset  = asset     -- the create function of the page to mock
    self.action = nil       -- function after animation completes(switch scenes)
    self.active = false     -- check for animation currently running
end

function Anima:draw()
--if an image exists then start the animation 
    if self.strtA then      
--       sprite(self.img,self.posA.x,self.posA.y)   -- just spriting the image works when uncommented
        
         sprite(self.img,self.strtA.x,self.strtA.y,self.strtA.size)  --draw animation (nil value?)
            
    end
end
--
-- Create an image for animating
function Anima:createImg()
    self.img = image(self.cellW,self.cellH)
    setContext( self.img )
    self.asset() 
    setContext()
    print("Image Creation Complete")
    return self.img
end
--
-- if animation isnt already active, activate animation
function Anima:imgAnimate()
    if not self.active then                       --if animation isnt active
        self.active = true                          --activate animation
        print("active =",transAni.active)
        self:imgPath()                              --follow proper path        
    end
end
--
-- Create a tween path for the image to follow
function Anima:imgPath()   

    self.strtA = {self.posA.x, self.posA.y, size = self.cellW}  --starting pointA
    self.strtB = {self.posB.x, self.posB.y, size = self.cellW}  --starting pointB
    self.twnA  = {self.posA.x, self.posA.y, size = self.cellW} --1st point in path
    self.twnB  = {self.posB.x, self.posB.y, size = self.cellW}  --2nd point in path
    
    if cScene == self.sceneB then
        print("open/slide in image")
        tween.path(.3, self.strtB, {self.twnB,self.twnA},{loop=tween.loop.once},self:imgAnimateDone()) --Slide in
    elseif cScene == self.sceneA then
        print("slide out image")
        tween.path(.5, self.strtA, {self.twnA,self.twnB},{loop=tween.loop.once},self:imgAnimateDone()) --slide out      
    end
end
--
-- this runs after the image completes the path
function Anima:imgAnimateDone()
    self.active = false                        --animation deactivate
    self.action()                                                           
end                                                         

To start, the strtA should be self.strtA. After changing it to self.strtA, it doesn’t look like it’s being set to anything before being used in Anima:draw.

@dave1707 Thanks for your speedy response. I didn’t realize I missed adding the “self” to the “strtA” in my draw function when I had copied them over to make my example to post here. Classic silly mistake on my part. I have made the “self” changes to my code above.

As for the strtA not being set before use in draw. Shouldn’t my “if” statement stop it from running before it’s first created in Anima:imgPath?

Simply for testing purposes, even after setting strtA value on the line directly before I call it in Anima:draw, I still get the same error. Self.strtA always shows as nil, but why? I’m pretty sure I’m doing everything in the exact same order that I was before moving things inside the class. I’m guessing it’s another silly mistake I’m making somewhere, as I’m fairly new to creating classes.

@Circuit Try this. When you create self.strtA in Anima:imgPath, you’re not creating the table with keys for the first 2 entries like you do for size. So you need to use the [1] and [2] to get the values.

function Anima:draw()
    --if an image exists then start the animation 
    if self.strtA~=nil then   
        sprite(self.img,self.strtA[1],self.strtA[2],self.strtA.size)  
    end
end

@Circuit Here’s a stripped down version of some transition code I have. Keep tapping the screen for the next transition.

EDIT: Modified the original code posted to not require code changes if more transition screens are added to the spr table.

displayMode(FULLSCREEN)

function setup()
    done=false
    offset=1
    slide=true
    sp=vec2(WIDTH/2,HEIGHT/2)
    fill(255)
    
    img1=image(WIDTH,HEIGHT)
    setContext(img1)
    sprite("Cargo Bot:Opening Background",WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    text("MENU",WIDTH/2,HEIGHT/1.2)
    text("tap screen to start transition animation",WIDTH/2,HEIGHT/2)
    setContext()  
      
    spr={img1,"Cargo Bot:Startup Screen","Cargo Bot:Starry Background",
            "Cargo Bot:Codea Icon","Cargo Bot:Toolbox","Cargo Bot:About Info Panel"}
end

function draw()
    background(40, 40, 50)
    transitionScreens()
end

function transitionScreens()
    if offset<=#spr then
        sprite(spr[offset],sp.x,sp.y)
    end
    if offset+1<=#spr then
        sprite(spr[offset+1],sp.x+WIDTH,sp.y)
    end
    if done then
        text("no more transition screens\
     tap to restart",WIDTH/2,HEIGHT/2)
    end
end

function touched(t) 
    if t.state==BEGAN then
        if done then    -- restart, or set flag to run whatever you want
            done=false
            offset=1
            slide=true
        elseif slide then
            slide=false
            sp={x=WIDTH/2,y=HEIGHT/2}
            tween(2,sp,{x=-WIDTH/2,y=HEIGHT/2},{},check)
        end
    end
end

function check()
    offset=offset+1  
    slide=true
    sp=vec2(WIDTH/2,HEIGHT/2)
    if offset>#spr then
        done=true
    end
end

@dave1707 I can’t believe I overlooked that, It’s always something so simple that I miss. I was just reading about it too in one of ignatz table tutorials. Oh, the hours I have waisted trying to figure it out on my own.

There is one other strange difference I’m noticing now that I placed these functions within a class.

Before class:
At the end of my tween path I would call a function imgAnimateDone like so…

tween.path(.5, strtA, {twnA,twnB},{loop=tween.loop.once},imgAnimateDone)

After migration to my class:
When I call the equivilant function using the self: prefix self:imgAnimateDone, I am unable to run my project without adding the () after it like so…

 tween.path(5, self.strtA, {self.twnA,self.twnB},{loop=tween.loop.once},self:imgAnimateDone())

But I noticed that this causes the function to run immediately instead of waiting for the tween.path to complete. Is there another way to do this? It’s critical that this function only runs after my tween is done.

Also, thanks for the transition example. I really like seeing all the different approaches to similar tasks. I’ve learned tons through your posts on many different subjects, and truly appreciate your efforts.

You need to enclose it in a closure, like this:

tween.path(5, self.strtA, {self.twnA,self.twnB},{loop=tween.loop.once}, function() self:imgAnimateDone() end )

@Circuit I modified my above code to not require any code changes if more screens are added to the spr table. Also, anyone could use the above code if they want to show different images and wait for a touch to show the next image.

@yojimbo2000 thanks for the fix, I think that was the final piece to my puzzle. Well, this one anyhow. I’m sure my next head scratcher is just around the corner ; )

@dave1707 @yojimbo2000 Again, thank you both for all the help, as you have saved me hours of confusion with creating classes. I always try to figure these things out on my own, it just takes me a lot longer to get from point A to point B (pun intended). I can only hope to eventually be as Codea competent as the both of you.

@Circuit I’m coming up on 5 years of playing with Codea. There’s still a lot about Codea/Lua I don’t know which keeps me interested. I also have about 45 years of programming experience and I still learn new things every now and then.

@dave1707 Well, your many years of coding experience definitely shine through in Codea. I always enjoy reading your input on different topics, as you have a very logical approach to problem solving. I’m still fairly green to the entire programming thing in general, Codea/Lua being the only thing I’ve tried to learn. As a noob, I can really appreciate having such knowlegable people to turn to when I’m feeling a little lost. It has made learning the language seem much more manageable and a lot less daunting.