Drag an object along a specific path

Bear with me, my programming experience is pretty much slim to none. :slight_smile:
I am getting close to finishing my first project, but I have one major roadblock: I am trying to figure out how to drag my User_ball object along a specific path.

The ‘path’ I want my object to follow is a series of display objects, and I want the user to be able to freely drag the ball along the path.
Currently all I am able to do is drag the ball. I’ve tried changing parts of the code attempting to get the ball’s movement to be confined to a specific course, but I haven’t been able to achieve what I want.

Originally I thought the answer would be to use bezier curves, but unfortunately all of the examples I’ve seen - draw a line and click an animate button, draw a curve and immediately following ended phase of the touch the object follows the path etc… - are not what I want.
By the way, the only “curves” in my game are right-angles.

Another thought I had was to create a table of invisible ‘points’ or ‘locations’ and have the user drag the ball between the various points… only problem is I have no idea how to code something like that. :]

Any help would be greatly appreciated.

Here is the code I use to drag the ball:
[lua]-- adding a function that will allow the user to drag the ball
function User_ball:touch( event )
if event.phase == “began” then

display.getCurrentStage():setFocus(self, event.id)
self.isFocus = true
self.markX = self.x – store x location of object
self.markY = self.y – store y location of object

elseif event.phase == “moved” then

local x = (event.x - event.xStart) + self.markX
local y = (event.y - event.yStart) + self.markY

self.x, self.y = x,y – move object based on calculations above

elseif event.phase == “ended” or event.phase == “cancelled” then

display.getCurrentStage():setFocus(self, nil)
self.isFocus = false

end

– stop further propagation of touch event
return true

end
User_ball:addEventListener( “touch”, User_ball )[/lua]

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 335544[/import]

Anyone have a suggestion?
I can’t figure it out!:\ [import]uid: 148623 topic_id: 35544 reply_id: 141569[/import]

Another thought I had was to create a table of invisible ‘points’ or ‘locations’ and have the user drag the ball between the various points (…)

As in something like a grid of points that the ball “snaps” to? [import]uid: 147322 topic_id: 35544 reply_id: 141598[/import]

Something like that…
Since I have been unable to get my user-object to move along the x,y of my display-objects, I thought I could make a series of points that would be a ‘guide’ that the ball would follow, via the user dragging the User_ball object.
An example of what I am trying to do would be similar to how a value-slider works - the user can drag the little circle within the confines of the… “roundedRect Area”? I want to achieve a similar effect, but rather than a visible slider, I want to make an invisible path and overlay it on my display objects, and that will be the area that the user can drag the ball…
(I hope all that made sense, I’m struggling to find the words to accurately describe what I want)

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 141603[/import]

You might begin with making a grid:
[lua]local grid={}

for w=1, 10 do
for h=1, 10 do
grid[w…"."…h]={}
end
end[/lua]
I titled them with [lua]w…"."…h[/lua] so that they’d be easier to get reference.

Then, you’d add an X and Y parameter to the objects in the grid:
[lua]local grid={}
local gridSize=10 – How big each “cell” is in the grid

for w=1, 10 do
for h=1, 10 do
grid[w…"."…h]={
x=w*gridSize,
y=h*gridSize
}
end
end[/lua]
Then, you might try when touch begins, check which direction the touch is moving and confine it to that direction.

That’s a starting point :slight_smile:

Also, when making grids, check this out:

http://developer.coronalabs.com/forum/2013/02/05/little-advice

Caleb [import]uid: 147322 topic_id: 35544 reply_id: 141665[/import]

Thank you so much!

Based on the changes you’ll see below, when the user drags the User_ball object it only moves at the center of the display along the x-axis (I have my ball object spawning at _W/2 and _H/2, so that is why it moves in the center)
When I un-comment the lines of code dealing with the y-axis, it can move all over the screen…? I take it that is because I need to change the grid parameters?

[lua]
– adding a function that will allow the user to drag the ball
function User_ball:touch( event )
if event.phase == “began” then

display.getCurrentStage():setFocus(self, event.id)
self.isFocus = true
grid.markX = self.x – store x location of object
–grid.markY = self.y – store y location of object

elseif event.phase == “moved” then

local x = (event.x - event.xStart) + grid.markX
–local y = (event.y - event.yStart) + grid.markY

self.x = x
–self.y = y – move object based on calculations above

elseif event.phase == “ended” or event.phase == “cancelled” then

