Wednesday, August 14, 2013

AndEngine PathModifier Tutorial

A lot of my recent AndEngine projects have used a PathModifier for one reason or another. I have a solid understanding of them now and they are pretty straight forward after you get to know them, but I decided to write this tutorial for people who are looking to start using some path modifiers, but don't know where to start. This tutorial assumes the reader is somewhat familiar with AndEngine and has a basic understanding of creating sprites and the like.

Simple Pathing
So, lets say we want to move a sprite along a path. Well first we need a sprite, but there is nothing special we need to do to set the sprite up for pathing. We just make a normal sprite:

  Sprite ourCharacter = new Sprite (100, 100, CharacterTextureRegion, this.getVertexBufferObjectManager());  

Now we have our sprite and we want to move it from its starting location at the coordinates (100, 100) to the coordinates (600, 350). We need to make a new path object for it to follow. In AndEngine, a Path is a series of coordinate points that the sprite will follow. It is important to remember that the sprite's origin point, or the top left corner, is what will actually moving to the points you specify in your path. So lets make our path:

 Path ourPath = new Path(2).to(100, 100).to(600, 350);  

In the first set of parentheses after the word "Path" we specify the number of points we are going to have in our path. In this case, we have two points. After that, we tack on our coordinate points using the ".to(x, y)" function. We start with our origin point in the series of points, then we add on any more points we want in the path.

We now have our Path object set up. We want to make our Sprite follow that path. To do this, we need to add our PathModifier on the Sprite using a call to registerEntityModifier and passing in a new PathModifier.

 ourCharacter.registerEntityModifier(new PathModifier(3.0f, ourPath, EaseLinear.getInstance()));  

When we make the PathModifier, we pass in a float representing the number of seconds we want it to take for the sprite to follow the path (in this case it is 3 seconds), our Path object, and and Ease Function. The ease function is how the sprite is going to follow the path. For example, the EaseLinear in the code above will have the sprite move from one point to the next at a consistent speed. When moving a character sprite along a path, I like to use EaseSineInOut, which will cause the sprite to slow down right before they reach a point in the path, and then speed up after leaving a point. I feel it gives a more "natural" feel as things slow down to change direction and such. There are a whole host of EaseFunctions to use. A few are listed below, but there are more and you can check out the AndEngine example app under "Modifiers & Animation" to see them in action.

EaseFunctions
  • EaseLinear
  • EaseBounceIn, EaseBounceOut, EaseBounceInOut
  • EaseElasticIn, EaseElasticOut, EaseElasticInOut
  • EaseSineIn, EaseSineOut, EaseSineInOut
  • EaseStrongIn, EaseStrongOut, EaseStrongInOut
And there you go. Our sprite will now move along the path we specified.

"Advanced" Pathing
The path we just did is cool, maybe for something like a title image sliding onto the screen or our character walking from one point to another. But maybe we want our character to patrol the perimeter of a square building and we want it to move along the path over and over. Well, we can do that.

First, we are going to make a longer, square path:

 Path ourPath = new Path(5).to(100, 100).to(100, 300).to(300, 300).to(300, 100).to(100, 100);  

Notice that this time our path has 5 points. But why do we need 5 points? We are moving in a square, and our square only has 4 corners. Well, we need the first one to establish the origin point of our path and the additional 4 points to move the sprite 4 times (sliding along the 4 sides of the square). We want the path to be a continuous loop, so our first point and our last point are the same. In the first figure in the image below, you can see the path the sprite will take with all five points. The second figure shows the path the sprite will take if we only use 4 points on the path.

So, we now have our path set to follow a square path and return to its original location just by plugging it into our PathModifier up above. But, we want our sprite to repeat this path indefinitely. So lets add in a loop entity modifier, shall we?

 LoopEntityModifier ourLoop = new LoopEntityModifier(new PathModifier(5.0f, ourPath, EaseSineInOut.getInstance()));  
 ourCharacter.registerEntityModifier(ourLoop);  

And there we go. Our sprite will move in this square continuously until you get rid of the entity modifiers on it.

But we aren't done yet. Path modifiers can be set up with listeners to determine when the path is finished, started, or hits a point on the path. Let's have our sprite do some things as it moves around our square. We'll set up our path modifier with an IPathModifierListener. The listener will require us to override 4 functions: onPathStarted, onPathFinished, onWaypointStarted, and onWaypointFinished. We'll add the path modifier listener and its requisite functions to our LoopEntityModifier.

 LoopEntityModifier ourLoop = new LoopEntityModifier(new PathModifier(speed, path, new IPathModifierListener()  
 {  
      @Override  
      public void onPathFinished(PathModifier pPathModifier, IEntity pEntity) { }
  
      @Override  
      public void onPathStarted(PathModifier pPathModifier, IEntity pEntity) { } 
 
      @Override  
      public void onPathWaypointFinished(final PathModifier pPathModifier, final IEntity pEntity,  
                final int pWaypointIndex) { }  

      @Override  
      public void onPathWaypointStarted(final PathModifier pPathModifier, final IEntity pEntity,  
                final int pWaypointIndex) { }  
 }, EaseSineInOut.getInstance()));  
 ourCharacter.registerEntityModifier(ourLoop);  

