Problem with using tweens on a class of object

Hi there

I’m having issues with tweens on objects set up by a class for animating text. For some reasons all the tutorials I find assume that the tweens are defined by a function, rather than a class.

In Main when I want to print to screen I do this:

table.insert(writing, Write ("game over"))
tween (2, writing[#writing], {y=50}, tween.easing.backOut, writing[#writing]:erase())

in the Write class:

function Write:init(str)
self.x=WIDTH/2
self.y=HEIGHT/2
self.str=str
self.col=color(0, 244, 255, 255)
--etc
end

function Write:draw()
--draws the text
end

function Write:erase()
 print "callback"  --ie this is just for debugging. 
end

A few questions:

  1. Is it possible to put the tween command into the Write:init function, something like this:

tween(2, self, {y=50})

every time I try this, or variations on it, I get an error. I can’t define the tween inside the class, is that correct?

  1. I can’t seem to tween the colour (I want the alpha to go down to 0, so that the text fades). This:

tween (2, writing[#writing], {y=50, col.a=0}, tween.easing.backOut, writing[#writing]:erase())

produces an error, as does adding any kind of colour variable

  1. the callback function seems to get called at the very bginning of the tween, rather than at the end of the tween.
    Also I don’t know how or whether a table.remove would work if the erase() function is inside the Write class

It would be great if someone could tell me where I’m going wrong. Perhaps there’s a reason why none of the tweening tutorials I’ve seen are animating objects defined by a class, and I need to convert the Write class into a set of functions in the Main tab. I’m new to Codea/Lua (my last programming experience was Blitz Basic on the Amiga a long tme ago), and don’t have a sense yet of when class or a function is the best way of defining the objects in the game.

thanks for reading

Tweens are just number interpolations, and very fast. Ellipses (in great numbers) can be slow, I recall from previous threads.

But why don’t you try it different ways and see what happens? That is the best way to discover the fastest approach.

@JakAttak you’re right. I went back to have another look at my original code. Apart from not using the callback function correctly, the destination was a variable I’d defined locally somewhere else (it was from my first Codea project, and the code was a mess basically). I thought I’d tried every combo (obviously not). I fixed all of that, and it now works. i.e. I have a class for animating text on the screen, the tween in the init of the class with self as object, and a callback that tells the draw loop to kill the text once the animation is done. Thank you everyone, I learnt a lot.

Slightly off-topic follow-on question, my FPS drops from 60 to 30 or lower when there’s lots of action on screen (ie explosions plus this text routine displaying points, multipliers, and so on). Is it faster to create an image of the text, and then draw it as a mesh, than using text() for the frame-by-frame drawing? Or is it the tweens that are the processor drag? I also have the same question for my explosion sparks, which are currently drawn with ellipse(). Would an image mesh be faster? Generally, I’m very impressed with the speed of codea. I have 50 physical bodies bouncing around, all drawn with meshes, and FPS is a consistent 60 on the iPad Air.

@yojimbo2000 - I suspect your problem is because you can’t use self as the item to be tweened, because self is not a normal table. If you define a table inside your class and tween that, you should be OK.

I’m not sure about the colour variable but that’s easy to get around by just tweening an ordinary number and then setting the colour value to that.

I don’t understand the last question about the table remove, can you provide more detail?

@Ignatz, thanks for the speedy reply.

OK, I’ll investigate putting a table inside the class.

I tried your suggestion of tweening a separate alpha variable, it worked, thanks for that.

the callback is meant to erase the writing object after the tween has finished.

Before I thought about using callback, I had a self.timer variable set in Write:init(), which would manually count down inside the draw loop. When it reached 0 I’d erase that entry. THis is the code from the main draw loop:

for i,v in ipairs(writing) do
v:draw()
if v.timer==0 then table.remove(writing, i) else v.timer = v.timer -1 end
end

Rather than this manual method, I thought it would be more elegant to use the callback to kill the writing when the tween finished. However, I don’t know how I would get the correct position in table.remove for this, and also the erase() function seems to get called at the very start of the tween, not the end of the motion

You can’t kill a class instance from within itself

Better is to have a table where you keep objects to be deleted, and then empty it in draw, eg something like

function Write:erase()
    table.insert(ObjectsForDeletion,self)
end

--then at the top of draw
for _,d in pairs(ObjectsForDeletion) do
    d=nil
end

Great idea, I’ll try that.

Any thoughts on why the callback function is being called at the very start of the tween though?

Try this. Tap screen to start the tween.


function setup()
    writing={}
    table.insert(writing, Write ("game over",200,200,300,500))
    table.insert(writing, Write ("game start",400,200,300,520))
end

function draw()
    background(0)
    for a,b in pairs(writing) do
        b:draw()   
    end
end

function touched(t)
    if t.state==BEGAN then
        for a,b in pairs(writing) do
            b:touched(t)   
        end
    end
end

function erase()
    print("tween done")
end

Write=class()

function Write:init(str,xs,ys,xe,ye)
    self.str=str
    self.col=color(0, 244, 255, 255)
    self.starts={x=xs,y=ys}
    self.ends={x=xe,y=ye}
end

function Write:tw()
    tween(2,self.starts,self.ends,tween.easing.sineOutIn,erase)
end

function Write:draw()
    fill(self.col)
    text(self.str,self.starts.x,self.starts.y)
end

function Write:touched(t)
    self:tw()
end

I changed my code above so the erase function gets called when the tween is done.

There’s no real need for a class for this. When I did it, I put messages into a table, and as each one expired, I removed it and tweened the next. That’s simpler to manage.

@dave1707 thanks for that example, the tween motion works great. However, I see the “callback” debug text being printed straightaway, not at the end of the motion. Are you seeing that behaviour too?

UPDATED: Great, it works now! So the callback cannot be inside the class? That’s interesting. I think @Ignatz is right, maybe it is better in a table/ function

@yojimbo2000 Here’s a version with the callback in the class. I have it clear the string when the tween is done.


function setup()
    writing={}
    table.insert(writing, Write ("game over",8,200,200,300,500))
    table.insert(writing, Write ("game start",3,400,200,300,520))
end

function draw()
    background(0)
    for a,b in pairs(writing) do
        b:draw()   
    end
end

function touched(t)
    if t.state==BEGAN then
        for a,b in pairs(writing) do
            b:touched(t)   
        end
    end
end

Write=class()

function Write:init(str,t,xs,ys,xe,ye)
    self.str=str
    self.col=color(0, 244, 255, 255)
    self.starts={x=xs,y=ys}
    self.ends={x=xe,y=ye}
    self.time=t
end

function Write:tw()
    tween(self.time,self.starts,self.ends,tween.easing.sineOutIn,
        function() self.str="" print("tween done") end)
end

function Write:draw()
    fill(self.col)
    text(self.str,self.starts.x,self.starts.y)
end

function Write:touched(t)
    self:tw()
end


@yojimbo2000 I modified the code above to allow different times for different Tweens. again, just tap the screen to start the Tweens.

Ok, combining @dave1707 and @Ignatz’s suggestions. I’ve also placed the tween in the unit function and commented out the touch function. The tween animates, the callback happens when it should, but, setting the value to nil does not kill the object. I’ve found this previously with objects stored in tables, I had to use remove.table to kill the object. Any ideas how to fix this?

function setup()
    writing={}
    kill={}
    table.insert(writing, Write ("game over",200,200,300,500))
    table.insert(writing, Write ("game start",400,200,300,520))
end

function draw()
    background(0)
    for a,b in pairs(writing) do
        b:draw()   
    end
    for _,b in ipairs(kill) do
        b=nil  -- why doesnt this work?
        
    end
end

--[[function touched(t)
    if t.state==BEGAN then
        for a,b in pairs(writing) do
            b:touched(t)   
        end
    end
end]]--

Write=class()

function Write:init(str,xs,ys,xe,ye)
    self.str=str
    self.col=color(0, 244, 255, 255)
    self.starts={x=xs,y=ys}
    self.ends={x=xe,y=ye}
    tween(2,self.starts,self.ends,tween.easing.sineOutIn,erase)
end

function Write:tw()
    tween(2,self.starts,self.ends,tween.easing.sineOutIn,self:erase())
end

function Write:draw()
    fill(self.col)
    text(self.str,self.starts.x,self.starts.y)
end

function Write:touched(t)
    self:tw()
end

function erase()
   table.insert(kill,self)
print "callback" --debug
end

Oh I’ve just realised, erase is no longer inside the class, so there’s no self variable here

@yojimbo2000 See my version above that has the callback in the class.

But when the callback was in the class, it didn’t work properly. It was called as soon as the tween started.

@yojimbo2000 You’re not looking at my last example where it’s in the class and it’s correct.

@dave1707

You’re right, sorry I didn’t see the second version that you posted.

And it works!

I had no idea that you could define a null function in a single line like that with just spaces inbetween each command. That is fantastically useful. Is there a tutorial anywhere explaining this syntax?

Thank you

I guess though that I should have the object stored in a table inside the class though, because the self.str=“” isn’t killing the object as such, just making it invisible, right? ie, as the game went on, the pairs(writing) loop would continue to get longer (I’ll be using this every time the player scores points). Although maybe the processor overhead will be minimal if things aren’t actually being drawn.

@yojimbo2000 I was just clearing the str variable because I didn’t know what you really want to do. You can add as much code as you want in that function to do whatever.