Best approach for "shaking" my display group?

Hi All,

I’m interested in having my whole display group “shake” when certain objects in my game explode. What’s the best approach for doing this? Create a function that manages some .x and .y adjustments to the group over a short duration of time (and that recenters everything on completion). Is there an approach with transition.to that would be better?

I’ve done a hasty test and can definitely move the group with .x and .y shifts so that seems like the best approach but I wanted to check in and see if I was missing anything that would be more effective.

Interested to know what you all think the best approach is.

Thanks :slight_smile: [import]uid: 105707 topic_id: 35365 reply_id: 335365[/import]

I’ve got a shaking explode effect that basically blows up a display group so the elements go flying everywhere. Not the same as what you’re looking for, but you’re welcome to crib anything useful from it…

[code]

– 10/1/12 – explode.lua by mpappas

– Takes a display list and explodes it.
– pass in display list, and callBack function
– screen is messed up afterwards, intended for destroy group and destroy message functions
– (screen explodes, exits to callBack screen)


module(…, package.seeall)

– states
local STARTUP = 1
local EXPLODE_STAGE1 = 2
local EXPLODE_STAGE2 = 3
local FINISH = 4

– local global vars
local explodeState = STARTUP
local explodeScreen = nil
local callbackScreen = nil
local tickCount = 0

– local function prototypes
local doExplode
local dismissExplode


– init function – called to start the screen to explode, callback func when done

function explode(screen, callback)

explodeScreen = screen
callbackScreen = callback – save off vars…

tickCount = 0
explodeState = STARTUP

Runtime:addEventListener( “enterFrame”, doExplode ) – Called 30 times / second…

end

function doExplode(event) – enterFrame handler – does the actual exploding…

tickCount = tickCount + 1

if( explodeState == STARTUP ) then
– start the explosion SFX
audio.play( mojoData._ExplodeAudio1, { channel=mojoData._sfxChannel, loops=0, fadein=0 } ) – explosion sfx
explodeState = EXPLODE_STAGE1

elseif( explodeState == EXPLODE_STAGE1 ) then
if( tickCount < 80 ) then
local shakeX
local shakeY

– Shake everything around…
for index, value in pairs(explodeScreen) do
if( type(value) == “table” ) then
if( value.x ~= nil ) then
–print("value.x == ", value.x)
shakeX = (0.5 - math.random()) * (tickCount*1.2) – Get a shake value…
shakeY = (0.5 - math.random()) * (tickCount*1.2)
value.x = value.x + shakeX
value.y = value.y + shakeY
end
end
end
else
explodeState = EXPLODE_STAGE2
end
elseif( explodeState == EXPLODE_STAGE2 ) then

if( tickCount < 140 ) then
if( explodeScreen.alpha > 0 ) then
explodeScreen.alpha = explodeScreen.alpha - 0.02
end

– make everything fly offscreen
for index, value in pairs(explodeScreen) do
if( type(value) == “table” ) then
if( value.x ~= nil ) then
if( value.x < 320 ) then – fly off to nearest side
value.x = value.x - 16
else
value.x = value.x + 16
end
if( value.y < 320 ) then – fly off to nearest side
value.y = value.y - 16
else
value.y = value.y + 16
end
end
end
end
else
explodeState = FINISH
end
elseif( explodeState == FINISH ) then
explodeState = nil
dismissExplode()
end

return true
end

function dismissExplode() – All done, cleanup and callBack the calling screen

explodeScreen.isVisible = false
Runtime:removeEventListener( “enterFrame”, doExplode ) – Called 30 times / second…
callbackScreen(true) – inform caller we’re done – true means screenRefresh is true (we exploded screen after all…)
callbackScreen = nil
explodeScreen = nil

end
[/code] [import]uid: 79933 topic_id: 35365 reply_id: 140554[/import]

This is a little snippet from my logos quiz template that shakes the screen when you get an answer wrong, might be of some use. Just pass your main display group as a parameter.
[lua]local shakeObject = function (object)

transition.to(object, { time = 100, x = 5, y = 5 })

for a = 1, 3, 1 do
transition.to(object, { delay = 0 + (120 * a), time = 40, x = 3, y = 3 })
transition.to(object, { delay = 40 + (120 * a), time = 40, x = -3, y = 3 })
transition.to(object, { delay = 80 + (120 * a), time = 40, x = 0, y = 0 })
end

end

shakeObject(localGroup)[/lua] [import]uid: 93133 topic_id: 35365 reply_id: 140559[/import]

