touch tracing in Fruit Ninja style

Hi,
I’d like to be able to add a trace of the touch while user swipes on the screen…
Something like they have it in Fruit Ninja.

I’m wondering how to approach this. I can draw a line - okay - but this leaves me with little options to customise it later. I mean, I can draw a white or black line, have it nicely fading out… But won’t do the “rainbow” slash or something like this.

So I’ve been thinking about using Particle Candy for it… then when the touch is moved, emit a series of particles from the line between the (event.x, event.y) and (lastX, lastY).
The problem is, with quite fast swipes the line created with the sprites/particles is kind of broken - it’s not smooth…
Anyone would have an advice of how should I approach this? Or has any idea of how to generate a “textured” line? [import]uid: 120659 topic_id: 33313 reply_id: 333313[/import]

The easiest way, I found, is to use Particle Candy to have a shaped emitter with two points; the first at the last touch point and the end at the current touch point. Customise the particle and emitter values to produce a short lived but dense set of particles along that emitter shape and have it autodestroy. I didn’t worry that the shape was too straight because of the lack of touch points during a fast swipe because the number of emitted particles would be relative to the length of the line between touch points, for example: the number of particles emitted would be the length of the line /4

If you want something more elegant you could use a bezier curve to calculate a smoother set of points between fast touch points, but there will be a performance penalty which I think you don’t need:

http://developer.coronalabs.com/code/bezier-curve-corona-sdk
http://developer.coronalabs.com/code/cat-mull-rom-open-and-closed [import]uid: 8271 topic_id: 33313 reply_id: 132300[/import]

The easiest way, I found, is to use Particle Candy to have a shaped emitter with two points; the first at the last touch point and the end at the current touch point. Customise the particle and emitter values to produce a short lived but dense set of particles along that emitter shape and have it autodestroy. I didn’t worry that the shape was too straight because of the lack of touch points during a fast swipe because the number of emitted particles would be relative to the length of the line between touch points, for example: the number of particles emitted would be the length of the line /4

If you want something more elegant you could use a bezier curve to calculate a smoother set of points between fast touch points, but there will be a performance penalty which I think you don’t need:

http://developer.coronalabs.com/code/bezier-curve-corona-sdk
http://developer.coronalabs.com/code/cat-mull-rom-open-and-closed [import]uid: 8271 topic_id: 33313 reply_id: 132300[/import]

Hi,

I’m interested also in doing something like you explain.
Do you tryed with Particle Candy?, is this the best choice?
[import]uid: 44013 topic_id: 33313 reply_id: 132625[/import]

Well, I tried (for now) to use just the plain images, not Particle Candy for the blade effect.
In my touch listener function I have the following piece of code:

[lua]function bgTouch(event)
if event.phase == “began” then
lastX, lastY = nil, nil
isSwipeEnded = false
end
if event.phase == “ended” or event.phase == “canceled” then
bladetip.x, bladetip.y = 0, 0
isSwipeEnded = true
end
if event.phase == “moved” then
if isSwipeEnded then
return
end
if swordSound == nil then
swordSound = audio.play(swordSwipe, {onComplete = function()
swordSound = nil
if not options.infiniteSlashes then
isSwipeEnded = true
end
end})
end
– check touches
bladetip.x, bladetip.y = event.x, event.y

if lastY ~= nil then
local dx, dy = math.abs(event.x - lastX), math.abs(event.y - lastY)

if (dx >= dy) then
local a = (1.0*(event.y - lastY))/(1.0*(event.x - lastX))
local b = lastY
local ang = math.atan2(event.y - lastY, event.x - lastX)
ang = ang/2/math.pi*360
for x=lastX,event.x,(event.x - lastX)/dx*5 do
local y = b + a * (x - lastX)
– print ("==> ", a, b, x - lastX, y)
local t = display.newImageRect(“bladeTrace.png”, 10, 30)
t.rotation = ang
t.x, t.y = x, y
bladetip.parent:insert(t)
transition.to(t, {time=500, alpha = 0, yScale = 0.1, transition = easing.inQuad, onComplete = function()
t:removeSelf()
end})
end
else
local a = (1.0*(event.x - lastX))/(1.0*(event.y - lastY))
local b = lastX
local ang = math.atan2(event.y - lastY, event.x - lastX)
ang = ang/2/math.pi*360

for y=lastY,event.y,(event.y - lastY)/dy*5 do
local x = b + a * (y - lastY)
– print ("==> ", a, b, x - lastX, y)
local t = display.newImageRect(“bladeTrace.png”, 10, 30)
t.rotation = ang
t.x, t.y = x, y
bladetip.parent:insert(t)
transition.to(t, {time=500, alpha = 0, yScale = 0.1, transition = easing.inQuad, onComplete = function()
t:removeSelf()
end})
end
end
end
lastX, lastY = event.x, event.y
end
end[/lua]

So I basically just create an image every 5 points of the trace and then scale it down and fade it out as time passes. This gives a nice result when I test it on a reasonably new device, like iPhone 4S.
However, when tested on the iPhone 3GS, the swipes lag the game a lot. So I might have to take some different approach or check if that lag is about the math above or about the display objects… [import]uid: 120659 topic_id: 33313 reply_id: 132627[/import]

