Search code examples
timerluasdkpositioncoronasdk

Lua / Corona SDK : Positional difference of display objects in loop


so while building a mobile game with Corona SDK i am encountering some problems now and then. One of them I didn't seem to solve :

When spawning display objects in a loop, there seems to randomly appear a positional difference between two of the objects in a row.

At first, I thought this was due to large chunks of code that were executed between the actual spawning and the start of the transition, but then I managed to reproduce the same problem in few lines :

local rectangleLoopTimer;
local counter = 0;
local rectangleArray = {}

local function rectangleLoop()

    counter = counter + 1

    local thisRectangle = display.newRect(1, 1, 216, 400)
    thisRectangle.anchorX = 0

    table.insert(rectangleArray, thisRectangle)

    transition.to(

        thisRectangle,

        {

            time = 5000,
            x = thisRectangle.x + 1080,

            onComplete = function()

                display.remove(thisRectangle)
                table.remove(rectangleArray, counter)

            end

        }

    )

end

rectangleLoopTimer = timer.performWithDelay(985, rectangleLoop, 0)

If one executes this, then one sees what I mean, so what do you think why this happens? I appreciate every answer!

Greetings, Nils

EDIT:

This also produces the same problem :

local rectangleLoopTimer;
local counter = 0
local rectangleArray = {}
local thisRectangle

local function rectangleLoop()

    counter = counter + 1

    thisRectangle = display.newRect(1, 1, 216, 400)
    thisRectangle.anchorX = 0
    thisRectangle.lastTime = 0
    thisRectangle.rate = 216
    table.insert(rectangleArray, thisRectangle)
    thisRectangle.lastTime = system.getTimer()

    thisRectangle.enterFrame = function(self, event)

        local curTime = system.getTimer()
        local dt = curTime - self.lastTime
        self.lastTime = curTime
        local dx = self.rate * dt / 1000
        self.x = self.x + dx

    end

    Runtime:addEventListener("enterFrame", thisRectangle)

end

rectangleLoopTimer = timer.performWithDelay(1000, rectangleLoop, 0)

RE-EDIT:

This code also produces the same problem, albeit using framerate independent animation. The issue is getting emphasized when increasing the speed of the loop as in the code below :

local loopSpeed =  306
local loopTimerSpeed = 1000
local gapTable = {}
local gapLoopTimer
local frameTime
local gap

--enterFrame for time only

    local function frameTime(event)

        frameTime = system.getTimer()

    end

--enterFrame

    local function enterFrame(self, event)

        local deltaTime = frameTime - self.time
        print(deltaTime/1000)
        self.time = frameTime
        local speed = self.rate * deltaTime / 1000
        self:translate(speed, 0)

    end

--loop speed function

local function setLoopSpeed(factor)

    loopSpeed = loopSpeed * factor
    loopTimerSpeed = loopTimerSpeed / factor

end

--set the loop speed

    setLoopSpeed(3)

--loop to create gaps

local function createGap()

    gap = display.newRect(1, 1, 308, 442)
    gap.time = system.getTimer()
    gap.anchorX = 1
    gap.anchorY = 0

    --animation

        gap.rate = loopSpeed
        gap.enterFrame = enterFrame
        Runtime:addEventListener("enterFrame", gap)

    --fill table for cleaning up

        table.insert(gapTable, gap)

    --cleaning up

        for i = #gapTable, 1, -1 do

            local thisGap = gapTable[i]

            if thisGap.x > display.contentWidth + 500 then

                display.remove(thisGap)
                table.remove(gapTable, i)
                Runtime:removeEventListener("enterFrame", thisGap)

            end

            thisGap = nil

        end

end

Runtime:addEventListener("enterFrame", frameTime)

gapLoopTimer = timer.performWithDelay(

    loopTimerSpeed,
    createGap,
    0

)

Solution

  • This is a very common problem with transitions, and [to me] a bug in Corona SDK. The important thing to note is how transitions work. Transitions are nothing else than a table with references to objects and information about what should be done to them each frame. Each frame such object is retrieved and current time is used to calculate the difference that should be applies to the values of the object, as specified in the transition itself. This basically means that if you ask Corona to move an object from x = 0 to x = 100 in time = 100. Each frame, Corona will take that information, take current time, and will calculate the x value of your object.

    The issue here is, that the current time taken, is current time at a time of calculation, and not time of the frame. It means, that if you have a lot of transitions, it could be quite a few milliseconds between first and last of the transitions within one frame. This will result in different positions within same frame.

    If Corona would take frame time [so time at the beginning of the frame] it would use the same value to calculate everything, and no matter how many objects you would be transitioning from A to B, all of them would appear in the same place in all of the frames.

    The easiest way to fix this, would be to handle transitions manually in enterFrame or use a library which does it for you, for example: AKTween.

    Hope this helps.

    EDIT: Based on your additional code and comments, I think this should work as you wanted. Please forgive me the code quality, I wrote it from memory and didn't test it in Corona.

    local rectangleLoopTimer;
    
    local allRectangles = display.newGroup()
    
    
    local lastTime = system.getTimer()
    
    local function enterFrame()
    
        local curTime = system.getTimer()
        local dt = curTime - lastTime
        lastTime = curTime
    
        for i = allRectangles.numChildren, 1 do
            local rect = allRectangles[i]
            local dx = rect.rate * dt / 1000
            rect.x = rect.x + dx
        end
    end
    
    Runtime:addEventListener("enterFrame", enterFrame)
    
    
    local function createRectangle()
    
        local thisRectangle = display.newRect(1, 1, 216, 400)
        thisRectangle.anchorX = 0
        thisRectangle.lastTime = 0
        thisRectangle.rate = 216
        allRectangles:insert(thisRectangle)
    end
    
    timer.performWithDelay(1000, createRectangle, 0)
    

    EDIT AFTER RE-EDIT of the post:

    You have time set in enterFrame listener, but you don't actually know when it's going to be called. I would not count on the order of functions called during enterFrame stage.