Move a physical ball with the touch

hi guy!

I have a physical ball and I would like to do this:

As the user moves his finger across the screen, the ball dies accordingly.

The ball must have the direction of touch and power (based on user stroke speed)

I know that to do this I have to use a physical impulse but what I do not know is the math to insert in the touch event.

Here is the video of a game that gives an idea of what I want to do.(0:12-0:25):

https://www.youtube.com/watch?time_continue=21&v=D2cItHrlUHU

can you give me a hand?

Try a touch joint:

Hi @roaminggamer

1.wow! Thank you so much for the quick response

2.My fault. I did not say that I would not use the joints. For two reasons:

  • I think that with math I could manage some aspects
  • As you can see in the video the ball stays still until the user finishes a stretch. if I used a joint and did:“tmpJoint:setTarget( event.x, event.y )” in moved phase then the ball would move immediately but I want it to move only when the user has finished. if instead I do it only in the final phase the ball may not have the thrust that…

Actually, I just downloaded tigerball and a touch joint won’t help.

The essential mechanic steps are:

  • touch begin:
    • collect t0 = system.getTimer() (Note: event has a time in it so you can use that too)
  • touch end:
    • t1 - system.getTimer() (Note: event has a time in it so you can use that too)
    • dt = (t1 - t0)/1000
    • dx = event.x - event.xStart 
    • dy = event.y - event.yStart 
    • distance = math.sqrt( dx * dx + dy * dy )
    • factor = choose your own value
    • speed limit = choose your own value
    • speed = factor * distance/dt
    • if speed > speed limit, cap at speed limit
    • vec = normalize the vector <dx,dy>
    • scale the vector vec = <vec.x * speed, vec.y * speed>
    • apply a linear impulse to the ball center with magnitude <vec.x , vec.y>

Thanks really fantastic. Just what I wanted!

I did a test but there is something I do not understand.

local ball = display.newCircle( 160, 100, 20 ) ball:setFillColor( 1, 0, 0 ) physics.addBody(ball, "dynamic", {radius=ballRadius}) local t0 local t1 local factor = 1 local speedLimit = 10 function ball:touch( event ) if(event.phase=="began")then display.getCurrentStage( ):setFocus( self ) self.isFocus = true t0 = system.getTimer() elseif(self.isFocus)then if(event.phase=="moved")then elseif((event.phase=="ended")or(event.phase=="cancelled")) then display.getCurrentStage( ):setFocus( nil ) self.isFocus = nil t1 = system.getTimer() local dt = (t1 - t0)/1000 local dx = event.x - event.xStart local dy = event.y - event.yStart local distance = math.sqrt( dx \* dx + dy \* dy ) local speed = factor \* distance/dt if (speed \> speedLimit)then speed = speedLimit end local vecX, vecY = math.abs(dx), math.abs(dy) local impulseX, impulseY = vecX\*speed, vecY\*speed self:applyLinearImpulse( self.x, self.y, impulseX, impulseY ) end end end ball:addEventListener( "touch" )

if I for example:

1.the event begins

2.I execute the stroke

3.I wait a few seconds

4.release

the ball receives a huge impulse instead of a low one

Did I miss something?

That sounds wrong.  If you wait to lift your finger, dt should be very large making the speed (impulse magnitude very small).

I’ll look over your code again in a moment.

Tip: Paste to notepad and replace tabs with three spaces before then copying and pasting the modified code here.  It makes the code more legible.

  • You didn’t normalize correctly.

  • Your call to applyLinearImpulse is backwards, you have the forces in the wrong place: 

    object:applyLinearImpulse( xForce, yForce, bodyX, bodyY )

  • You didn’t include the object mass.  (If you don’t do this, then later change the size of the ball you’ll need to re-balance your factor calculation.)

  • I made some changes to the original recipe.

https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/06/tigerBall.zip

