curves-and-paths
Curves and Paths
Creating paths from curves, getting points along them, drawing them with Graphics, and making sprites follow paths automatically using PathFollower in Phaser 4.
Key source paths: src/curves/, src/curves/path/, src/gameobjects/pathfollower/, src/gameobjects/components/PathFollower.js
Related skills: ../sprites-and-images/SKILL.md, ../graphics-and-shapes/SKILL.md, ../tweens/SKILL.md
Quick Start
// In a Scene's create() method:
// 1. Create a Path starting at (50, 300)
const path = this.add.path(50, 300);
// 2. Add curves to the path
path.lineTo(200, 100);
path.splineTo([ new Phaser.Math.Vector2(300, 400), new Phaser.Math.Vector2(500, 200) ]);
path.lineTo(700, 300);
// 3. Draw the path using Graphics
const graphics = this.add.graphics();
graphics.lineStyle(2, 0xffffff, 1);
path.draw(graphics, 64);
// 4. Create a PathFollower sprite that moves along the path
const follower = this.add.follower(path, 50, 300, 'ship');
follower.startFollow({
duration: 5000,
rotateToPath: true,
repeat: -1,
yoyo: true
});
Core Concepts
Path
A Phaser.Curves.Path is a container that combines multiple Curves into one continuous compound curve. Curves in a Path do not need to be connected end-to-end. Only the order of curves affects point calculations along the path.
Created via factory: this.add.path(x, y) where x/y is the starting point.
Key properties:
curves-- array ofPhaser.Curves.Curveobjects in the PathstartPoint--Vector2, the defined starting positionautoClose-- boolean, if truegetPoints()appends the first point at the enddefaultDivisions-- number (default: 12), divisions per curve when callinggetPoints()name-- string, empty by default, for developer use
Curves
All curve types extend Phaser.Curves.Curve (the base class). Every curve supports:
getPoint(t, out)-- get a point at position t (0-1) based on curve parameterizationgetPointAt(u, out)-- get a point at position u (0-1) based on arc length (evenly spaced)getPoints(divisions, stepRate, out)-- array of points along the curvegetSpacedPoints(divisions, stepRate, out)-- array of equidistant points by arc lengthgetDistancePoints(distance)-- points spaced by pixel distancegetLength()-- total arc length in pixelsgetBounds(out, accuracy)-- bounding RectanglegetTangent(t, out)/getTangentAt(u, out)-- unit tangent vectorgetStartPoint(out)/getEndPoint(out)-- first/last pointsgetRandomPoint(out)-- random point on the curvedraw(graphics, pointsTotal)-- render the curve onto a Graphics objectactive-- boolean, when false the parent Path skips this curve
PathFollower
A Phaser.GameObjects.PathFollower is a Sprite with the Components.PathFollower mixin. It uses an internal Tween (a number counter from 0 to 1) to advance along a Path each frame.
Created via factory: this.add.follower(path, x, y, texture, frame)
The PathFollower component provides:
path-- thePhaser.Curves.Pathbeing followedpathTween-- the internal Tween driving movementpathOffset--Vector2, offset added to path coordinatespathVector--Vector2, current position on the pathpathDelta--Vector2, distance traveled since last framerotateToPath-- boolean, auto-rotate to face path directionpathRotationOffset-- number (degrees), added to auto-rotation
Common Patterns
Creating Paths with Chained Curves
Path has convenience methods that create curves starting from the previous end point:
const path = this.add.path(100, 500);
path.lineTo(300, 100); // straight line
path.cubicBezierTo(500, 100, 350, 50, 450, 50); // cubic bezier (endX, endY, cp1X, cp1Y, cp2X, cp2Y)
path.quadraticBezierTo(700, 400, 600, 100); // quadratic bezier (endX, endY, cpX, cpY)
path.splineTo([ // spline through points
new Phaser.Math.Vector2(750, 300),
new Phaser.Math.Vector2(600, 500)
]);
path.ellipseTo(50, 80, 0, 270, false, 0); // ellipse arc (xRadius, yRadius, startAngle, endAngle, clockwise, rotation)
path.circleTo(40); // shortcut for ellipseTo with equal radii and 0-360
// Jump to a new position without drawing (creates a gap)
path.moveTo(400, 400);
path.lineTo(500, 400);
// Close the path by connecting end to start
path.closePath();
Adding Standalone Curve Objects
const path = new Phaser.Curves.Path(0, 0);
// Add pre-constructed curve objects
const line = new Phaser.Curves.Line(new Phaser.Math.Vector2(0, 0), new Phaser.Math.Vector2(200, 200));
path.add(line);
const spline = new Phaser.Curves.Spline([ 200, 200, 300, 100, 400, 300 ]);
path.add(spline);
const ellipse = new Phaser.Curves.Ellipse(400, 300, 100, 60, 0, 360, false, 0);
path.add(ellipse);
Getting Points Along a Path
// Array of points (uses defaultDivisions per curve)
const points = path.getPoints();
// With explicit divisions per curve
const detailed = path.getPoints(32);
// Equally spaced points along the entire path
const spaced = path.getSpacedPoints(100);
// Single point at normalized position (0-1)
const midpoint = path.getPoint(0.5);
// Tangent vector at a position
const tangent = path.getTangent(0.5);
// Total path length in pixels
const length = path.getLength();
// Bounding rectangle
const bounds = path.getBounds();
Drawing Paths with Graphics
const graphics = this.add.graphics();
// Draw entire path
graphics.lineStyle(2, 0x00ff00, 1);
path.draw(graphics, 64); // 64 = points per curve for smoothness
// Draw individual curves
graphics.lineStyle(1, 0xff0000, 1);
path.curves[0].draw(graphics, 32);
// Draw debug points
const points = path.getSpacedPoints(50);
points.forEach(p => {
graphics.fillStyle(0xffff00, 1);
graphics.fillCircle(p.x, p.y, 3);
});
PathFollower Sprite
const path = this.add.path(100, 200);
path.lineTo(400, 400);
path.lineTo(700, 200);
// Create follower
const enemy = this.add.follower(path, 100, 200, 'enemy');
// Start following with config
enemy.startFollow({
duration: 3000, // ms to traverse path
positionOnPath: true, // snap to path start position
rotateToPath: true, // auto-rotate to face direction
rotationOffset: 90, // offset added to auto-rotation (degrees)
repeat: -1, // -1 = infinite repeat
yoyo: true, // reverse on each repeat
from: 0, // start position on path (0-1)
to: 1, // end position on path (0-1)
startAt: 0, // initial seek position
ease: 'Sine.easeInOut' // any valid Phaser ease
});
// Control during playback
enemy.pauseFollow();
enemy.resumeFollow();
enemy.stopFollow();
enemy.isFollowing(); // returns boolean
// Change path at runtime
enemy.setPath(newPath);
enemy.setPath(newPath, { duration: 2000 }); // auto-starts
// Set rotation independently
enemy.setRotateToPath(true, 90); // (value, offsetDegrees)
PathFollower with Simple Duration
// Shorthand: pass just a duration number
enemy.startFollow(5000);
// Equivalent to:
enemy.startFollow({ duration: 5000 });
All Curve Types
| Curve | Class | Constructor Params | Description |
|---|---|---|---|
| Line | Phaser.Curves.Line |
(p0, p1) Vector2 endpoints, or ([x0,y0,x1,y1]) |
Straight line segment between two points |
| Spline | Phaser.Curves.Spline |
(points) array of Vector2, flat numbers, or nested arrays |
Catmull-Rom spline through control points |
| CubicBezier | Phaser.Curves.CubicBezier |
(p0, p1, p2, p3) or ([x0,y0,...x3,y3]) |
Cubic Bezier with start, 2 control points, end |
| QuadraticBezier | Phaser.Curves.QuadraticBezier |
(p0, p1, p2) or ([x0,y0,...x2,y2]) |
Quadratic Bezier with start, 1 control point, end |
| Ellipse | Phaser.Curves.Ellipse |
(x, y, xRadius, yRadius, startAngle, endAngle, clockwise, rotation) or config object |
Elliptical arc; angles in degrees; yRadius defaults to xRadius |
Ellipse Curve Properties
The Ellipse curve has get/set properties for runtime modification:
x,y-- center positionxRadius,yRadius-- radiistartAngle,endAngle-- in degrees (get/set convert to/from radians internally)clockwise-- booleanrotation-- in radiansangle-- rotation in degrees (alternative torotation)setWidth(value)/setHeight(value)-- sets radius to value/2
API Quick Reference
Path (Phaser.Curves.Path)
| API | Type | Description |
|---|---|---|
add(curve) |
method | Append any Curve to the path |
lineTo(x, y) |
method | Add a Line from current end point |
splineTo(points) |
method | Add a Spline from current end point |
cubicBezierTo(x, y, cp1X, cp1Y, cp2X, cp2Y) |
method | Add CubicBezier from current end point |
quadraticBezierTo(x, y, cpX, cpY) |
method | Add QuadraticBezier from current end point |
ellipseTo(xR, yR, start, end, cw, rot) |
method | Add Ellipse arc from current end point |
circleTo(radius, clockwise, rotation) |
method | Shortcut for ellipseTo with equal radii |
moveTo(x, y) |
method | Move end point without drawing (creates gap) |
closePath() |
method | Add Line from end to start if not already closed |
getPoint(t, out) |
method | Point at normalized position (0-1) on entire path |
getPoints(divisions, stepRate) |
method | Array of points, divisions per curve |
getSpacedPoints(divisions) |
method | Equidistant points along entire path |
getRandomPoint(out) |
method | Random point anywhere on the path |
getStartPoint(out) |
method | Path starting point |
getEndPoint(out) |
method | Path ending point |
getTangent(t, out) |
method | Unit tangent vector at position t |
getCurveAt(t) |
method | Return the Curve at normalized position t |
getLength() |
method | Total path length in pixels |
getCurveLengths() |
method | Array of cumulative curve lengths |
getBounds(out, accuracy) |
method | Bounding Rectangle |
draw(graphics, pointsTotal) |
method | Draw all curves onto a Graphics object |
toJSON() / fromJSON(data) |
method | Serialization |
updateArcLengths() |
method | Force recalculation of cached lengths |
destroy() |
method | Clear internal references |
Base Curve (Phaser.Curves.Curve)
| API | Type | Description |
|---|---|---|
getPoint(t, out) |
method | Point at parameter t (0-1) -- abstract, each subclass implements |
getPointAt(u, out) |
method | Point at arc-length position u (0-1) -- evenly spaced |
getPoints(divisions, stepRate, out) |
method | Array of points |
getSpacedPoints(divisions, stepRate, out) |
method | Equidistant points by arc length |
getDistancePoints(distance) |
method | Points spaced by pixel distance |
getLength() |
method | Total curve arc length |
getTangent(t, out) / getTangentAt(u, out) |
method | Unit tangent vector |
getTFromDistance(distance) |
method | Convert pixel distance to t value |
draw(graphics, pointsTotal) |
method | Render onto Graphics (default 32 points) |
getBounds(out, accuracy) |
method | Bounding Rectangle |
active |
boolean |
When false, parent Path skips this curve |
defaultDivisions |
number |
Default 5 for standalone curves |
arcLengthDivisions |
number |
Precision for arc length calculations (default 100) |
PathFollower Component
| API | Type | Description |
|---|---|---|
setPath(path, config) |
method | Set a new Path (optionally auto-start) |
startFollow(config, startAt) |
method | Begin following; config = duration number or PathConfig |
pauseFollow() |
method | Pause movement |
resumeFollow() |
method | Resume paused movement |
stopFollow() |
method | Stop following |
isFollowing() |
method | Returns true if actively moving on path |
setRotateToPath(value, offset) |
method | Enable/disable auto-rotation with offset |
path |
Path |
Current path reference |
pathTween |
Tween |
Internal tween driving movement |
pathOffset |
Vector2 |
Offset from path coordinates |
pathVector |
Vector2 |
Current position on the path |
pathDelta |
Vector2 |
Movement delta since last update |
rotateToPath |
boolean |
Auto-rotate to path direction |
pathRotationOffset |
number |
Rotation offset in degrees |
PathConfig (Phaser.Types.GameObjects.PathFollower.PathConfig)
| Property | Type | Default | Description |
|---|---|---|---|
duration |
number |
1000 | Time in ms to traverse the path |
from |
number |
0 | Start position on path (0-1) |
to |
number |
1 | End position on path (0-1) |
positionOnPath |
boolean |
false | Snap follower to path start on begin |
rotateToPath |
boolean |
false | Auto-rotate to face path direction |
rotationOffset |
number |
0 | Degrees added to auto-rotation |
startAt |
number |
0 | Initial seek position on path (0-1) |
The config also accepts all standard Tween properties: ease, repeat, yoyo, delay, hold, onComplete, etc.
Gotchas
-
getPoint(t)vsgetPointAt(u)on curves.getPointuses the raw curve parameter t, which does not produce evenly spaced points on most curve types.getPointAtmaps through arc length for even spacing. On a Path,getPointalready accounts for arc length across the whole path. -
Path
moveTocreates an inactive curve. TheMoveTopseudo-curve hasactive: falseand zero length. It only repositions the end point for the next curve. It does not draw anything and is skipped bygetPoints()anddraw(). -
PathFollower uses a Tween internally. The
startFollowconfig is passed toscene.tweens.addCounter(). All tween properties (ease, delay, repeat, yoyo, callbacks) work. The tween is set topersist: trueautomatically. -
PathFollower offset behavior. When
positionOnPath: false(default), the follower's current position becomes the offset from the path start. WhenpositionOnPath: true, the follower snaps to the path's start point and the offset is zeroed. -
Ellipse angles are in degrees. The constructor and
startAngle/endAngleproperties accept degrees. Internally they are stored as radians. Therotationproperty is in radians, butangleis in degrees. -
closePathvsautoClose.closePath()adds an explicit Line curve from end to start.autoClose = trueonly affectsgetPoints()andgetSpacedPoints()output by appending the first point, without adding a curve. -
Cached lengths can go stale.
getCurveLengths()caches results based on array length only. If you modify a curve's control points, callpath.updateArcLengths()to force recalculation. -
cubicBezierToparameter order with numbers. When passing numbers:cubicBezierTo(endX, endY, cp1X, cp1Y, cp2X, cp2Y). The end point comes first, not the control points. When passing Vector2 objects:cubicBezierTo(cp1, cp2, endPoint). -
Spline needs at least 4 points. The Catmull-Rom interpolation used by Spline works best with 4+ points. With fewer points, the curve may not behave as expected.
-
Line curve
arcLengthDivisionsis 1. Unlike other curves (default 100), Line overrides this to 1 since a line is inherently uniform. No need to adjust it.
Source File Map
| File | Purpose |
|---|---|
src/curves/path/Path.js |
Path class -- combines multiple curves, factory registered as this.add.path |
src/curves/path/MoveTo.js |
MoveTo pseudo-curve for creating gaps in paths |
src/curves/Curve.js |
Base Curve class -- shared methods for all curve types |
src/curves/LineCurve.js |
Line curve (two-point segment) |
src/curves/SplineCurve.js |
Spline curve (Catmull-Rom through multiple points) |
src/curves/CubicBezierCurve.js |
Cubic Bezier curve (4 control points) |
src/curves/QuadraticBezierCurve.js |
Quadratic Bezier curve (3 control points) |
src/curves/EllipseCurve.js |
Ellipse/arc curve with angle and rotation support |
src/gameobjects/pathfollower/PathFollower.js |
PathFollower Game Object (extends Sprite + PathFollower mixin) |
src/gameobjects/pathfollower/PathFollowerFactory.js |
this.add.follower factory registration |
src/gameobjects/components/PathFollower.js |
PathFollower component mixin (setPath, startFollow, pathUpdate, etc.) |
src/gameobjects/pathfollower/typedefs/PathConfig.js |
PathConfig typedef |