There we go. We're all set. We just need to add anything we want it to do. We'll start with the onPathFinished function. Lets say we have an integer variable called "LoopCount" and we simply want to increment the loop count whenever we the loop finishes. Piece of cake.

      @Override  
      public void onPathFinished(PathModifier pPathModifier, IEntity pEntity)   
      {  
            LoopCount++;  
      }  

Now, lets rotate our sprite to 180 degrees at the start of each path if LoopCount is even. Why would we do this? Nobody knows, but we can, so we're going to.

      @Override  
      public void onPathStarted(PathModifier pPathModifier, IEntity pEntity)   
      {   
           if(LoopCount % 2 == 0)  
                pEntity.setRotation(180);  
           else  
                pEntity.setRotation(0);  
      }  

In the code above, we check if the loop count is even like we wanted and set the rotation to 180 degrees if it is, or 0 degrees if it is odd. Notice that when we set the rotation of the sprite, we actually use the reference that was passed into the function. Here we have called it pEntity.

There we have it. Our sprite will swap rotations every time it loops around the square. We're almost done with our path here but we have one final thing we want our sprite to do. Every time the sprite reaches the bottom left or the top right corner of our square path, we want to Scale our sprite up to double its normal size and then reset the size back to normal on the other two corners.

Now, here is where things start to get tricky. First, lets mention how "onPathWaypointFinished" and "onPathWaypointStarted" work. In both of the functions, an integer value is passed in, which represents the point on the path that has resulted in the function call. For example, if it is the first point on the path, the number passed in will be 0. With "onPathWaypointFinished", the function is called sprite reaches a point on the path. Alternatively, "onPathWaypointStarted" is called as soon as the sprite starts heading to a point on the path from the previous point. In both cases it is important to realize that for the listener, the origin point is not point 0. Point 0 is the first point the sprite will move to after leaving the origin point.

We want our sprite to change scale every time it reaches the bottom left corner (point 0) or the top right corner (point 2) and have it change back to normal at every other point. Since we dont want it to scale as it move towards the bottom left corner of our path, but rather when it actually reaches the point, we are going to use "onPathWaypointFinished" instead of "onPathWaypointStarted". We're going to use a simple switch statement and the passed in value of pWaypointIndex:

      @Override  
      public void onPathWaypointFinished(PathModifier pPathModifier, IEntity pEntity,  
                int pWaypointIndex)             
      {   
           switch(pWaypointIndex)  
           {  
                case 0:  
                case 2:  
                     pEntity.setScale(2.0f);  
                     break;  
                default:  
                     pEntity.setScale(1.0f); 
                     break;
           }  
      }  

And there you have it. Our sprite is now moving in an endless square shape, upside down every time the loop count is even and doubling its size every time it reaches the bottom left or top right corners.

Path modifiers can be insanely helpful for things in your app. Making title images slide in from off screen, having characters move around, or perhaps having an item slide off the screen after your character collects it. There are a lot of possibilities and a strong understanding of Pathing is great to have if you are working with AndEngine.

I hope this tutorial helps anybody who might stumble across it. If I need to explain anything more clearly or I left something out, please leave me a comment or suggestion.

9 comments:

  1. hi
    this one is very nice tutorial, it help me a lot, but when i applied your code in my project
    my sprite is not moving as you said ,that registerenityModifier with sprite but instead i registerentitymodifier with my scene and it start working.

    ReplyDelete
    Replies
    1. Glad this post helped! Provided your sprite is attached to the scene and you are passing in a path modifier, registerEntityModifier should be working.

      Example: sprite1.registerEntityModifier(pathModifier);

      Delete
  2. I'm so surprised to see such a quality andengine guide. Thumbs up dude helps me alot. The world needs more people like you. I hope more people will managed to find this post.

    ReplyDelete
    Replies
    1. Glad this post could help. When I started with AndEngine I couldn't find any examples of pathing that I found particularly helpful. It was a lot of playing around with it to get it down, and thought I'd try to help some other people out.

      Delete
  3. Hi kyle ,
    i am facing a problem with the OnPathFinished.
    i wanted my sprite to stay on finish line so i added pEntity.setPosition(x,y); in it .. but nothing happens.. my sprite falls back to where i created it after the path ..

    Also tried clearEntityModifiers but got OutOfBounds exception ..
    Any help would be highly appreciated ..
    Thanks

    ReplyDelete
    Replies
    1. Hi goonzAks,

      I have some ideas about what might be happening, but a bigger snippet of your code would be helpful.

      Delete
    2. Thanks for the reply kyle, but i found the problem. I think it was an update handler which was causing it. Works fine now.
      Great tut btw, learn path modifiers here within secs
      thumbs up (Y)

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Thanks, Kyle. Today, I needed to quickly learn about Paths for a demo in a few hours, and your tutorial was a lifesaver!

    ReplyDelete