local ballRadius = 20 local ball = display.newCircle( cx, bottom - 80, ballRadius ) ball.factor = 0.06 ball.limit = 50 ball:setFillColor( 1, 0, 0 ) physics.addBody( ball, "dynamic", { radius = ballRadius, bounce = 0 } ) ball.linearDamping = 1 function ball.enterFrame(self) display.remove(self.line) if( not self.lx ) then return end self.line = display.newLine( self.x, self.y, self.lx, self.ly ) self.line:setStrokeColor(1,1,0) self.line.strokeWidth = 4 end Runtime:addEventListener( "enterFrame", ball ) function ball.finalize( self ) Runtime:removeEventListener( "enterFrame", self ) end ball:addEventListener("finalize") local ground = display.newRect( cx, bottom - 50, fullw, 40) physics.addBody( ground, "static") function ball:touch( event ) local id = event.id if(event.phase=="began") then display.getCurrentStage( ):setFocus( self, id ) self.isFocus = true -- self.t0 = system.getTimer() -- elseif(self.isFocus) then if(event.phase=="moved") then self.lx = event.x self.ly = event.y elseif((event.phase=="ended")or(event.phase=="cancelled")) then display.getCurrentStage( ):setFocus( self, nil ) self.isFocus = nil -- local dt = (system.getTimer() - self.t0)/1000 -- local dx = event.x - event.xStart local dy = event.y - event.yStart print(dx,dy) local distance = math.sqrt( dx \* dx + dy \* dy ) -- This is how you normalize (not by using math.abs()): local vx = dx/distance local vy = dy/distance -- local magnitude = self.factor \* distance/dt --print( distance, dt, magnitude, self.t0, system.getTimer(), dx, dy ) magnitude = (magnitude \> self.limit) and self.limit or magnitude magnitude = magnitude \* self.mass -- self:applyLinearImpulse( vx \* magnitude, vy \* magnitude, self.x, self.y ) -- self.lx = nil self.ly = nil -- end end end ball:addEventListener( "touch" )

https://www.youtube.com/watch?v=7np_XgPwgRE&feature=youtu.be

Really fantastic now we are here!

And the question of mass would be my next question…

Really thanks again

Hi.

could I ask one last question?

with your project if I “tap” the ball, both (the ball and the ground) will disappear.

I understand that with those values a tap face splashed the ball (really short time and non-existent distance), but why does the background disappear?

I have already corrected the mistake by checking the positions in touch event only I do not understand this thing of the ground…

A distance of 0 causes division by-zero.

Make this change:

 local dx = event.x - event.xStart local dy = event.y - event.yStart local distance = math.sqrt( dx \* dx + dy \* dy ) if( distance \> 0.01 ) then -- This is how you normalize (not by using math.abs()): local vx = dx/distance local vy = dy/distance -- local magnitude = self.factor \* distance/dt --print( distance, dt, magnitude, self.t0, system.getTimer(), dx, dy ) magnitude = (magnitude \> self.limit) and self.limit or magnitude magnitude = magnitude \* self.mass -- self:applyLinearImpulse( vx \* magnitude, vy \* magnitude, self.x, self.y ) end -- self.lx = nil self.ly = nil

Thanks again!

Still here to ask for advice.

Currently if I run a long stretch(more than a few seconds) and after release, the ball will have a small push.

I know this happens because I have sampled a lot of time.

While playing tiger ball it seems that only the last stretch is considered.

So to do this I am storing in a table all the positions it makes in the last 1-2 seconds then I delete all the previous ones.

This all around works. But I’d love to hear what you think and how you can solve it.

Try a touch joint:

Hi @roaminggamer

1.wow! Thank you so much for the quick response

2.My fault. I did not say that I would not use the joints. For two reasons:

  • I think that with math I could manage some aspects
  • As you can see in the video the ball stays still until the user finishes a stretch. if I used a joint and did:“tmpJoint:setTarget( event.x, event.y )” in moved phase then the ball would move immediately but I want it to move only when the user has finished. if instead I do it only in the final phase the ball may not have the thrust that…

Actually, I just downloaded tigerball and a touch joint won’t help.

The essential mechanic steps are:

  • touch begin:
    • collect t0 = system.getTimer() (Note: event has a time in it so you can use that too)
  • touch end:
    • t1 - system.getTimer() (Note: event has a time in it so you can use that too)
    • dt = (t1 - t0)/1000
    • dx = event.x - event.xStart 
    • dy = event.y - event.yStart 
    • distance = math.sqrt( dx * dx + dy * dy )
    • factor = choose your own value
    • speed limit = choose your own value
    • speed = factor * distance/dt
    • if speed > speed limit, cap at speed limit
    • vec = normalize the vector <dx,dy>
    • scale the vector vec = <vec.x * speed, vec.y * speed>
    • apply a linear impulse to the ball center with magnitude <vec.x , vec.y>

Thanks really fantastic. Just what I wanted!

I did a test but there is something I do not understand.

