07 January 2014
Tutorial: Moving objects along a path
There are many ways to move objects in Corona SDK. If you want to move an object from point A to B, the simplest approach is to use transition.to(). But what if you need to move it along a path with multiple segments, like moving a knight on a chess board in its unique “L” pattern? This tutorial outlines how to achieve sequential movement via a series of queued transitions.
Initial setup
The basis of this process is a table of x
and y
coordinate pairs to move the object along, in sequence. In its most simple format, the table may look like this:
1 2 3 4 5 6 7 8 9 10 |
local movePath = {} movePath[1] = { x=200, y=0 } movePath[2] = { x=200, y=200 } movePath[3] = { x=0, y=200 } movePath[4] = { x=200, y=300 } movePath[5] = { x=150, y=200 } movePath[6] = { x=200, y=100 } movePath[7] = { x=100, y=200 } movePath[8] = { x=0, y=0 } |
This just declares a series of movement points, starting at index 1 and progressing across as many points as you wish. By default, these points equate to specific coordinates on the screen, not points relative to the object’s starting position, but we’ll include a setting to let you choose which option is most suitable to your application.
Customizing the path parameters
In addition to the basic x
and y
coordinate setup, we’ll allow two additional parameters for each “segment” in the path: time
and easingMethod
. For example, we may expand our table like this:
1 2 3 4 5 6 7 8 9 |
local movePath = {} movePath[1] = { x=200, y=0 } movePath[2] = { x=200, y=200 } movePath[3] = { x=0, y=200, time=500 } movePath[4] = { x=200, y=300, time=500 } movePath[5] = { x=150, y=200, time=250, easingMethod=easing.inOutExpo } movePath[6] = { x=200, y=100, time=2000 } movePath[7] = { x=100, y=200, time=500 } movePath[8] = { x=0, y=0, time=500, easingMethod=easing.outQuad } |
If a custom time
parameter is specified, it will override any other time settings, and the object will move to that point over that exact duration. Similarly, if easingMethod
is defined and set to one of Corona’s easing methods, the object will move to that point using that easing method, not the default linear interpolation.
Distance function
For calculating the distance between two points, we’ll need to include a quick function. The purpose of this will be explained later, but let’s add it now:
1 2 3 4 5 6 |
local function distBetween( x1, y1, x2, y2 ) local xFactor = x2 - x1 local yFactor = y2 - y1 local dist = math.sqrt( (xFactor*xFactor) + (yFactor*yFactor) ) return dist end |
Create the object(s)
Obviously, we’ll need one or more objects to move along the path we created. Let’s create two basic circles now:
1 2 3 4 5 |
local circle1 = display.newCircle( 60, 100, 15 ) circle1:setFillColor( 1, 0, 0.4 ) local circle2 = display.newCircle( 120, 100, 15 ) circle2:setFillColor( 1, 0.8, 0.4 ) |
The setPath() function
Now that we’ve done the basic setup, let’s explore the function which will queue up all of the transitions for an object. Let’s call the function setPath()
and provide it with three arguments: the object
to move, the path
to move along, and a table of params
which we can use to customize the movement.
1 2 3 4 5 6 7 8 9 10 |
local function setPath( object, path, params ) local delta = params.useDelta or nil local deltaX = 0 local deltaY = 0 local constant = params.constantTime or nil local ease = params.easingMethod or easing.linear local tag = params.tag or nil local delay = params.delay or 0 local speedFactor = 1 |
In the first several lines of the function, we set some local variables, most of which are used to check if various parameters are passed in via the params
table. Each of these parameters will be explained as we step through the tutorial.
Delta
This gives us the option to use delta position via a boolean value — useDelta
— passed in via the params
table. If set to true
, the object’s path will be relative to its starting position. If set to false
, or omitted, the path points will equate to explicit screen coordinates instead.
Whether to use params.useDelta = true
depends on the scenario. For moving a knight on a chess board, delta would be the logical choice, since the knight may reside on any square of the board and we’d need to move it in its “L” pattern from the current square to another valid square.
If useDelta
is passed in the params
table, let’s set deltaX
and deltaY
to the object’s starting position, instead of their default of 0
. When the transitions are set up, this will be used to offset each point along the path by the object’s starting position. It will also be used to refactor the constant rate of movement, discussed in a moment.
1 2 3 4 |
if ( delta ) then deltaX = object.x deltaY = object.y end |
Constant rate of movement
Another option we’ll add is the ability to set a “constant rate of movement.” For example, we may wish to move the object at a steady, constant rate across the entire path, even if the distance between the starting point and the 2nd point is 100 pixels but the distance between the 2nd and 3rd point is 280 pixels. To accomplish this, we just need to calculate a speed factor based on a “time” value passed in via params.constantTime
.
1 2 3 4 |
if ( constant ) then local dist = distBetween( object.x, object.y, deltaX+path[1].x, deltaY+path[1].y ) speedFactor = constant/dist end |
The value that should be passed to params.constantTime
is simply an integer “time” value, and speedFactor
is calculated by the distance between the starting point and the 2nd point. In other words, if we use a value of 1200, the object will move from the starting point to the 2nd point in 1200 milliseconds, and the speed established along that segment will be applied to all other segments in the path, no matter their distance apart.
Default easing method
If we wish to use a non-linear easing method for all segments in the path, we can tell the module to use any of the easing methods. For example, to use a quadratic-in-out method, we can pass easingMethod = easing.inOutQuad
in the params
table. This easing method will be applied to all transitions along the path except those with a specific easingMethod
setting in the path table.
Transition tagging
One of the features in the transition library is the ability to tag (name) any number of transitions and then cancel, pause, or resume all transitions sharing the same tag. Since we’ll be building paths that consist of multiple related transitions, tagging is essential if you want to pause or resume the path movement as it progresses from point to point. To tag all of the transitions that will collectively make up the path, just pass the tag
parameter (string value) in the params
table.
Looping through the points
Now, let’s loop through our points and set up the queue of transitions. For simplicity, we’ll declare all of them initially and apply a delay
parameter on each, so each transition starts when the previous one is finished.
First, we’ll start our loop and immediately set a default segmentTime
of 500 milliseconds. Then, we’ll check if params.constantTime
has been supplied and, if so, we’ll overwrite segmentTime
with a refactored time — specifically, the distance between the points multiplied by the speedFactor
that we calculated above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
for i = 1,#path do local segmentTime = 500 --if "constant" is defined, refactor transition time based on distance between points if ( constant ) then local dist if ( i == 1 ) then dist = distBetween( object.x, object.y, deltaX+path[i].x, deltaY+path[i].y ) else dist = distBetween( path[i-1].x, path[i-1].y, path[i].x, path[i].y ) end segmentTime = dist*speedFactor |
As mentioned above, we can optionally set a custom time on any specific segment in the path. If supplied, this value will override both the default of 500 milliseconds and the “constant rate” value, if it’s being used. Let’s check if a custom time has been specified for this segment:
1 2 3 4 |
else --if this path segment has a custom time, use it if ( path[i].time ) then segmentTime = path[i].time end end |
In addition, let’s check if a custom easingMethod
parameter has been set on this specific path segment. If declared, this will override the optional default easing method applied to the path as a whole.
1 2 |
--if this segment has custom easing, override the default method (if any) if ( path[i].easingMethod ) then ease = path[i].easingMethod end |
And finally, the heart of the entire process — creating each transition for the path:
1 2 3 4 |
transition.to( object, { tag=tag, time=segmentTime, x=deltaX+path[i].x, y=deltaY+path[i].y, delay=delay, transition=ease } ) delay = delay + segmentTime end end |
Notice that the parameters table for each actual transition is populated based on the values we gathered or calculated in the lines above. Also, note line #66 — here, we add to the total delay
value for each new transition in sequence, thus making each transition begin when the previous one has completed.
Setting an object in motion
Let’s start our circles in motion across the path! At the most basic level, the setPath()
function may be called like this:
1 2 |
setPath( circle1, movePath, { tag="moveObject" } ) setPath( circle2, movePath, { tag="moveObject" } ) |
However, this doesn’t include any options like useDelta
or constantTime
, so let’s expand on it:
1 2 |
setPath( circle1, movePath, { useDelta=true, constantTime=1200, easingMethod=easing.inOutQuad, delay=200, tag="moveObject" } ) setPath( circle2, movePath, { useDelta=true, constantTime=1200, easingMethod=easing.inOutQuad, tag="moveObject" } ) |
And that’s it — the circles will both follow the same path, movePath
, and the path will be offset by each object’s starting position because we used the delta setting. The rate of movement across each segment will be constant, and an in-out quadratic easing will be applied to all segments.
Pausing, resuming, canceling
Because we tagged every transition in the sequence with the "moveObject"
tag, pausing, resuming, or canceling is simple — just pass the tag name to one of the transition control APIs:
1 2 3 4 5 6 7 |
--pause the sequence transition.pause( "moveObject" ) --some time later, resume it transition.resume( "moveObject" ) --all finished... cancel it! transition.cancel( "moveObject" ) |
In summary
Hopefully this tutorial gets you started on path-based movement in Corona. In closing, note the following:
- The functional format allows you to create and re-use several unique path “patterns” and apply them to various objects by simply passing the appropriate path table to the
setPath()
function, along with the object and the optional parameters. In this manner, you could, for example, create several different movement paths for pieces in a board game and re-use those paths as needed depending on the player’s move. - This tutorial outlines just linear path-based movement. If you need curved-based path methods, please refer to the working with curved paths tutorial.
fuhongxue
Posted at 18:12h, 07 JanuaryHow can I Moving Objects Along a Bezier Path?
Is there some easy way ?
juf jannie
Posted at 10:10h, 09 JanuaryOr a curve.
Adding every coordinate for a curve by hand really is no fun.
Brent
Posted at 11:34h, 09 JanuaryThis tutorial isn’t geared toward curved paths. If there are enough requests, I may work up another tutorial involving bezier paths.
Brent
Peter Rich
Posted at 21:12h, 09 January+1
Kawika
Posted at 05:25h, 12 JanuaryIi vote YES to a curved path tutorial. thanks Brent!
Fernker
Posted at 12:36h, 13 MarchI’d love to see one on curved paths.
Nathan Harper
Posted at 21:07h, 13 MayCurves please!
Joe Colling
Posted at 09:54h, 04 SeptemberYes, please! +1 curved paths.
Chris
Posted at 02:00h, 28 JanuaryAnother +1 for bezier path tutorial please.
Noah (Chunky Apps)
Posted at 07:25h, 16 February+1 for curved path. Been waiting for that for years!
Evandro
Posted at 16:21h, 04 March+1
Charley
Posted at 06:04h, 01 NovemberCurved Path yes!
Erich Grüttner Díaz
Posted at 19:35h, 07 JanuaryExcellent tutorial, as usual!
Perhaps could be interesting to add a “loop” param (true or false). What do you think?
Thanks Brent!!!
Tony
Posted at 04:34h, 12 JanuaryPlease make a tut for moving objects along a curved path! Thanks!
Sid
Posted at 08:45h, 13 JanuaryFor Bezier curves check out Level Director (3rd party tools), I’ve been using this in my projects and it allows you to plot paths and beziers and allows you to make objects follow them automatically, pretty cool.
Dave Baxter
Posted at 05:18h, 15 JanuaryI used this to get objects moving along a Bezier curve –
http://developer.coronalabs.com/code/bezier-curve-corona-sdk
Dave
Erik
Posted at 20:29h, 28 AprilThis tutorial is amazing! Thank you very much for this guide, it is very useful. Only thing this amazing program is missing is an option to reset it back to first point and forever looping through all points with a true or false param. Would greatly appreciate a param like this 🙂
Nick
Posted at 16:02h, 25 MayVote up for curve tutorial
Thank you!!
Olivier Romanetti
Posted at 07:16h, 30 MayVote up for curve tutorial too 🙂
Nick
Posted at 11:34h, 03 Junecurve!
keenan
Posted at 06:49h, 25 Juneawesome stuff are we free to use this in our projects?
Joe
Posted at 15:24h, 04 SeptemberGreat post. One question, though….
Under “INITIAL SETUP” towards the top of the post, it says just under the first set of code –
“By default, these points equate to specific coordinates on the screen, not points relative to the object’s starting position, but we’ll include a setting to let you choose which option is most suitable to your application.”
Now, I read through the post several times and did not find where this setting is shown. Did I miss it, or is it not there??? What is the “setting to let me choose which option is most suitable for my app”?
Please advise. Many thanks.
Brent
Posted at 21:46h, 04 SeptemberHi Joe,
The setting you’re speaking of is the “delta” setting. Read over that sub-section, try both options, and observe the difference.
Brent
Joe
Posted at 11:21h, 06 SeptemberAh. I see now. *puts on glasses*
Okay. Well, I do have one more question. under the “SetPath” section, the code starts out like this –
local function setPath( object, path, params )
For the “params” part – does it mean I have to put in every parameter I am going to use in my transition statement? It also talks about a params table. Where is it written in the tutorial here? I’m not understanding where Params comes from…..May just be the the word “params” thats confusing me….Anyway, I understand “object” and “path” – however, I dont understand why “params” is there…
It seems like “params” is being called first, and defined second…..Which cant be right, right? Or am I wrong? Can you kindly explain what “params” in that snippet of code is for and why it is there? I’m just not getting it.
Thanks in advance, sir.
-Joe
Brent
Posted at 14:04h, 06 SeptemberHi Joe,
“params” is simply the table that is passed in as the third argument to the “setPath” function. It’s not actually named “params” in the code, so just consider that I’m referring to that table as “params”.
Mikel
Posted at 10:40h, 27 FebruaryVery good tutorial.
Takes me 1 hour and now my enemies are following my path.
Very nice. Thank you.
Brian
Posted at 08:53h, 28 FebruaryI think there might be a slight issue with the code. You pass in ‘constantTime’ as a parameter, but in your setPath() function you refer to ‘constantRate’. Thus, the variable ‘constant’ in the setPath() function will always be nil.
Brent Sorrentino
Posted at 14:04h, 29 FebruaryHi Brian,
Thanks for pointing that out… the name was indeed incorrect.
– Brent