display.getCurrentStage():setFocus(self, nil)
self.isFocus = false

end

– stop further propagation of touch event
return true

end
User_ball:addEventListener( “touch”, User_ball )
[/lua]

EDIT: If I understand correctly, to get it to work the way I want I would have to match the x,y location of my display objects with the corresponding grid “cell” and then code the ball follow the defined ‘cells’ when the drag event occurs?

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 141902[/import]

Any thoughts on how to get it to work the way I want?

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 142148[/import]

Just hang on for a while - I’m trying to code it out.

C [import]uid: 147322 topic_id: 35544 reply_id: 142158[/import]

No worries Caleb, didn’t mean to rush you. :slight_smile:

@ John - Thanks for the suggestion.
I’ve actually looked into bezier curves before, however I stopped looking into it because I didn’t like the idea of “drawing” my path. I want a very specific path and as stated above, there are only right angles in my game so I thought “drawing” my path would make the ‘drag’ motion of my user object look… wrong.

I’ll definitely take another look at the docs in the link/s and try what you suggested.

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 142165[/import]

@Saerothir:

You weren’t rushing me :slight_smile:

So here it is - unoptimized code to clamp something to a grid. Of course, you’d need to refine it, etc, but here’s the shell:
[lua]
local square – Declare the grid table at the top

local function clamp(t, l, h) – Clamps a value to range (l-h)
if t return l
elseif t>h then
return h
else
return t
end
end

local function spaceToGrid(x,y,size) – Changes an (x, y) position to a grid’s (x, y) position
return math.ceil((x==0 and 1 or x)/size), math.ceil((y==0 and 1 or y)/size)
end

square={} – Table to hold grid

local squareGradient=graphics.newGradient( – Just to make things prettier
{255, 255, 0},
{255, 0, 0}
)

for w=1, 10 do
for h=1, 10 do
square[w…"."…h]=display.newRect(0, 0, 50, 50, 16) – Populate the grid with squares
square[w…"."…h].x, square[w…"."…h].y=w60, h60
square[w…"."…h].num=w*h
square[w…"."…h]:setFillColor(squareGradient)
end
end

local prevX, prevY=0, 0

local ball=display.newCircle(0, 0, 20) – Create the ball itself
ball.x, ball.y=square[“1.1”].x, square[“1.1”].y
ball:setFillColor(255, 255, 0)
ball.strokeWidth=4
ball:setStrokeColor(255, 0, 0)
ball.position=square[“1.1”]
ball.constraint=“X"
ball.hasPosition=true

local function touchBall(event)
if “began”==event.phase then
display.getCurrentStage():setFocus(ball) – Set focus to ball
ball.isFocus=true
prevX, prevY=event.x, event.y