local ball = display.newCircle( 160, 100, 20 ) ball:setFillColor( 1, 0, 0 ) physics.addBody(ball, "dynamic", {radius=ballRadius}) local t0 local t1 local factor = 1 local speedLimit = 10 function ball:touch( event ) if(event.phase=="began")then display.getCurrentStage( ):setFocus( self ) self.isFocus = true t0 = system.getTimer() elseif(self.isFocus)then if(event.phase=="moved")then elseif((event.phase=="ended")or(event.phase=="cancelled")) then display.getCurrentStage( ):setFocus( nil ) self.isFocus = nil t1 = system.getTimer() local dt = (t1 - t0)/1000 local dx = event.x - event.xStart local dy = event.y - event.yStart local distance = math.sqrt( dx \* dx + dy \* dy ) local speed = factor \* distance/dt if (speed \> speedLimit)then speed = speedLimit end local vecX, vecY = math.abs(dx), math.abs(dy) local impulseX, impulseY = vecX\*speed, vecY\*speed self:applyLinearImpulse( self.x, self.y, impulseX, impulseY ) end end end ball:addEventListener( "touch" )

if I for example:

1.the event begins

2.I execute the stroke

3.I wait a few seconds

4.release

the ball receives a huge impulse instead of a low one

Did I miss something?

That sounds wrong.  If you wait to lift your finger, dt should be very large making the speed (impulse magnitude very small).

I’ll look over your code again in a moment.

Tip: Paste to notepad and replace tabs with three spaces before then copying and pasting the modified code here.  It makes the code more legible.

  • You didn’t normalize correctly.

  • Your call to applyLinearImpulse is backwards, you have the forces in the wrong place: 

    object:applyLinearImpulse( xForce, yForce, bodyX, bodyY )

  • You didn’t include the object mass.  (If you don’t do this, then later change the size of the ball you’ll need to re-balance your factor calculation.)

  • I made some changes to the original recipe.

https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/06/tigerBall.zip

local ballRadius = 20 local ball = display.newCircle( cx, bottom - 80, ballRadius ) ball.factor = 0.06 ball.limit = 50 ball:setFillColor( 1, 0, 0 ) physics.addBody( ball, "dynamic", { radius = ballRadius, bounce = 0 } ) ball.linearDamping = 1 function ball.enterFrame(self) display.remove(self.line) if( not self.lx ) then return end self.line = display.newLine( self.x, self.y, self.lx, self.ly ) self.line:setStrokeColor(1,1,0) self.line.strokeWidth = 4 end Runtime:addEventListener( "enterFrame", ball ) function ball.finalize( self ) Runtime:removeEventListener( "enterFrame", self ) end ball:addEventListener("finalize") local ground = display.newRect( cx, bottom - 50, fullw, 40) physics.addBody( ground, "static") function ball:touch( event ) local id = event.id if(event.phase=="began") then display.getCurrentStage( ):setFocus( self, id ) self.isFocus = true -- self.t0 = system.getTimer() -- elseif(self.isFocus) then if(event.phase=="moved") then self.lx = event.x self.ly = event.y elseif((event.phase=="ended")or(event.phase=="cancelled")) then display.getCurrentStage( ):setFocus( self, nil ) self.isFocus = nil -- local dt = (system.getTimer() - self.t0)/1000 -- local dx = event.x - event.xStart local dy = event.y - event.yStart print(dx,dy) local distance = math.sqrt( dx \* dx + dy \* dy ) -- This is how you normalize (not by using math.abs()): local vx = dx/distance local vy = dy/distance -- local magnitude = self.factor \* distance/dt --print( distance, dt, magnitude, self.t0, system.getTimer(), dx, dy ) magnitude = (magnitude \> self.limit) and self.limit or magnitude magnitude = magnitude \* self.mass -- self:applyLinearImpulse( vx \* magnitude, vy \* magnitude, self.x, self.y ) -- self.lx = nil self.ly = nil -- end end end ball:addEventListener( "touch" )

https://www.youtube.com/watch?v=7np_XgPwgRE&feature=youtu.be

Really fantastic now we are here!

And the question of mass would be my next question…

Really thanks again

Hi.

could I ask one last question?

with your project if I “tap” the ball, both (the ball and the ground) will disappear.

I understand that with those values a tap face splashed the ball (really short time and non-existent distance), but why does the background disappear?

I have already corrected the mistake by checking the positions in touch event only I do not understand this thing of the ground…