Making a sprite follow a path

jwatte's picture

A while back, I posted a simple program on the XNA forums that showed how to define a path, and then have objects follow that path. I figured I'd copy the code from there onto this site, for easy reference. The program below is a command-line C# program; you can compile it from the command line with "csc flypath.cs" and run it to test it out. Or you can copy the "follower" class from the middle, and use it in your XNA project. (The additional code, including the declaration of Vector2, is just there to make the test program work stand-alone)

using System;
 
namespace flypath
{
  /// <summary>
  /// Create an instance of class Follow, passing in the initial position of the object
  /// and its speed in pixels per second. Also pass in an array of Vector2 that describe
  /// the relative movement of the object. Call Move() each frame, passing in the number
  /// of seconds since the last frame (typically 0.166667f for 60 Hz) and get back the
  /// new position of the object. If Move() returns false, it means that the path has
  /// been completed.
  /// </summary>
  public class Follow
  {
    Vector2[] path_;
    Vector2 offset_;
    float speed_;
    float t_;
    float len_;
    int segment_;
 
    public static Vector2[] SomePath = new Vector2[] {
      new Vector2(-100, -100), // start out going down/left
      new Vector2(0, -200), // then go down-right until you're right below where you started
      new Vector2(0, -300), // then go straight down
      new Vector2(100, -100), // then go right/up
      new Vector2(0, 0), // then return to "home"
    };
 
    public Follow(Vector2[] path, Vector2 spawnPoint, float speed)
    {
      path_ = path;
      offset_ = spawnPoint;
      speed_ = speed;
      t_ = 0;
      //  Make sure the internal state machine knows we're at the first segment start point
      EnterSegment(0);
    }
 
    void EnterSegment(int seg)
    {
      //  the state machine has reached a new segment in the path to follow
      segment_ = seg;
      if (segment_ < path_.Length - 1)
      {  // calculate length to go
        //  calculate how long the path is, at our configured velocity
        len_ = (path_[segment_ + 1] - path_[segment_]).Length();
        t_ = 0; // I have not started moving here yet, so I'm at time 0 of this segment
      }
    }
 
    /// <summary>
    /// Move the object along the path, given some amount of time has elapsed.
    /// </summary>
    /// <param name="dt">The number of seconds that has elapsed since the last call.</param>
    /// <param name="pos">Returns the new position of the object.</param>
    /// <returns>false if the path has finished</returns>
    public bool Move(float dt, out Vector2 pos)
    {
      //  add to the current time along the path
      t_ += dt * speed_;
      //  if I reached the end of this segment, move to a new segment
      if (t_ >= len_)
      {
        EnterSegment(segment_ + 1);
      }
      //  calculate my position along the current segment (or, if complete, just return the start point)
      pos = (segment_ >= path_.Length - 1) ? offset_ : offset_ + path_[segment_] + (path_[segment_ + 1] - path_[segment_]) * (t_ / len_);
      //  return true as long as I'm still following the path
      return segment_ < path_.Length;
    }
  }
 
  public class Program
  {
    static void Main(string[] args)
    {
      Vector2 initPos = new Vector2(300, 600);
      Follow f = new Follow(Follow.SomePath, initPos, 20);
      Console.WriteLine("First pos: {0}", initPos);
      while (f.Move(1, out initPos))
      {
        Console.WriteLine("New pos: {0}", initPos);
      }
      Console.WriteLine("End pos: {0}", initPos);
      return;
    }
  }
 
  //  In this test program, I define my own Vector2, so that I don't have to
  //  link with the entire XNA framework.
  public struct Vector2 {
 
    public Vector2(float x, float y) { X = x; Y = y; }
 
    public float X;
    public float Y;
    public float Length() { return (float)Math.Sqrt(X*X + Y*Y); }
 
    public override string ToString()
    {
      return String.Format("{0:0.0},{1:0.0}", X, Y);
    }
 
    public static Vector2 operator +(Vector2 a, Vector2 o) { return new Vector2(a.X + o.X, a.Y + o.Y); }
    public static Vector2 operator -(Vector2 a, Vector2 o) { return new Vector2(a.X - o.X, a.Y - o.Y); }
    public static Vector2 operator *(Vector2 a, float s) { return new Vector2(a.X * s, a.Y * s); }
  }
}