Bezier Curve in Transition Function

@StarCrunch

Here is a little more context

[lua]

function randomHatMove()

    local randm1 = math.floor(math.random(1,5))

    local randm2 = math.floor(math.random(1,5))

    if (randm1 == randm2) then

        if (randm1 == 1 ) then

            randm1 = randm1 + 1

        elseif (randm1 > 1) then

            randm1 = randm1 - 1

        end

    end

    local h1 = hats[randm1]

    local h2 = hats[randm2]

    totalMoves = totalMoves - 1

    

    h1.t = 0 – Give this to one of the objects

    transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) – Interpolate the time

    transition.to(h2, { time = moveSpeed, rotation = rotation_negative, transition = easing.inQuad, onComplete = checkMovesLeft })

    – Cache the state as upvalues.

    local x1, y1 = h1.x, h1.y

    local x2, y2 = h2.x, h2.y

    local midx, midy = .5 * (x1 + x2), .5 * (y1 + y2)

    local to_midx, to_midy = midx - x1, midy - y1

    – Perpendiculars: Depends how the points are laid out, assuming leftish to rightish

    local overx, overy = midx - to_midy, midy + to_midx

    local underx, undery = midx + to_midy, midy - to_midx

    – Give this to the one with the “onComplete” logic

    h2.update_curves = timer.performWithDelay(5, function()

    

      – Coefficients

      local t = h1.t

      local s = 1 - t

      local a = s * s

      local b = 2 * s * t

      local c = t * t

      h1.x, h1.y = x1 * a + overx * b + x2 * c, y1 * a + overy * b + y2 * c

      h2.x, h2.y = x2 * a + underx * b + x1 * c, y2 * a + undery * b + y1 * c

    end, 0)

    ball.isVisible = false

end

–playBtn:addEventListener(“tap”, randomHatMove)

function checkMovesLeft(h)

    if(totalMoves > 0) then

        randomHatMove()

        timer.cancel(h.update_curves) – Kill the timer

    else

        hat1:addEventListener(‘tap’, revealBall)

        hat2:addEventListener(‘tap’, revealBall)

        hat3:addEventListener(‘tap’, revealBall)

        hat4:addEventListener(‘tap’, revealBall)

        hat5:addEventListener(‘tap’, revealBall)

        timer.cancel(h.update_curves) – Kill the timer

        totalMoves = 15

    end

end

[/lua]

Also thank you so much. This has been killing me for a while now!

I’m not sure about the glitching, but it looks like the not moving is because the timer gets canceled right after randomHatMove() gets called in checkMovesLeft(). Does the first move work?

Actually, since you’re keeping track of the move count, an enterFrame listener might be a more natural place to update the positions anyhow; timers are just a personal habit. :slight_smile:

Where would you place the Event Listener at in the checkMovesLeft() function?

@StarCrunch

How would you write the code out?

Im sorry I just haven’t been able to figure it out and its getting to me  :frowning:

Hi.

I’d amend the above code as

-- Current hats -- local Hat1, Hat2 -- Bezier polygons -- local x1, y1, x2, y2, overx, overy, underx, undery function randomHatMove() local randm1 = math.floor(math.random(1,5)) local randm2 = math.floor(math.random(1,5)) if (randm1 == randm2) then if (randm1 == 1 ) then randm1 = randm1 + 1 elseif (randm1 \> 1) then randm1 = randm1 - 1 end end local h1 = hats[randm1] local h2 = hats[randm2] totalMoves = totalMoves - 1 h1.t = 0 -- Give this to one of the objects transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) -- Interpolate the time transition.to(h2, { time = moveSpeed, rotation = rotation\_negative, transition = easing.inQuad, onComplete = checkMovesLeft }) -- Cache some position state (since the positions themselves change as the curve updates). x1, y1 = h1.x, h1.y x2, y2 = h2.x, h2.y local midx, midy = .5 \* (x1 + x2), .5 \* (y1 + y2) local to\_midx, to\_midy = midx - x1, midy - y1 -- Perpendiculars: Depends how the points are laid out, assuming leftish to rightish overx, overy = midx - to\_midy, midy + to\_midx underx, undery = midx + to\_midy, midy - to\_midx -- Tell the enterFrame listener which hats we're watching. Hat1, Hat2 = h1, h2 ball.isVisible = false end --playBtn:addEventListener("tap", randomHatMove) function checkMovesLeft(h) if(totalMoves \> 0) then randomHatMove() else hat1:addEventListener('tap', revealBall) hat2:addEventListener('tap', revealBall) hat3:addEventListener('tap', revealBall) hat4:addEventListener('tap', revealBall) hat5:addEventListener('tap', revealBall) totalMoves = 15 end end Runtime:addEventListener("enterFrame", function() if totalMoves \> 0 then -- Coefficients local t = Hat1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t Hat1.x, Hat1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c Hat2.x, Hat2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end end)

