Problem with using tweens on a class of object

@yojimbo2000 Here’s a version where I increased the table count and then I delete the table entry when the tween is done.


function setup()
    parameter.watch("#writing")
    writing={}
    table.insert(writing, Write ("game over 3",3,100,200,300,560))
    table.insert(writing, Write ("game start 4",8,200,200,300,600))
    table.insert(writing, Write ("game over 5",5,300,200,300,540))
    table.insert(writing, Write ("game start 6",7,400,200,300,520))
    table.insert(writing, Write ("game over 7",6,500,200,300,580))
    table.insert(writing, Write ("game start 8",4,600,200,300,500))   
end

function draw()
    background(0)
    for a,b in pairs(writing) do
        b:draw() 
        if b.remove then
            table.remove(writing,a)
        end
    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
    self.remove=false
end

function Write:tw()
    tween(self.time,self.starts,self.ends,tween.easing.sineOutIn,
        function() self.remove=true print("tween complete") 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

Ok, I figured it out. I made the callback function set the string to nil, and this is what I changed the main draw command to:

for a,b in ipairs(writing) do
 if b.str~=nil then b:draw() else table.remove(writing, a) end
end

So the actual act of deletion occurs outside the class in the main loop.

Thanks to both of you for your help, I’ve learned a lot!

Sorry I was typing my comment at the same time as yours, I think our solutions are the same, aren’t they?

@yojimbo2000 - IMHO, a simple table will be better than a class - and efficient - because it works like a queue, which is exactly what you want. Using a class and setting messages to blanks without killing them when they finish is pretty ugly.

I’ve written about anonymous functions here
https://coolcodea.wordpress.com/2013/09/13/111-anonymous-functions-and-the-mysterious-_g/

@yojimbo2000 They both work the same. I like using seperate self variables for different conditions.

@Ignatz you could be right there. I might have a go at recoding it as a table/ function in the main tab just to see what the difference is. But if you look at mine and @dave1707 's most recent code, we do have an efficient way of properly killing the instance in the draw loop, after it has been set to nil.

@yojimbo2000 - below is a table of functions I use in my 3D dungeon, to show messages in sequence, one after the other, for variable times.

to add a message: Messages.Add{text=a.text,length=a.length}
(note I send a table of named parameters through, enabling me to define only the message elements I need)

to show messages in draw: Messages.draw()
(which also manages deletion)

Messages={}
    Messages.text={}
    Messages.defaultLength=5
    Messages.phaseOutTime=2 --seconds
    Messages.defaultColour=color(255)
    Messages.defaultSize=32
    --Messages.defaultPosition=vec2(WIDTH/2, HEIGHT/2)
    Messages.defaultFont="Noteworthy-Bold"
 
    function Messages.Add(settings)       
    --messageText,durationInSeconds=Messages.defaultLength,size=64,colour)
    Messages.text[#Messages.text+1]={
        ["message"]=settings.text,
        ["length"]=settings.length or Messages.defaultLength,
        ["size"]=settings.size or Messages.defaultSize,
        ["colour"]=settings.colour or Messages.defaultColour,
        ["position"]=settings.position or vec2(WIDTH/2, HEIGHT/2),                
        ["font"]=settings.font or Messages.defaultFont
    }
end
 
function Messages.draw()
    if #Messages.text==0 then return end
    m=Messages.text[1]
    if not m.endTime then
        m.endTime=ElapsedTime+m.length
    elseif ElapsedTime>m.endTime then
        table.remove(Messages.text,1)
        return
    end
    Messages.WriteMessage(m)
end
 
function Messages.WriteMessage(m)
    pushStyle()
    font(m.font)
    textWrapWidth(WIDTH*.75)
    fontSize(m.size)
    local t=m.endTime-ElapsedTime
    m.colour.a=math.min(1,t/Messages.phaseOutTime)*255
    fill(m.colour)
    textMode(CENTER)
    text(m.message,WIDTH/2,HEIGHT/2)
    popStyle()
end 

I have a variation on this in my 2D sidescroller, which shows multiple messages. scrolling them upwards and fading them, so they can stack on each other.

@Ignatz oh, you’re the coolcodea author! I love that blog! It’s an incredible resource

Below is my scrolling message function set, you can see the result here
https://coolcodea.wordpress.com/2014/09/16/162-2d-platform-game-6-extras/

Message.draw needs to be given a player object in this case so it can figure out where to position the message (just above the player’s head)

SS_Message={}
SS_Message.text={}
 
function SS_Message.Add(txt,t) --txt is message, t is time
    t= (t or 2) + ElapsedTime --time when message ends
    SS_Message.text[#SS_Message.text+1]={txt,t}
    SS_Message.startTime=nil
end
 
function SS_Message.draw(p)
    if #SS_Message.text==0 then return end
    pushStyle()
    textWrapWidth(400)
    fontSize(18)
    local pos=p:GetPos()
    --starting height
    local ww,hh=pos.x*SS_w,pos.y*SS_h+150 --WIDTH/2, HEIGHT*2/3 
    for i=#SS_Message.text,1,-1 do
        if ElapsedTime>SS_Message.text[i][2] then
            table.remove(SS_Message.text,i)
        else
            local t=math.min(1,SS_Message.text[i][2]-ElapsedTime)*255
            fill(124, 167, 210, t)
            local w,h=textSize(SS_Message.text[i][1])
            local w2=math.max(ww,w/2+30)
            rect(w2-w/2-20,hh-h/2-20,w+40,h+40)
            fill(255,255,255,t)
            text(SS_Message.text[i][1],w2,hh)
            hh=hh+h+50
        end
    end
    popStyle()
end 

@Ignatz I saw a version of the code for the 2d platform game, I was the guy that asked that question today on that page of the blog. It looks very different from a class, when you store it in a table like that. I still don’t understand the first 2 lines though. Is the messages.text {} table nested inside (like a dimension) the messages {} table? Are the functions also part of the messages {} table? You have to forgive me, object oriented stuff (if that’s the correct word for classes and functions) is still new to me, and I don’t entirely get the .suffix convention

See if this helps
https://coolcodea.wordpress.com/2013/04/12/31-a-lesson-from-the-roller-coaster-demo/
https://coolcodea.wordpress.com/2014/10/01/169-why-tables-and-classes-are-so-useful/
https://coolcodea.wordpress.com/2013/06/14/84-a-practical-example-showing-the-value-of-classes/

Essentially, you can put functions in a table, which just means you add a prefix with the table name. The same goes for variables. This enables you to keep this stuff completely separate from your other code and not risk duplicating variable names etc.

Just replying to something @Ignatz said waaay above: you can use self as the table for a tween.

@JakAttak but you can’t just make “self” by itself the object of the tween though can you, like I was trying to do right at the top of the thread? It has to be self.something, whether that something is a position vector, like @Dave1707 came up with here, or a table inside the table, like something={str=srt, x=xs, y=ys, col=col} etc.

Or have you managed it with just self as the object?

tween(1, self, { x = 100 }, tween.easing.linear) works perfectly fine

Here’s a small example:

function setup()
    ex = Example()
    ex2 = Example(HEIGHT / 4, WIDTH / 3, WIDTH * 2/3)
end

function draw()
    background(255)
    
    ex:draw()
    ex2:draw()
end

Example = class()

function Example:init(y, x, x2)
    self.y = y or HEIGHT / 2
    self.x = x or 0
    
    tween(2, self, { x = x2 or WIDTH }, { loop = tween.loop.pingpong })
end

function Example:draw()
   ellipse(self.x, self.y, 30)
end