Here’s what I used for our game Infinite;

[lua]local rand = math.random

–Shake HUD on anti-matter hit
local function directHit()
if dHit == false then
dHit = true
tnt:newTransition( hud, {time = 50, x = rand(-60, 60), y = rand(-60, 60), transition=easing.inoutQuad})
tnt:newTransition( hud, {delay = 25, time = 50, x = rand(-60, 60), y = rand(-60, 60), transition=easing.inoutQuad})
tnt:newTransition( hud, {delay = 51, time = 50, x = rand(-60, 60), y = rand(-60, 60), transition=easing.inoutQuad})
tnt:newTransition( hud, {delay = 102, time = 50, x = 0, y = 0, transition = easing.inoutQuad, onComplete = function() dHit = false end})
end
end[/lua]

This will create a random shake every time.

Also, this was used with Lerg’s pausable timers and transitions module (http://developer.anscamobile.com/code/pausable-timers-and-transitions-speed-adjustment)
so just replace “tnt:newTransition” with “transition.to” if you’re not using it. [import]uid: 40731 topic_id: 35365 reply_id: 140569[/import]

Hi Guys,

Your input is awesome, I’ve got some great options with the code you’ve shared!

I’m using Nick’s technique for a quick solution (took me about 20 seconds to implement) but as I work off other features I’m going to revisit this and draw from all of these examples.

Do I need to be careful about transitions getting interrupted if I go that route? That does seem like one advantage to manually adjusting .x and .y position over time:
http://www.coronalabs.com/blog/2011/08/15/corona-sdk-memory-leak-prevention-101/
Thanks for your support :slight_smile: [import]uid: 105707 topic_id: 35365 reply_id: 140654[/import]

Yeah, I can’t recommend using my method with ‘transition.to’ for the fact they are interrupted. The transition module I use has it’s own method of cleaning up transitions and timers not being used, so I don’t have to worry about that. [import]uid: 40731 topic_id: 35365 reply_id: 140655[/import]

I’ve got a shaking explode effect that basically blows up a display group so the elements go flying everywhere. Not the same as what you’re looking for, but you’re welcome to crib anything useful from it…

[code]

– 10/1/12 – explode.lua by mpappas

– Takes a display list and explodes it.
– pass in display list, and callBack function
– screen is messed up afterwards, intended for destroy group and destroy message functions
– (screen explodes, exits to callBack screen)


module(…, package.seeall)

– states
local STARTUP = 1
local EXPLODE_STAGE1 = 2
local EXPLODE_STAGE2 = 3
local FINISH = 4

– local global vars
local explodeState = STARTUP
local explodeScreen = nil
local callbackScreen = nil
local tickCount = 0

– local function prototypes
local doExplode
local dismissExplode


– init function – called to start the screen to explode, callback func when done

function explode(screen, callback)

explodeScreen = screen
callbackScreen = callback – save off vars…

tickCount = 0
explodeState = STARTUP

Runtime:addEventListener( “enterFrame”, doExplode ) – Called 30 times / second…

end

function doExplode(event) – enterFrame handler – does the actual exploding…

tickCount = tickCount + 1

if( explodeState == STARTUP ) then
– start the explosion SFX
audio.play( mojoData._ExplodeAudio1, { channel=mojoData._sfxChannel, loops=0, fadein=0 } ) – explosion sfx
explodeState = EXPLODE_STAGE1

elseif( explodeState == EXPLODE_STAGE1 ) then
if( tickCount < 80 ) then
local shakeX
local shakeY

– Shake everything around…
for index, value in pairs(explodeScreen) do
if( type(value) == “table” ) then
if( value.x ~= nil ) then
–print("value.x == ", value.x)
shakeX = (0.5 - math.random()) * (tickCount*1.2) – Get a shake value…
shakeY = (0.5 - math.random()) * (tickCount*1.2)
value.x = value.x + shakeX
value.y = value.y + shakeY
end
end
end
else
explodeState = EXPLODE_STAGE2
end
elseif( explodeState == EXPLODE_STAGE2 ) then

if( tickCount < 140 ) then
if( explodeScreen.alpha > 0 ) then
explodeScreen.alpha = explodeScreen.alpha - 0.02
end

– make everything fly offscreen
for index, value in pairs(explodeScreen) do
if( type(value) == “table” ) then
if( value.x ~= nil ) then
if( value.x < 320 ) then – fly off to nearest side
value.x = value.x - 16
else
value.x = value.x + 16
end
if( value.y < 320 ) then – fly off to nearest side
value.y = value.y - 16
else
value.y = value.y + 16
end
end
end
end
else
explodeState = FINISH
end
elseif( explodeState == FINISH ) then
explodeState = nil
dismissExplode()
end

return true
end

function dismissExplode() – All done, cleanup and callBack the calling screen

explodeScreen.isVisible = false
Runtime:removeEventListener( “enterFrame”, doExplode ) – Called 30 times / second…
callbackScreen(true) – inform caller we’re done – true means screenRefresh is true (we exploded screen after all…)
callbackScreen = nil
explodeScreen = nil

end
[/code] [import]uid: 79933 topic_id: 35365 reply_id: 140554[/import]

This is a little snippet from my logos quiz template that shakes the screen when you get an answer wrong, might be of some use. Just pass your main display group as a parameter.
[lua]local shakeObject = function (object)

transition.to(object, { time = 100, x = 5, y = 5 })

for a = 1, 3, 1 do
transition.to(object, { delay = 0 + (120 * a), time = 40, x = 3, y = 3 })
transition.to(object, { delay = 40 + (120 * a), time = 40, x = -3, y = 3 })
transition.to(object, { delay = 80 + (120 * a), time = 40, x = 0, y = 0 })
end

end

shakeObject(localGroup)[/lua] [import]uid: 93133 topic_id: 35365 reply_id: 140559[/import]

Here’s what I used for our game Infinite;

[lua]local rand = math.random

–Shake HUD on anti-matter hit
local function directHit()
if dHit == false then
dHit = true
tnt:newTransition( hud, {time = 50, x = rand(-60, 60), y = rand(-60, 60), transition=easing.inoutQuad})
tnt:newTransition( hud, {delay = 25, time = 50, x = rand(-60, 60), y = rand(-60, 60), transition=easing.inoutQuad})
tnt:newTransition( hud, {delay = 51, time = 50, x = rand(-60, 60), y = rand(-60, 60), transition=easing.inoutQuad})
tnt:newTransition( hud, {delay = 102, time = 50, x = 0, y = 0, transition = easing.inoutQuad, onComplete = function() dHit = false end})
end
end[/lua]

This will create a random shake every time.

Also, this was used with Lerg’s pausable timers and transitions module (http://developer.anscamobile.com/code/pausable-timers-and-transitions-speed-adjustment)
so just replace “tnt:newTransition” with “transition.to” if you’re not using it. [import]uid: 40731 topic_id: 35365 reply_id: 140569[/import]

Hi Guys,

Your input is awesome, I’ve got some great options with the code you’ve shared!

I’m using Nick’s technique for a quick solution (took me about 20 seconds to implement) but as I work off other features I’m going to revisit this and draw from all of these examples.

Do I need to be careful about transitions getting interrupted if I go that route? That does seem like one advantage to manually adjusting .x and .y position over time:
http://www.coronalabs.com/blog/2011/08/15/corona-sdk-memory-leak-prevention-101/
Thanks for your support :slight_smile: [import]uid: 105707 topic_id: 35365 reply_id: 140654[/import]

Yeah, I can’t recommend using my method with ‘transition.to’ for the fact they are interrupted. The transition module I use has it’s own method of cleaning up transitions and timers not being used, so I don’t have to worry about that. [import]uid: 40731 topic_id: 35365 reply_id: 140655[/import]

Thought i will just shared this code snippet, which is based on nick’s code.

function will take in the ‘image object’, and also a table to store the transitions. It may not ‘shake’ enough, as I set it to only 2. Make this value bigger if you want it to shake further/more.

 function shakeObject( img, transitionsUsed ) local object = img local tmpOriXPos = object.x local tmpOriYPos = object.y transition.to(object, { time = 100, x = object.x , y = object.y + 2 }) for a = 1, 3, 1 do local tmpTransition1 = transition.to(object, { delay = 0 + (120 \* a), time = 40, x = object.x + 2, y = object.y + 2 }) table.insert( transitionsUsed, tmpTransition1 ) local tmpTransition2 = transition.to(object, { delay = 40 + (120 \* a), time = 40, x = object.x - 2, y = object.y + 2 }) table.insert( transitionsUsed, tmpTransition2 ) local tmpTransition3 = transition.to(object, { delay = 80 + (120 \* a), time = 40, x = tmpOriXPos, y = tmpOriYPos }) table.insert( transitionsUsed, tmpTransition3 ) end end

Sample usage :

local transitionTable  = {}

local myImage = newImageRect(‘your image’)

local tmpTimer = timer.performWithDelay( 5000, function() shakeObject( myImage, transitionTable ); end, 0  )

Hi All,

above solutions make either use of a runtime listener or multiple transitions mutually calling themselves via onComplete.

I found enterframe listeners can slow down your program pretty easy. And multiple transitions can be inconvenient when you need to stop them.

So in my opinion the safer way to achieve shaking or oscillating is using an appropriate easing function. Sadly the standard easing library doesn’t contain one, so here’s mine:

local function easeSin(f,a)
  return function(t, tMax, start, delta)
    return start + delta + a*math.sin( (t/tMax) *f * math.pi*2)
  end
end M.easeSin = easeSin

you could of course use other oscillating functions (which could be faster to evaluate than math.sin or more suitable)

then you can shake/oscillate things like that:

local function oscillate(f, a, axis, howlong)
  return function(thing)
    transition.to( thing, {time=howlong, delta=true, [axis]=0, transition=easeSin(f,a)} )
  end
end

oscillate( 100, 10, ‘y’, 1000 ) (thing)

hope this helps, Johannes

Thought i will just shared this code snippet, which is based on nick’s code.

function will take in the ‘image object’, and also a table to store the transitions. It may not ‘shake’ enough, as I set it to only 2. Make this value bigger if you want it to shake further/more.

 function shakeObject( img, transitionsUsed ) local object = img local tmpOriXPos = object.x local tmpOriYPos = object.y transition.to(object, { time = 100, x = object.x , y = object.y + 2 }) for a = 1, 3, 1 do local tmpTransition1 = transition.to(object, { delay = 0 + (120 \* a), time = 40, x = object.x + 2, y = object.y + 2 }) table.insert( transitionsUsed, tmpTransition1 ) local tmpTransition2 = transition.to(object, { delay = 40 + (120 \* a), time = 40, x = object.x - 2, y = object.y + 2 }) table.insert( transitionsUsed, tmpTransition2 ) local tmpTransition3 = transition.to(object, { delay = 80 + (120 \* a), time = 40, x = tmpOriXPos, y = tmpOriYPos }) table.insert( transitionsUsed, tmpTransition3 ) end end

Sample usage :

local transitionTable  = {}

local myImage = newImageRect(‘your image’)

local tmpTimer = timer.performWithDelay( 5000, function() shakeObject( myImage, transitionTable ); end, 0  )

Hi All,

above solutions make either use of a runtime listener or multiple transitions mutually calling themselves via onComplete.

I found enterframe listeners can slow down your program pretty easy. And multiple transitions can be inconvenient when you need to stop them.

So in my opinion the safer way to achieve shaking or oscillating is using an appropriate easing function. Sadly the standard easing library doesn’t contain one, so here’s mine:

local function easeSin(f,a)
  return function(t, tMax, start, delta)
    return start + delta + a*math.sin( (t/tMax) *f * math.pi*2)
  end
end M.easeSin = easeSin

you could of course use other oscillating functions (which could be faster to evaluate than math.sin or more suitable)

then you can shake/oscillate things like that:

local function oscillate(f, a, axis, howlong)
  return function(thing)
    transition.to( thing, {time=howlong, delta=true, [axis]=0, transition=easeSin(f,a)} )
  end
end

oscillate( 100, 10, ‘y’, 1000 ) (thing)

hope this helps, Johannes

Thanks for posting that approach johannes - that’s very cool and definitely seems safer!

Hey there,

What I usually do is, every frame (so in the enterFrame.loop), run something like:

if cameraShake \> 0 then group.x = math.random(cameraShake) group.y = math.random(cameraShake) cameraShake = cameraShake -1 end

And whenever you want to trigger a camera jolt movement just set cameraShake to something like 15.

Cheers,

Thomas

Thanks for posting that approach johannes - that’s very cool and definitely seems safer!

Hey there,

What I usually do is, every frame (so in the enterFrame.loop), run something like:

if cameraShake \> 0 then group.x = math.random(cameraShake) group.y = math.random(cameraShake) cameraShake = cameraShake -1 end

And whenever you want to trigger a camera jolt movement just set cameraShake to something like 15.

Cheers,

Thomas

Johannes, your solution is quite elegant. I tried to shake both ‘x’ and ‘y’ but passing ‘xy’ or ‘x,y’ does not work…