elseif ball.isFocus then
if “moved”==event.phase then
ball.X, ball.Y=spaceToGrid(ball.x, ball.y, 60) – Find the square the ball is closest to
ball.position=square[clamp(ball.X, 1, 10)…”."…clamp(ball.Y, 1, 10)]

if ball.constraint==“Y” then – Constraint along the Y-axis
ball.y=clamp(event.y, 60, 600)
ball.x=ball.position.x
elseif ball.constraint==“X” then – Constraint along the X-axis
ball.x=clamp(event.x, 60, 600)
ball.y=ball.position.y
end

if ball.x<=ball.position.x+5 and ball.x>=ball.position.x-5 and ball.y<=ball.position.y+5 and ball.y>=ball.position.y-5 then – If the ball is near enough to the center of it’s square
ball.x, ball.y=ball.position.x, ball.position.y – Clamp it to the square’s position

ball.diffX, ball.diffY=math.abs(event.x-prevX), math.abs(event.y-prevY) – Difference between X dragging and Y dragging
ball.constraint=“Y” – Defaults to Y-axis constraint

if ball.diffX>ball.diffY then – If the X difference is greater than the Y difference (ie you dragged left or right instead of up or down)
ball.constraint=“X” – Constrain to X-axis
end
end

prevX, prevY=event.x, event.y – Reset prevX and prevY
end
end
return true
end
ball:addEventListener(“touch”, touchBall) – Add listener
[/lua]
Hope this helps!

Caleb [import]uid: 147322 topic_id: 35544 reply_id: 142184[/import]

Anyone have a suggestion?
I can’t figure it out!:\ [import]uid: 148623 topic_id: 35544 reply_id: 141569[/import]

Another thought I had was to create a table of invisible ‘points’ or ‘locations’ and have the user drag the ball between the various points (…)

As in something like a grid of points that the ball “snaps” to? [import]uid: 147322 topic_id: 35544 reply_id: 141598[/import]

Something like that…
Since I have been unable to get my user-object to move along the x,y of my display-objects, I thought I could make a series of points that would be a ‘guide’ that the ball would follow, via the user dragging the User_ball object.
An example of what I am trying to do would be similar to how a value-slider works - the user can drag the little circle within the confines of the… “roundedRect Area”? I want to achieve a similar effect, but rather than a visible slider, I want to make an invisible path and overlay it on my display objects, and that will be the area that the user can drag the ball…
(I hope all that made sense, I’m struggling to find the words to accurately describe what I want)

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 141603[/import]

You might begin with making a grid:
[lua]local grid={}

for w=1, 10 do
for h=1, 10 do
grid[w…"."…h]={}
end
end[/lua]
I titled them with [lua]w…"."…h[/lua] so that they’d be easier to get reference.

Then, you’d add an X and Y parameter to the objects in the grid:
[lua]local grid={}
local gridSize=10 – How big each “cell” is in the grid

for w=1, 10 do
for h=1, 10 do
grid[w…"."…h]={
x=w*gridSize,
y=h*gridSize
}
end
end[/lua]
Then, you might try when touch begins, check which direction the touch is moving and confine it to that direction.

That’s a starting point :slight_smile:

Also, when making grids, check this out:

http://developer.coronalabs.com/forum/2013/02/05/little-advice

Caleb [import]uid: 147322 topic_id: 35544 reply_id: 141665[/import]

Thanks Caleb!

Your code is pretty straight forward and easy to understand, however I do have a couple questions:

  1. On line #26 [lua] square[w…"."…h]=display.newRect(0, 0, 50, 50, 16) [/lua] what does the 16 do?

  2. Could you give an example of how the spaceToGride function works? Also, Say object1 starts at cell 3-1 and ends 3-9, object2 starts 4-9 , ends 7-9 etc… how would I specify those cells as the only cells the ball can move along?

  3. When I run the simulator I can click on&interact with an object I have, but once I click on the ball, drag, and release the ball I can no longer interact with my other object and wherever I click on the display it starts to drag the ball…?

I changed the [lua] local function touchBall(event) [/lua] to [lua] function ball:touch(event) [/lua]
and added [lua]

if “ended”==event.phase then

display.getCurrentStage():setFocus(ball, nil)
ball.isFocus = false

end [/lua]
Thinking that would do the trick, but no such luck…

Thanks again for your help!! :slight_smile:

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 142542[/import]

  1. Sorry, I originally had it with roundedRects. That was the corner radius - it’s not needed.

  2. SpaceToGrid takes an (x, y) position and converts it to grid coordinates (x, y). I don’t quite understand the last part of your question, though.

  3. Change it to this:
    [lua]
    display.getCurrentStage():setFocus(nil)
    ball.isFocus=false
    [/lua]

Caleb [import]uid: 147322 topic_id: 35544 reply_id: 142547[/import]

lol no worries. thanks.

So if my track.x = 500, track.y = 375 it would take the x,y and match it with the corresponding grid cells?

okay, I have a line and it starts at grid cell (R = Right, D = down) 3R-1D and ends 3R-9D, a second line starts 3R-9D and ends 9R-9D … how would I define those grid cells as the only cells the ball can move along? - Hope that made sense.

Hmm, still doesn’t work…?
I have a circular platform that can rotate via the user touch&dragging. When I first run the simulator I can rotate the platform, but once I drag the ball it appears to constantly be the focus - wherever I click&drag within the display the ball ‘snaps’ instantly to that spot, and my rotating platform no longer registers when I go to rotate it…?

I think it may have something to do with this: [lua]

if “moved”==event.phase then

– Find the square the ball is closest to
ball.X, ball.Y=spaceToGrid(ball.x, ball.y, 30)
ball.position=square[clamp(ball.X, 1, 35)…"."…clamp(ball.Y, 1, 25)]

– Constraint along the Y-axis
if ball.constraint==“Y” then

ball.y=clamp(event.y, 75, 680)
ball.x=ball.position.x

– Constraint along the X-axis
elseif ball.constraint==“X” then

ball.x=clamp(event.x, 120, 925)
ball.y=ball.position.y

[/lua]

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 142553[/import]

SpaceToGrid: Correct. That’s what it does :slight_smile:

You are making some sort of a thing with paths that the ball can move on - like a half-sliding puzzle, half-maze sort of thing?

More complex paths, in my opinion, should use something more complex. Something like… PHYSICS!!!

Walls on each side so that it can’t pass them, in other words. A simple way to do it would be to make a map in Tiled and create some sort of a simple map loading code that makes walls.

I’d be happy to help with that, too :slight_smile:

It’s odd - the un-setting focus works for me. Here’s the complete ball drag code for my previous post - you’ll probably need to change it, if you decide to go with the physics solution.
[lua]
local function touchBall(event)
if “began”==event.phase then
display.getCurrentStage():setFocus(ball)
ball.isFocus=true
prevX, prevY=event.x, event.y

elseif ball.isFocus then
if “moved”==event.phase then
ball.X, ball.Y=spaceToGrid(ball.x, ball.y, 60)
ball.position=square[clamp(ball.X, 1, 10)…"."…clamp(ball.Y, 1, 10)]

if ball.constraint==“Y” then
ball.y=clamp(event.y, 60, 600)
ball.x=ball.position.x
elseif ball.constraint==“X” then
ball.x=clamp(event.x, 60, 600)
ball.y=ball.position.y
end

if ball.x<=ball.position.x+5 and ball.x>=ball.position.x-5 and ball.y<=ball.position.y+5 and ball.y>=ball.position.y-5 then
ball.x, ball.y=ball.position.x, ball.position.y

ball.diffX, ball.diffY=math.abs(event.x-prevX), math.abs(event.y-prevY)
ball.constraint=“Y”

if ball.diffX>ball.diffY then
ball.constraint=“X”
end
end

prevX, prevY=event.x, event.y
elseif “ended”==event.phase then
display.getCurrentStage():setFocus(nil)
ball.isFocus=false
end
end
return true
end
[/lua]
Oh, and it’s better (I tried it out) to replace the spaceToGrid function with this (using math.round instead of math.ceil):
[lua]
local function spaceToGrid(x,y,size)
return math.round((x==0 and 1 or x)/size), math.round((y==0 and 1 or y)/size)
end
[/lua]

Caleb [import]uid: 147322 topic_id: 35544 reply_id: 142617[/import]

Thank you so much!

Based on the changes you’ll see below, when the user drags the User_ball object it only moves at the center of the display along the x-axis (I have my ball object spawning at _W/2 and _H/2, so that is why it moves in the center)
When I un-comment the lines of code dealing with the y-axis, it can move all over the screen…? I take it that is because I need to change the grid parameters?

[lua]
– adding a function that will allow the user to drag the ball
function User_ball:touch( event )
if event.phase == “began” then

display.getCurrentStage():setFocus(self, event.id)
self.isFocus = true
grid.markX = self.x – store x location of object
–grid.markY = self.y – store y location of object

elseif event.phase == “moved” then

local x = (event.x - event.xStart) + grid.markX
–local y = (event.y - event.yStart) + grid.markY

self.x = x
–self.y = y – move object based on calculations above

elseif event.phase == “ended” or event.phase == “cancelled” then

display.getCurrentStage():setFocus(self, nil)
self.isFocus = false

end

– stop further propagation of touch event
return true

end
User_ball:addEventListener( “touch”, User_ball )
[/lua]

EDIT: If I understand correctly, to get it to work the way I want I would have to match the x,y location of my display objects with the corresponding grid “cell” and then code the ball follow the defined ‘cells’ when the drag event occurs?

-Saer [import]uid: 148623 topic_id: 35544 reply_id: 141902[/import]

Ahh, I see. cool! :smiley:

Thanks, I made some changes and it works now.

Pretty much. It is a maze game that has a small line/track that the ball moves along, and there are some platforms that can rotate to connect with a different piece of track so the ball can continue until it reaches the goal/end-point etc… etc…
I have almost the entire game done (menus, scenes, sounds, and such ) and the only thing stopping me from finishing it is this whole ball-follow-path thing lol.

Actually, that was my first thought - create invisible rects that would border the entire track&platforms.
I would think you’d have to have the rect-borders partially overlap the the ball so there wouldn’t be any odd movement - such as the ball not moving in a straight line - when the user is dragging it…?

I’d very much appreciate your help with it. :slight_smile:

Would Tiled be a better choice over LevelHelper?

-Saer

[import]uid: 148623 topic_id: 35544 reply_id: 142679[/import]