you could try preloading a table of a number of particle images, then position and expose/hide them when needed. This will be faster than creating and removing them each time.

also, localize your functions

local atan2 = math.atan2
local pi= math.pi

doing that has been shown to speed things up [import]uid: 118333 topic_id: 33313 reply_id: 132650[/import]

Thanks for that. Had no idea that aliasing the functions in local namespace would be a speed up.

And great thought about having a table of particles. Thanks :slight_smile:

I might also pre-calculate a table of atan2 values, to keep them in memory and maybe use linear approximation for the calculation during runtime… I’ll experiment with it a bit :wink: [import]uid: 120659 topic_id: 33313 reply_id: 132662[/import]

Hi kender!

Thanks a lot for sharing!
I was trying your code and looks great.
What I’m using till now was this:

local function drawSlashLine(event)  
  
 -- Constants  
 local maxPoints = 3  
 local grosorCorte = 12  
 local lineFadeTime = 250  
  
 -- Play a slash sound  
 if(endPoints ~= nil and endPoints[1] ~= nil) then  
 local distance = math.sqrt(math.pow(event.x - endPoints[1].x, 2) + math.pow(event.y - endPoints[1].y, 2))  
 if (distance \> minDistanceForSlashSound and slashSoundEnabled == true) then   
 sonCorte();   
 slashSoundEnabled = false  
 timer.performWithDelay(minTimeBetweenSlashes, function(event) slashSoundEnabled = true end)  
 end  
 end  
  
 -- Insert a new point into the front of the array  
 table.insert(endPoints, 1, {x = event.x, y = event.y, line= nil})   
  
 -- Remove any excessed points  
 if(#endPoints \> maxPoints) then   
 table.remove(endPoints)  
 end  
  
 local posX = event.x  
 local posY = event.y  
  
 for i,v in ipairs(endPoints) do  
 local puntaAux = display.newCircle(0,0,grosorCorte)  
 puntaAux:setFillColor(100, 150, 190)  
 puntaAux.x = posX  
 puntaAux.y = posY  
 local trans3=transition.to(puntaAux, {time = lineFadeTime/5, alpha = 0, onComplete = function(event) puntaAux:removeSelf() end})  
 table.insert(transicions,trans3)  
  
 local lineAux = display.newLine(v.x, v.y, event.x, event.y)  
 lineAux.width = grosorCorte + 15  
 lineAux:setColor( 100, 150, 190, 255 )  
 local trans=transition.to(lineAux, {time = lineFadeTime/2, alpha = 0, width = 0, onComplete = function(event) lineAux:removeSelf() end})   
 table.insert(transicions,trans)  
  
 local line = display.newLine(v.x, v.y, event.x, event.y)  
 line.width = grosorCorte  
 local trans1=transition.to(line, {time = lineFadeTime, alpha = 0, width = 0, onComplete = function(event) line:removeSelf() end})   
 table.insert(transicions,trans1)  
  
 local punta = display.newCircle(0,0,grosorCorte-5)  
 punta:setFillColor(255,255,255)  
 punta.x = posX  
 punta.y = posY  
 local trans2=transition.to(punta, {time = lineFadeTime/5, alpha = 0, onComplete = function(event) punta:removeSelf() end})  
 table.insert(transicions,trans2)  
 end  
  
 if(event.phase == "ended") then   
 while(#endPoints \> 0) do  
 table.remove(endPoints)  
 end  
 end  
end  

but I think your choice is more smooth and looks fine. [import]uid: 44013 topic_id: 33313 reply_id: 132744[/import]

Hi,

I’m interested also in doing something like you explain.
Do you tryed with Particle Candy?, is this the best choice?
[import]uid: 44013 topic_id: 33313 reply_id: 132625[/import]

Well, I tried (for now) to use just the plain images, not Particle Candy for the blade effect.
In my touch listener function I have the following piece of code:

[lua]function bgTouch(event)
if event.phase == “began” then
lastX, lastY = nil, nil
isSwipeEnded = false
end
if event.phase == “ended” or event.phase == “canceled” then
bladetip.x, bladetip.y = 0, 0
isSwipeEnded = true
end
if event.phase == “moved” then
if isSwipeEnded then
return
end
if swordSound == nil then
swordSound = audio.play(swordSwipe, {onComplete = function()
swordSound = nil
if not options.infiniteSlashes then
isSwipeEnded = true
end
end})
end
– check touches
bladetip.x, bladetip.y = event.x, event.y

if lastY ~= nil then
local dx, dy = math.abs(event.x - lastX), math.abs(event.y - lastY)

if (dx >= dy) then
local a = (1.0*(event.y - lastY))/(1.0*(event.x - lastX))
local b = lastY
local ang = math.atan2(event.y - lastY, event.x - lastX)
ang = ang/2/math.pi*360
for x=lastX,event.x,(event.x - lastX)/dx*5 do
local y = b + a * (x - lastX)
– print ("==> ", a, b, x - lastX, y)
local t = display.newImageRect(“bladeTrace.png”, 10, 30)
t.rotation = ang
t.x, t.y = x, y
bladetip.parent:insert(t)
transition.to(t, {time=500, alpha = 0, yScale = 0.1, transition = easing.inQuad, onComplete = function()
t:removeSelf()
end})
end
else
local a = (1.0*(event.x - lastX))/(1.0*(event.y - lastY))
local b = lastX
local ang = math.atan2(event.y - lastY, event.x - lastX)
ang = ang/2/math.pi*360

for y=lastY,event.y,(event.y - lastY)/dy*5 do
local x = b + a * (y - lastY)
– print ("==> ", a, b, x - lastX, y)
local t = display.newImageRect(“bladeTrace.png”, 10, 30)
t.rotation = ang
t.x, t.y = x, y
bladetip.parent:insert(t)
transition.to(t, {time=500, alpha = 0, yScale = 0.1, transition = easing.inQuad, onComplete = function()
t:removeSelf()
end})
end
end
end
lastX, lastY = event.x, event.y
end
end[/lua]

So I basically just create an image every 5 points of the trace and then scale it down and fade it out as time passes. This gives a nice result when I test it on a reasonably new device, like iPhone 4S.
However, when tested on the iPhone 3GS, the swipes lag the game a lot. So I might have to take some different approach or check if that lag is about the math above or about the display objects… [import]uid: 120659 topic_id: 33313 reply_id: 132627[/import]

you could try preloading a table of a number of particle images, then position and expose/hide them when needed. This will be faster than creating and removing them each time.

also, localize your functions

local atan2 = math.atan2
local pi= math.pi

doing that has been shown to speed things up [import]uid: 118333 topic_id: 33313 reply_id: 132650[/import]

Thanks for that. Had no idea that aliasing the functions in local namespace would be a speed up.

And great thought about having a table of particles. Thanks :slight_smile:

I might also pre-calculate a table of atan2 values, to keep them in memory and maybe use linear approximation for the calculation during runtime… I’ll experiment with it a bit :wink: [import]uid: 120659 topic_id: 33313 reply_id: 132662[/import]

Hi kender!

Thanks a lot for sharing!
I was trying your code and looks great.
What I’m using till now was this:

local function drawSlashLine(event)  
  
 -- Constants  
 local maxPoints = 3  
 local grosorCorte = 12  
 local lineFadeTime = 250  
  
 -- Play a slash sound  
 if(endPoints ~= nil and endPoints[1] ~= nil) then  
 local distance = math.sqrt(math.pow(event.x - endPoints[1].x, 2) + math.pow(event.y - endPoints[1].y, 2))  
 if (distance \> minDistanceForSlashSound and slashSoundEnabled == true) then   
 sonCorte();   
 slashSoundEnabled = false  
 timer.performWithDelay(minTimeBetweenSlashes, function(event) slashSoundEnabled = true end)  
 end  
 end  
  
 -- Insert a new point into the front of the array  
 table.insert(endPoints, 1, {x = event.x, y = event.y, line= nil})   
  
 -- Remove any excessed points  
 if(#endPoints \> maxPoints) then   
 table.remove(endPoints)  
 end  
  
 local posX = event.x  
 local posY = event.y  
  
 for i,v in ipairs(endPoints) do  
 local puntaAux = display.newCircle(0,0,grosorCorte)  
 puntaAux:setFillColor(100, 150, 190)  
 puntaAux.x = posX  
 puntaAux.y = posY  
 local trans3=transition.to(puntaAux, {time = lineFadeTime/5, alpha = 0, onComplete = function(event) puntaAux:removeSelf() end})  
 table.insert(transicions,trans3)  
  
 local lineAux = display.newLine(v.x, v.y, event.x, event.y)  
 lineAux.width = grosorCorte + 15  
 lineAux:setColor( 100, 150, 190, 255 )  
 local trans=transition.to(lineAux, {time = lineFadeTime/2, alpha = 0, width = 0, onComplete = function(event) lineAux:removeSelf() end})   
 table.insert(transicions,trans)  
  
 local line = display.newLine(v.x, v.y, event.x, event.y)  
 line.width = grosorCorte  
 local trans1=transition.to(line, {time = lineFadeTime, alpha = 0, width = 0, onComplete = function(event) line:removeSelf() end})   
 table.insert(transicions,trans1)  
  
 local punta = display.newCircle(0,0,grosorCorte-5)  
 punta:setFillColor(255,255,255)  
 punta.x = posX  
 punta.y = posY  
 local trans2=transition.to(punta, {time = lineFadeTime/5, alpha = 0, onComplete = function(event) punta:removeSelf() end})  
 table.insert(transicions,trans2)  
 end  
  
 if(event.phase == "ended") then   
 while(#endPoints \> 0) do  
 table.remove(endPoints)  
 end  
 end  
end  

but I think your choice is more smooth and looks fine. [import]uid: 44013 topic_id: 33313 reply_id: 132744[/import]