For reference, is this roughly the behavior you would like to see?

local h1 = display.newRect(50, 50, 100, 100) local h2 = display.newRect(400, 200, 100, 100) h1.t = 0 -- Give this to one of the objects local moveSpeed = 5000 local rotation = 180 local rotation\_negative = -180 transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) -- Interpolate the time transition.to(h2, { time = moveSpeed, rotation = rotation\_negative, transition = easing.inQuad }) -- Cache the state as upvalues. local x1, y1 = h1.x, h1.y local x2, y2 = h2.x, h2.y local midx, midy = .5 \* (x1 + x2), .5 \* (y1 + y2) local to\_midx, to\_midy = midx - x1, midy - y1 -- Perpendiculars: Depends how the points are laid out, assuming leftish to rightish local overx, overy = midx - to\_midy, midy + to\_midx local underx, undery = midx + to\_midy, midy - to\_midx Hat1, Hat2 = h1, h2 Runtime:addEventListener("enterFrame", function() if Hat1.t \>= 1 then return end -- Coefficients local t = Hat1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t Hat1.x, Hat1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c Hat2.x, Hat2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end)

Its giving me the error " attempting to index upvalue Hat1 ( a nil value)"

Any ideas? Also thank you so much

My guess is that totalMoves starts as 15? In that case, “if totalMoves > 0 then” is not the right test to be doing in the enterFrame listener (since it would already “succeed” before we’ve moved any hats). Try it with “if Hat1 then” instead, and clear the hats when you set totalMoves back to 15 (“Hat1, Hat2 = nil”).

The hats now move properly, but the ball pops up under the wrong hat! Im about to go crazy on this one. 

[lua]

–Start Movement---------------------------------------------------------------------------------------

function upHatAnimation()

    transition.to( hat3, { time = 500, y = hat3.y - 150})

    local function downHatAnimation ()

        transition.to( hat3, { time = 500, y = hat3.y + 150, onComplete = randomHatMove})

    end

    timer.performWithDelay( 3000, downHatAnimation, 1)

    playBtn.alpha = 0.5

    playBtn:removeEventListener(“tap”, upHatAnimation)

    textBoxStart.isVisible = false

    textBoxBlank.isVisible = true

    textBoxRight.isVisible = false

    textBoxWrong.isVisible = false

    textBoxPick.isVisible = false

    playBtn:removeEventListener(“tap”, playBtn)

    --backArrow:removeEventListener(“tap”, gotoGame)

end

playBtn:addEventListener(“tap”, upHatAnimation)

–Movement---------------------------------------------------------------------------------------

local currentHat1, currentHat2

function randomHatMove()

    local randm1 = math.floor(math.random(1,5))

    local randm2 = math.floor(math.random(1,5))

    if (randm1 == randm2) then

        if (randm1 == 1 ) then

            randm1 = randm1 + 1

        elseif (randm1 > 1) then

            randm1 = randm1 - 1

        end

    end

    local h1 = hats[randm1]

    local h2 = hats[randm2]

    totalMoves = totalMoves - 1

    

    h1.t = 0 – Give this to one of the objects

    transition.to(h1, { time = moveSpeed, rotation = rotation, t = 1, transition = easing.inQuad }) – Interpolate the time

    transition.to(h2, { time = moveSpeed, rotation = rotation_negative, transition = easing.inQuad, onComplete = checkMovesLeft })

    – Cache some position state (since the positions themselves change as the curve updates).

    x1, y1 = h1.x, h1.y

    x2, y2 = h2.x, h2.y

    local midx, midy = .5 * (x1 + x2), .5 * (y1 + y2)

    local to_midx, to_midy = midx - x1, midy - y1

    – Perpendiculars: Depends how the points are laid out, assuming leftish to rightish

    overx, overy = midx - to_midy, midy + to_midx

    underx, undery = midx + to_midy, midy - to_midx

    – Tell the enterFrame listener which hats we’re watching.

    currentHat1, currentHat2 = h1, h2

    ball.isVisible = false

end

–playBtn:addEventListener(“tap”, randomHatMove)

