Tutorial: Working with curved paths

Tutorial: Working with curved paths

Since the previous tutorial on moving objects along a path, we’ve received many requests for a tutorial that deals with curved paths, either generated via a bezier curve algorithm or “drawn” by the user’s touch on the screen. Today’s tutorial will cover both methods, including sample projects for download. In addition, we’ll walk through a basic module that makes an object follow the curved path.

Bezier method

bezier

The first sample project, available for download, lets the user touch one point on the screen to set the starting point of the curve, then the user may drag outward to create a “handle” that will adjust the curve. Next, the user may touch a second point to indicate the ending point of the curve and likewise drag outward to adjust the handle. For anybody who has worked with paths in an image/vector editing program like Photoshop or Illustrator, this process will be very familiar.

Dissecting the entire sample project is beyond the scope of this tutorial, but a few elements near the top of the code are important to understand:

This is simply an up-value reference to what will become, during manipulation of the anchors and handles, the display object (display.newLine()) for the generated curve. Instead of creating a separate object for each segment in the curve, we’ll use the convenient append() function to add segments to a core line object.

This table will contain an ordered series of sub-tables, each of which will contain the x and y position of a point along the curve. The structure of each sub-table is very simple, for example: { x=10, y=24 }. These points will also be passed to the follow.lua module that places an object at the starting point and transitions it from point to point — this will be discussed further down.

This variable is important to mention since it allows us to easily adjust the “smoothness” of the curve. More specifically, this value represents the total number of segments which will constitute the curve, and thus, higher values will yield a smoother curve. The default is 100 which should be sufficient for most scenarios.

This table is also passed to the follow.lua module and it allows us to adjust the behavior of the routine via the following key-value pairs:

  • segmentTime — The time of the transition between each point along the curve.
  • constantRate — Because the distance between points will vary, this sets the rate of movement to be more constant by using the length of the first segment as a basis and then adjusting the transition time of subsequent segments accordingly.
  • showPoints — If true, this will place a dot along each point in the curve.

Drawing method

pathFor generating a more “organic” path, we can use a path drawing module, also available for download. This method simply lets the user draw a path of any length by touching and dragging around the screen.

As above, dissecting the entire project is beyond the scope of this tutorial, but a few elements should be explained:

Similar to the bezier method, this is simply an up-value reference to what will become, as the user begins drawing, the display object for the path.

This table serves the same purpose as in the bezier module. It will contain an ordered series of sub-tables, each of which will contain the x and y position of a point along the curve.

This value represents the minimum distance between any two points along the path. This is especially important because, in the "moved" phase of a touch event, the user’s touch will be registered at very small increments and, if we created a path point on each increment, the pathPoints table would potentially be populated by hundreds or even thousands of coordinate sub-tables. That would result in an extremely “smooth” curve, but it’s more detail than necessary in most cases. Thus, the drawing routine will only register a new coordinate point if the distance from the previous point is greater than or equal to pathPrecision.

This table is serves the same purpose as in the bezier example, where segmentTime sets the transition time between points on the path, constantRate makes the movement speed more even, and showPoints plots the points along the path.

Object following the curve/path

For this tutorial, our follow.lua module uses basic transitions to move an object from point to point along the path. In addition, it uses a basic angleBetween() function to make the object face toward the next point as it moves along the path:

init() function

The follow.lua module is initially set up via the init() function which is called from either of the demo projects outlined above. First, this function creates a polygon display object, places it at the x and y location of the path starting point (passed in as the startPoint argument), and sets its rotation to face the second point:

A few lines after, we set a local variable precision with a default value equal to the pathPrecision argument. This is intended for compatibility between both the bezier example and the drawing example. In the bezier example, this argument can simply be passed in as a value of 0 because, in that module, there is no explicit set value for the distance between path points — instead, the algorithm creates the bezier based on a total number of segments. As a result, we must calculate a precision value based on the distance between the starting point and the second point, as indicated on line 75.

Next, we check if the showPoints parameter is true, we generate a dot along each point in the path by looping through the pathPoints table. Each point is added to a display group, pathPointsGroup, for easier cleanup when the curve is re-drawn.

Finally, we call the follow() function and pass some core arguments to it:

Follow function

The follow() function essentially performs some calculations and begins a series of transitions where each subsequent transition is queued by the completion of the previous transition. The calculations include an adjustment of the transition time if the constantRate boolean is true.

Additionally, we rotate the object to face the next point using the angleBetween() function:

For the actual transition, we simply pass in some core parameters including the transTime calculated above and the x and y destination point. Additionally, we tag the transition with "moveObject" so it can easily be paused, resumed, or canceled, and we set the onComplete function to nextTransition so the process repeats until the object reaches the ending point. Finally, as each iteration occurs, we increment obj.nextPoint so the next transition moves to the next point along the path.

Pausing, resuming, canceling

Because we tag each transition in the sequence with "moveObject", pausing, resuming, or canceling is simple — just pass the tag name to one of the transition control APIs:

In summary

Hopefully this tutorial gets you started on curve-based path movement in Corona. Remember to download and carefully inspect the sample code used in this tutorial:


Brent Sorrentino
brent@coronalabs.com

Brent Sorrentino is a full-time Developer Evangelist and technical writer who assists others with game development, code, and in overcoming other challenges to help them bring their app dreams to life.

7 Comments
  • Matt
    Posted at 00:27h, 10 September

    Awesome tutorial! Thanks Brent!

  • Mo
    Posted at 14:04h, 10 September

    FANTASTIC tutorial! Now enemies no longer need to follow a straight line 🙂

    THANK YOU.

    Mo

  • Jon
    Posted at 22:20h, 10 September

    So good. Thank you very much for this.

  • Fernker
    Posted at 11:06h, 11 September

    Is there an easy way to convert this curved path into a physics curved path? I’m having troubles where the line (when added as a static physics body) turns into a rectangle.

  • edualc
    Posted at 03:02h, 27 February

    the pathDrawing works very well.
    it’s just that .. one expect the path to disappear as the vehicle goes along its path. e.g. in flightPath.
    i tried to add { alpha = 0.1 } to the transition_to but nothing happened.
    How can we do that ?
    before I used your code, i had an array of lines between coordinates so I could clear them one at a time. And that is not perfect because if the distance/time between 2 coordinate is very long, it will only clear it when reaching the second coord.
    is there another way ?

    thanks !
    Edualc

  • Cliff
    Posted at 20:57h, 03 May

    Hello,

    I am trying to make a minimap for a racing game which has an object(will be a circle) run along it. I have a png and SVG file of a racetrack and have used the SVG files path element to create a set of straight lines roughly looks like the track and have successfully made a circle that follows the track. I now need to add curves but am unsure how to do so. a lot(if not all) the curves only use one control point in the SVG file. How would I make a curve based on that 1 control point?

    Thanks you for your time,
    Cliff.