Object Sprite Direction Tutorial (8 Directions)

GM Version: Studio 2 (Possibly later versions of Studio 1)
Target Platform: Any
Download: None (Code below)
Links: round(), point_direction(), switch, sprite_index, image_speed
Summary:

Hello all I'm going to show you how to change your Object's sprite, based on direction.

It all starts with chopping 360 degrees into eight 45 degree octants, each switch case represents a direction based on that octant.

Think of an octant as the space of a pie piece, where the pie is split in 8 pieces. Each piece of pie represents the direction, the Object being the center of the pie. You can also think of it like a compass (image below). Each octant is the space between N & NE, NE & E, E & SE,....



Below the code checks the current position (x and y) against the previous position (xprevious and yprevious) and calculates direction. Using round(), we are rounding up or down (i.e. 5.8=6 & 5.2=5) the calculated direction using the point_direction function. We use that value to determine the current octant by using the switch function to set the Object's sprite in 8 directions by dividing the value by 45 creating 8 cases. (direction value is a cycle of 360 degrees)

Case 0 and 8 both mean the same direction (0 degrees and 360 degrees).
I just used both 0 and 8 for direction right/east

Also in the code we start and stop the sprite animation, using image_speed.

Enter the code below in your Objects End Step Event.

Code:
// Calculate direction based on previous x and y value and current x and y value; Switch 360 degrees into 8 45 degree octants (cases) by dividing point_direction value by 45
if x != xprevious || y != yprevious {  
// If Character is moving, start sprite animation
    image_speed = 1;
    switch(round(point_direction(xprevious,yprevious,x,y)/45)){
    case 0:
        sprite_index = spr_right;
        break;
    case 1:
        sprite_index = spr_up_right;
        break;
    case 2:
        sprite_index = spr_up;
        break;
    case 3:
        sprite_index = spr_up_left;
        break;
    case 4:
        sprite_index = spr_left;;
        break;
    case 5:
        sprite_index = spr_down_left;
        break;
    case 6:
        sprite_index = spr_down;
        break;
    case 7:
        sprite_index = spr_down_right;
        break;
    case 8:
        sprite_index = spr_right;
        break;
}
// If Character is not moving, stop sprite animation
}else {
    image_speed = 0;
}
 
Last edited:

chance

predictably random
Forum Staff
Moderator
Thanks for adding more detailed description of how you're dividing direction into eight octants. Absolute beginners without much mathematics background often find this confusing.

But I owe you an apology for suggesting you use floor(). Because I feel your original approach with round() was better. And you had a case 8, to handle 338 - 359 degrees. Using round(), the sprite change occurs symmetrically around each octant boundary, rather than at the boundary itself. So "up" (case 2) is 68 - 112 deg, rather than 90 - 135 deg.

Of course, you can add 22.5 deg to the point_direction() and then use floor(). But you have extra considerations around the 0 point. So in this case, your original approach is more intuitive.

Perhaps you can show both options in your tutorial. Up to you, of course.
 

FrostyCat

Member
Several notes on usage:
  • You forgot to handle the case for 8.
  • The prefix on the sprite IDs is wrong (notice you have obj_ instead of spr_).
  • speed will not work if the user is handling movement manually. You already have a check for change in position, so use that for image_speed too.
  • An image_speed of 2 will skip every other frame and waste the work that have gone into the skipped frames. Use an image_speed of 1 instead.
  • In situations like this where a variable's value depends on something else that runs 0, 1, 2, 3, ..., M-1, M, I recommend using an array instead of a switch block for brevity. This particular use case also has a tendency to demand reconfiguration at runtime (i.e. skinning), and implementing it as an array makes it trivial to swap one set of sprites for another.
Given the above, you could have achieved the same effect in just 7 lines:

Create:
Code:
walking_sprites = [spr_right, spr_up_right, spr_up, spr_up_left, spr_left, spr_down_left, spr_down, spr_down_right];
End Step:
Code:
if (x != xprevious || y != yprevious) {
    sprite_index = walking_sprites[round(point_direction(xprevious, yprevious, x, y)/45) mod 8];
    image_speed = 1;
} else {
    image_speed = 0;
}
 
Several notes on usage:
  • You forgot to handle the case for 8.
  • The prefix on the sprite IDs is wrong (notice you have obj_ instead of spr_).
  • speed will not work if the user is handling movement manually. You already have a check for change in position, so use that for image_speed too.
  • An image_speed of 2 will skip every other frame and waste the work that have gone into the skipped frames. Use an image_speed of 1 instead.
  • In situations like this where a variable's value depends on something else that runs 0, 1, 2, 3, ..., M-1, M, I recommend using an array instead of a switch block for brevity. This particular use case also has a tendency to demand reconfiguration at runtime (i.e. skinning), and implementing it as an array makes it trivial to swap one set of sprites for another.
Given the above, you could have achieved the same effect in just 7 lines:

Create:
Code:
walking_sprites = [spr_right, spr_up_right, spr_up, spr_up_left, spr_left, spr_down_left, spr_down, spr_down_right];
End Step:
Code:
if (x != xprevious || y != yprevious) {
    sprite_index = walking_sprites[round(point_direction(xprevious, yprevious, x, y)/45) mod 8];
    image_speed = 1;
} else {
    image_speed = 0;
}
Hello FrostyCat. Thank you for the suggestions. I added case 8, updated obj to spr (in my game, the sprites have the same name as the Object for the Character, but for a tutorial it looks cleaner with spr), and moved the image_speed variable up; I changed =2 to =1. I originally added case 8, but it didn’t seem to make a difference in practice when I execute the code; Maybe I just don't have good enough eyes. I like your method because, like you stated "for brevity," it is using shorter code for the same result. However, for the educational purposes of my tutorial, I'd like to keep the switch statement, but recommend your method in practice.
 
Last edited:
Top