function checkMovesLeft(h)

     

     function checkHatMovement ()

      if totalMoves > 0 then

          – Coefficients

          local t = currentHat1.t

          local s = 1 - t

          local a = s * s

          local b = 2 * s * t

          local c = t * t

    

          currentHat1.x, currentHat1.y = x1 * a + overx * b + x2 * c, y1 * a + overy * b + y2 * c

          currentHat2.x, currentHat2.y = x2 * a + underx * b + x1 * c, y2 * a + undery * b + y1 * c

      end

    end

    Runtime:addEventListener(“enterFrame”, checkHatMovement)

   

    if(totalMoves > 0) then

        timer.performWithDelay(100, randomHatMove, 1)

    else

        hat1:addEventListener(‘tap’, revealBall)

        hat2:addEventListener(‘tap’, revealBall)

        hat3:addEventListener(‘tap’, revealBall)

        hat4:addEventListener(‘tap’, revealBall)

        hat5:addEventListener(‘tap’, revealBall)

        Runtime:removeEventListener(“enterFrame”, checkHatMovement)

        totalMoves = 15

    end

end

–Reveal Ball---------------------------------------------------------------------------------------

function revealBall(e)

    textBoxBlank.isVisible = false    

    textBoxPick.isVisible = true

    hat1:removeEventListener(‘tap’, revealBall)

    hat2:removeEventListener(‘tap’, revealBall)

    hat3:removeEventListener(‘tap’, revealBall)

    hat4:removeEventListener(‘tap’, revealBall)

    hat5:removeEventListener(‘tap’, revealBall)

    ball.x = hat3.x + 130

    ball.y = hat3.y + 250

    ball.isVisible = true

    if ( e.target.name == ‘finalHat’ ) then

        textBoxPick.isVisible = false

        textBoxRight.isVisible = true

        transition.to( hat3, { time = 500, y = hat3.y - 150})

        local function downHatAnimation_end ()

            transition.to( hat3, { time = 500, y = hat3.y + 150})

            playBtn:addEventListener(“tap”, upHatAnimation)

            playBtn.alpha = 1

        end

        timer.performWithDelay( 1500, downHatAnimation_end, 1)

    else

        textBoxPick.isVisible = false

        textBoxWrong.isVisible = true

        transition.to( hat3, { time = 500, y = hat3.y - 150})

        local function downHatAnimation_end ()

            transition.to( hat3, { time = 500, y = hat3.y + 150})

            playBtn.alpha = 1

            playBtn:addEventListener(“tap”, upHatAnimation)

            --backArrow:addEventListener(“tap”, gotoGame)

        end

        timer.performWithDelay( 1500, downHatAnimation_end, 1)

    end

end

[/lua]

Any idea whatsoever?

I don’t know if this is related, but you want to pull your checkHatMovement() function outside of checkMovesLeft()… otherwise it’s getting redefined every time you enter that function and you’ll pile up 15 unique listeners.  :slight_smile: It’s “harmless” because it only does the same assignment over and over, but the only event listener that will get removed is the one you just added, so it’s possible that’s causing some subtle error, e.g. immediately interpolating to the next hat.

When I pull out checkHatMovement(), it tells me that " Attempt to index ‘currentHat’ (a nil value)" Any idea?

If that wasn’t a typo, then you just forgot the “1” on currentHat1, so you’re accessing a non-existent variable.

Otherwise… did you move the function / end part outside, too? If you just moved the code out, it would be in file scope and get called while the script is loading, which is a bit early.  :slight_smile:

Basically, is it like this?

 function checkHatMovement () if totalMoves \> 0 then -- Coefficients local t = currentHat1.t local s = 1 - t local a = s \* s local b = 2 \* s \* t local c = t \* t currentHat1.x, currentHat1.y = x1 \* a + overx \* b + x2 \* c, y1 \* a + overy \* b + y2 \* c currentHat2.x, currentHat2.y = x2 \* a + underx \* b + x1 \* c, y2 \* a + undery \* b + y1 \* c end end function checkMovesLeft(h) Runtime:addEventListener("enterFrame", checkHatMovement) -- ...and so on

That works not, but is there a way to make it only check the function once every move? RIght now it is infinitely checking the movements, causing memory errors.

Ah, it’s probably still doing that pile-up, just a little slower. It looks like upHatAnimation is where you kick the whole thing off? If so, try adding the enterFrame listener there, instead. (It looks like then it should only get added once, and removed when it’s all done.)

Its still giving me the " Attempt to index ‘currentHat1’ (a nil value)"

Ah, right, there’s the timer.

Well, if you do want to add the listener in checkMovesLeft(), maybe it would suffice to guard it with “if totalMoves == 15 then” (it starts at 15, right?).

An alternative would be, instead of passing randomHatMove directly as the onComplete parameter in upHatAnimation(), instead pass a function that adds the listener first, then calls randomHatMove().

Sorry this is such a late response!!! So sometimes when I check click on the hat, the animation bugs out, and even moves the ball sometimes. I believe this has to do with the timer not fully stopping, but dont know how to fix. Any ideas?

Ah, nuts.

Can you be more specific about the “sometimes”? And the “animation bugs out”? I’ve also gotten a bit lost as far as what the current shape of the code is. :slight_smile:

An alternate technique, instead of the timer, which I hadn’t considered before (I saw the basic idea in some of roaminggamer’s code), would be to use a proxy with metatables. This relies on the assumption (which seems to be valid) that the transition is doing this:

t[k] = t[k] + delta  

in the background (thus triggering __index and __newindex if k is undefined, which we achieve by the whole “proxy” technique, cf. Programming in Lua). Anyhow, this avoids the need to manage the timers (although not transitions, if you ever need to cancel them).

Anyhow, it would be something like (untested):

-- Metatable local ProxyMT = { \_\_index = function(proxy, \_) -- Ignore key unless our proxy gets more complicated return proxy.\_t end, \_\_newindex = function(proxy, \_, t) -- Ditto proxy.\_t = t -- Do Bezier stuff here (at time t) end } -- Transition params local ProxyParams = { t = 1, -- Interpolate t to 1 onStart = function(proxy) rawset(proxy, "\_t", 0) -- Reset this on a fresh proxy end } local function LaunchProxy (proxy) proxy = proxy or setmetatable({ count = 15 }, ProxyMT) -- Might be convenient to keep the counter in here... not really important; anyhow, if we're doing this from an onComplete, just reuse the proxy, otherwise create a new one proxy.count = proxy.count - 1 -- Pick your hats, set currentHat1, currentHat2... transition.to(proxy, ProxyParams) end -- This was deferred so we can reference LaunchProxy here function ProxyParams.onComplete (proxy) if proxy.count == 0 then -- We're done... reveal ball, etc. else LaunchProxy(proxy) end end

Then kick it off with a LaunchProxy() (no arguments). Note that the proxy itself is only a dummy object to update the time parameter. You’d still fire off your normal transitions on the hats for rotation and what not, probably at the same time.

Anyhow, see if that helps.

Sorry this is such a late response!!! So sometimes when I check click on the hat, the animation bugs out, and even moves the ball sometimes. I believe this has to do with the timer not fully stopping, but dont know how to fix. Any ideas?

Ah, nuts.

Can you be more specific about the “sometimes”? And the “animation bugs out”? I’ve also gotten a bit lost as far as what the current shape of the code is. :slight_smile:

An alternate technique, instead of the timer, which I hadn’t considered before (I saw the basic idea in some of roaminggamer’s code), would be to use a proxy with metatables. This relies on the assumption (which seems to be valid) that the transition is doing this:

t[k] = t[k] + delta  

in the background (thus triggering __index and __newindex if k is undefined, which we achieve by the whole “proxy” technique, cf. Programming in Lua). Anyhow, this avoids the need to manage the timers (although not transitions, if you ever need to cancel them).

Anyhow, it would be something like (untested):

-- Metatable local ProxyMT = { \_\_index = function(proxy, \_) -- Ignore key unless our proxy gets more complicated return proxy.\_t end, \_\_newindex = function(proxy, \_, t) -- Ditto proxy.\_t = t -- Do Bezier stuff here (at time t) end } -- Transition params local ProxyParams = { t = 1, -- Interpolate t to 1 onStart = function(proxy) rawset(proxy, "\_t", 0) -- Reset this on a fresh proxy end } local function LaunchProxy (proxy) proxy = proxy or setmetatable({ count = 15 }, ProxyMT) -- Might be convenient to keep the counter in here... not really important; anyhow, if we're doing this from an onComplete, just reuse the proxy, otherwise create a new one proxy.count = proxy.count - 1 -- Pick your hats, set currentHat1, currentHat2... transition.to(proxy, ProxyParams) end -- This was deferred so we can reference LaunchProxy here function ProxyParams.onComplete (proxy) if proxy.count == 0 then -- We're done... reveal ball, etc. else LaunchProxy(proxy) end end

Then kick it off with a LaunchProxy() (no arguments). Note that the proxy itself is only a dummy object to update the time parameter. You’d still fire off your normal transitions on the hats for rotation and what not, probably at the same time.

Anyhow, see if that helps.

Sorry to bring this back, but I have a new question! How would I replace with an image, such as one of the hats, with another image? So the player could change the hat graphic. Thanks and sorry!