Steering Behaviours: The AI system everyone needs for their game.

A

amusudan

Guest
GM Version: Game Maker: Studio
Target Platform: ALL
Download: Clicky
Links: Original Post, and follow-up of the original post (Please do check them out). And also this very helpful gallery with an explaination of each behaviour.

Summary:
This is a re-post of two reddit posts made a while back by PixelatedPope and devlkore, and the file (See "Download" above) contains all you need to add steering behaviours to your GM:S game!

Tutorial (Directly copied (almost) from the "follow-up" post):
I needed steering behaviours for my game, but there was nothing prebuilt, so I followed the breadcrumbs. Thanks to PixelatedPope, The Mojo Collective (TMC), http://tutsplus.com/authors/fernando-bevilacqua and Craig Reynolds ( http://www.red3d.com/cwr/steer/ ).

So what are these steering behaviours? Think swarms, think basic pathing, following and avoiding.
I've tried to make the project as simple to understand how to use as possible. There are a bunch of scripts that handle the steering behaviours, they all begin with "sb_..." and they are dependant on the scripts in the Vector folder and a few need the 2 scripts from GMLscripts.com (included).

There are a few objects (obj_drone...) set up already to show you how to use the scripts. The scripts themselves have some comments, but if you really want to understand what's going on, check out the tutorial by PixelatedPope and this series of tutorials (this is exactly what I did).

If you run the project as is (or standalone exe), you can see a bunch of steering behaviours running in various combinations to get an idea of what you can do with these, though the potential is far greater.

There is likely a bunch of optimisation to be done, but I wanted to get my head round the concepts involved before tightening it up fully. Most of the behaviours seem to have a pretty low CPU overhead EXCEPT the ones used for flocking (alignment, cohesion and separation) and to a lesser extent obstacle avoidance.​

List of behaviours:
sb_seek - Basic "go to here" behaviour (WILL constantly overshoot the destination).
sb_seek_arrive - Same as above except it will slow down and stop at the destination.
sb_flee - Opposite of seek, will move away from the specified point.
sb_pursuit - specify an object (must have position and velocity variables) and it will try to intercept it based on it's velocity. Look inside the script to change how T (time) is calculated from dynamic to fixed if desired.
sb_evade - Opposite of pursuit.
sb_wander - Will constantly change direction, amount and angle offset can be configured.
sb_path_loop - Will follow a path and start again from the beginning. Works on closed or unclosed paths.
sb_path_tofro - Follows a path then turns around when it reaches the end/beginning.
sb_obstacle_avoidance - Specify an object to avoid and a distance to "see", tries to avoid the object.
sb_alignment - Flocking. Tries to align its velocity to the average velocity of its neighbours.
sb_cohesion - Flocking. Tries to move to the centre of mass of its neighbours.
sb_separation - Flocking. Tries to move away from the centre of mass of it's neighbours.
 
P

ph101

Guest
Yeah this does indeed look awesome. I'm mid project so not sure I can use this, but it looks very cool, very interesting, plus appreciate the heads up to @Pixelated_Pope tutorial which also looks excellent - thanks!
 
Last edited by a moderator:
D

devlkore

Guest
I signed up to post here and notify y'all that the OP isn't actually the latest version. At the time @amusudan messaged me on reddit, my latest version thread was removed by auto mod for some reason. I've since had the thread restored, but I can't post links here until I have 5 posts.

I'll do some more posting after work and then just make a new thread with the latest version. If you are hungry for it RIGHT NOW, search "steering grid finally" on r/gamemaker and it'll be the only result.
 
A

amusudan

Guest
I signed up to post here and notify y'all that the OP isn't actually the latest version. At the time @amusudan messaged me on reddit, my latest version thread was removed by auto mod for some reason. I've since had the thread restored, but I can't post links here until I have 5 posts.

I'll do some more posting after work and then just make a new thread with the latest version. If you are hungry for it RIGHT NOW, search "steering grid finally" on r/gamemaker and it'll be the only result.
Welcome to the forums :).
 

king javo

Member
This is really awesome! I'm looking through your project now and not 100% sure yet how to `test` each behavior out. I'll probably figure it out, but if you happen to want to point me in the right direction, I'd really appreciate it! :)

EDIT: Figured it out.. haha.
 
Last edited:
V

v_roma

Guest
Hello,

Hoping to revive this thread to ask about something I've been trying to resolve. I have been playing with steering behaviors and have gotten all the behaviors to work. However, I was wondering if anyone has figured out a work around for the "jitteriness" (i.e., the very fast rotation from side to side) that the drones display. I've tried a number of things but the only way I've managed to get rid of this was by slowing down the image_angle rotation but I need to slow it down so much that this doesn't look good either.

EDIT: For those that come across this post looking for a solution to this problem as well, the code below works reasonably well. The number multiplied at the end can be adjusted for more/less "jitters".

Code:
image_angle -= angle_difference(image_angle, vect_direction(velocity))*max_force/2;
 
Last edited by a moderator:
V

v_roma

Guest
Have you tried using the lerp function?
I did. However, at least the way I was trying it, you run into issues when trying to interpolate directions around zero degrees (i.e., facing right) because the value of two directions that straddle zero degrees can be very different even though the directions are not.

I'm ok in my code above, though. It essentially does what I need it to. I'm looking into alternatives to steering behaviors, though, as there are other reasons I'm not totally happy with. I recently read about context behaviors and it sounds like it could be an interesting alternative.
 

king javo

Member
I did. However, at least the way I was trying it, you run into issues when trying to interpolate directions around zero degrees (i.e., facing right) because the value of two directions that straddle zero degrees can be very different even though the directions are not.

I'm ok in my code above, though. It essentially does what I need it to. I'm looking into alternatives to steering behaviors, though, as there are other reasons I'm not totally happy with. I recently read about context behaviors and it sounds like it could be an interesting alternative.
Did you ever get anywhere with Context Behaviors? I'm reading up on it and because I'm dealing with the same issues with Steering Behaviors and need a better solution?
 

Aajowa

Member
Thanks Amusudan! Not just for that, but because I followed the link in the link and discovered reddit has a gml thing, so...double thanks? Haha!
 
Sorry for the necro, but as i am currently also solving this problem and want to give some insights. The jittery behaviour of the solution comes from the used vectors for the adition.
To visualize it for an extreme Example i have uploaded an image.
The red vector is added every frame, and for that extreme example that means that the desired/target vectors are equaling -velocity vector (minus velocity vector). Thus resulting in an velocity vector of [0;0] at some point.
Thats a replication of the most efficient use of power for zero-g environments, but it doesnt look "gamey" enough for space shooters which want a "jet fighter style".
current.png




A better solution is the second image
using a normal vector to "steer" the velocity vector till it points into the target vector thus resulting in a real circular motion (look here: https://en.wikipedia.org/wiki/Centripetal_force and here: https://en.wikipedia.org/wiki/Circular_motion )
better.png


I would not use any lerps bc the used vectors in the addition are already close to a lerping of the values.
 
Just a quick example for the step event for a seek behaviour which overshoots and circles around.

Code:
steering = scr_vect2(0,0);

// needs a starting velocity vector and objects velocity vetor shall not reach [0,0] at any time
normal_right =   scr_vect2_truncate(scr_vect2(-velocity[2],velocity[1]),max_rot_force);
normal_left =   scr_vect2_truncate(scr_vect2(velocity[2],-velocity[1]),max_rot_force);


target_coordinate = scr_vect2(target_x,target_y);
target_vector =  scr_vect2_norm(scr_vect2_subtract(target_coordinate,position));
target_angle = point_direction(x,y,target_x,target_y);

velocity_norm = scr_vect2_norm(velocity);
velocity_angle = point_direction(0,0,velocity_norm[1],velocity_norm[2]);

angle_dif = angle_difference(velocity_angle,target_angle);

   if (angle_dif > 0){
       steering = scr_vect2_add(velocity,normal_right);
   }
   if (angle_dif < 0){
       steering = scr_vect2_add(velocity,normal_left);
   }




steering = scr_vect2_truncate(steering,max_force);
velocity = scr_vect2_truncate(scr_vect2_add(velocity,steering),max_speed);
position = scr_vect2_add(position,velocity);

x = position[1];
y = position[2];
image_angle = point_direction(0,0,velocity[1],velocity[2]);
 

ajrdesign

Member
Just a quick example for the step event for a seek behaviour which overshoots and circles around.

Code:
steering = scr_vect2(0,0);

// needs a starting velocity vector and objects velocity vetor shall not reach [0,0] at any time
normal_right =   scr_vect2_truncate(scr_vect2(-velocity[2],velocity[1]),max_rot_force);
normal_left =   scr_vect2_truncate(scr_vect2(velocity[2],-velocity[1]),max_rot_force);


target_coordinate = scr_vect2(target_x,target_y);
target_vector =  scr_vect2_norm(scr_vect2_subtract(target_coordinate,position));
target_angle = point_direction(x,y,target_x,target_y);

velocity_norm = scr_vect2_norm(velocity);
velocity_angle = point_direction(0,0,velocity_norm[1],velocity_norm[2]);

angle_dif = angle_difference(velocity_angle,target_angle);

   if (angle_dif > 0){
       steering = scr_vect2_add(velocity,normal_right);
   }
   if (angle_dif < 0){
       steering = scr_vect2_add(velocity,normal_left);
   }




steering = scr_vect2_truncate(steering,max_force);
velocity = scr_vect2_truncate(scr_vect2_add(velocity,steering),max_speed);
position = scr_vect2_add(position,velocity);

x = position[1];
y = position[2];
image_angle = point_direction(0,0,velocity[1],velocity[2]);
Can you expand on this a bit? This seems like something I'm very interested in understanding as I really want my objects behavior to be more of a slow rotation rather than spinning instantly when they overshoot their target but there's a lot of scripts in there that I don't recognize from the OP's batch of scripts so I don't really know how to implement this to try it out.
 
the scripts are a renamed and i have rewritten them for 2 dimensional vectors, but should work the same as the other downloads (asmusudan's download and pixelated pope's youtube vid)
simply use these with the same name after the "vect2_"



better commented/cleaned up version (the first code I uploaded had many additional AI values)
the object needs a starting velocity and new value max_rot_force to work correctly

Code:
//steering reset to 0;0 like in original
steering = scr_vect2(0,0);

// normal_right/left are the normal vectors of the current direction of movement, that means they are orthogonal to the current movement direction
normal_right =   scr_vect2_truncate(scr_vect2(-velocity[2],velocity[1]),max_rot_force);
normal_left =   scr_vect2_truncate(scr_vect2(velocity[2],-velocity[1]),max_rot_force);

// save target as vector
target_coordinate = scr_vect2(target_x,target_y);

// like in sb_seek, subtract the target coordinates from own position to get the vector directing from your object to the target
target_vector = scr_vect2_subtract(target_coordinate,position));

//angle the object needs to point to the target
target_angle = point_direction(x,y,target_x,target_y);

//current angle of movement
velocity_angle = point_direction(0,0,velocity[1],velocity[2]);

//calculate the difference
angle_dif = angle_difference(velocity_angle,target_angle);


// "turn left"/"turn right" according to angle_difference
   if (angle_dif > 0){
      steering = scr_vect2_add(velocity,normal_right);
   }
   if (angle_dif < 0){
      steering = scr_vect2_add(velocity,normal_left);
   }





//exact the same stuff like in the tutorial
steering = scr_vect2_truncate(steering,max_force);
velocity = scr_vect2_truncate(scr_vect2_add(velocity,steering),max_speed);
position = scr_vect2_add(position,velocity);

x = position[1];
y = position[2];
image_angle = point_direction(0,0,velocity[1],velocity[2]);
 

ajrdesign

Member
the scripts are a renamed and i have rewritten them for 2 dimensional vectors, but should work the same as the other downloads (asmusudan's download and pixelated pope's youtube vid)
simply use these with the same name after the "vect2_"



better commented/cleaned up version (the first code I uploaded had many additional AI values)
the object needs a starting velocity and new value max_rot_force to work correctly

Code:
//steering reset to 0;0 like in original
steering = scr_vect2(0,0);

// normal_right/left are the normal vectors of the current direction of movement, that means they are orthogonal to the current movement direction
normal_right =   scr_vect2_truncate(scr_vect2(-velocity[2],velocity[1]),max_rot_force);
normal_left =   scr_vect2_truncate(scr_vect2(velocity[2],-velocity[1]),max_rot_force);

// save target as vector
target_coordinate = scr_vect2(target_x,target_y);

// like in sb_seek, subtract the target coordinates from own position to get the vector directing from your object to the target
target_vector = scr_vect2_subtract(target_coordinate,position));

//angle the object needs to point to the target
target_angle = point_direction(x,y,target_x,target_y);

//current angle of movement
velocity_angle = point_direction(0,0,velocity[1],velocity[2]);

//calculate the difference
angle_dif = angle_difference(velocity_angle,target_angle);


// "turn left"/"turn right" according to angle_difference
   if (angle_dif > 0){
      steering = scr_vect2_add(velocity,normal_right);
   }
   if (angle_dif < 0){
      steering = scr_vect2_add(velocity,normal_left);
   }





//exact the same stuff like in the tutorial
steering = scr_vect2_truncate(steering,max_force);
velocity = scr_vect2_truncate(scr_vect2_add(velocity,steering),max_speed);
position = scr_vect2_add(position,velocity);

x = position[1];
y = position[2];
image_angle = point_direction(0,0,velocity[1],velocity[2]);
Thanks for cleaning it up. I finally got around to trying it out and I noticed that target_vector isn't being used at all so it doesn't seem to be doing a whole lot. Objects are just accelerating in one direction (Because I set their velocity to 1,1). Where is that target_vector intended to be used?
 

ajrdesign

Member
Okay I got this worked out with a few adjustments I'll try to point out here:

Code:
// normal_right/left are the normal vectors of the current direction of movement, that means they are orthogonal to the current movement direction
   normal_right =   vect_truncate(vect2(-velocity[2],velocity[1]),max_rot_force);
   normal_left =   vect_truncate(vect2(velocity[2],-velocity[1]),max_rot_force);

   // save target as vector
   target_coordinate = vect2(target.x,target.y);

   //angle the object needs to point to the target
   target_angle = point_direction(x,y,target_coordinate[1],target_coordinate[2]);

   //current angle of movement
   velocity_angle = point_direction(0,0,velocity[1],velocity[2]);

   //calculate the difference
   angle_dif = angle_difference(velocity_angle,target_angle);


   // "turn left"/"turn right" according to angle_difference
      if (angle_dif > 0){
         steering = vect_add(velocity,normal_right);
      }
      if (angle_dif < 0){
         steering = vect_add(velocity,normal_left);
      }
Took out target_vector because it wasn't doing anything (Please correct me if I'm missing something here). Then I just used target_coordinate values to determine the target angle.

This is the behavior with a separation behavior added on top of it: https://media.giphy.com/media/Ihgd5AFiScXbYul0Gl/giphy.gif
 

ajrdesign

Member
Another update. I transformed this back into a similar script to the ones @amusudan pulled together here. This can be used in the same way other steering behaviors can and should adhear to the same weight system that they all do:

Code:
/// @description sb_seek_slow(x, y, weight)
/// @arg x
/// @arg y
/// @arg weight
///Return vector2 for steering towards given point

var _target = vect2(argument[0], argument[1]);
var _weight = argument[2];
var _normal_right,_normal_left,_target_angle,_angle_dif,_new_steering,_velocity_angle;

// normal_right/left are the normal vectors of the current direction of movement, that means they are orthogonal to the current movement direction
   _normal_right =   vect_truncate(vect2(-velocity[2],velocity[1]),max_rot_force);
   _normal_left =   vect_truncate(vect2(velocity[2],-velocity[1]),max_rot_force);

   //angle the object needs to point to the target
   _target_angle = point_direction(x,y,_target[1],_target[2]);

   //current angle of movement
   _velocity_angle = point_direction(0,0,velocity[1],velocity[2]);

   //calculate the difference
   _angle_dif = angle_difference(_velocity_angle,_target_angle);


   // "turn left"/"turn right" according to angle_difference
   if (_angle_dif >= 0){
       _new_steering = vect_add(velocity,_normal_right);
   }
   if (_angle_dif < 0){
       _new_steering = vect_add(velocity,_normal_left);
   }
     

return (vect_multr(_new_steering,_weight));
 

ajrdesign

Member
Running into optimization issues with this now. Trying to figure out where to start with it. Having even a dozen or so object utilizing separation, wander, cohesion behaviors starts to take it's toll on the system. I've changed some of the behaviors to compensate for now but I'd like to be able to optimize some of this for future use.

Is there a way to reduce the number of vect_x calls or optimize those specifically? They seem to be the weightiest as they are being called sometimes 20+ times a step and are taking up a huge amount of each step.
 
sorry for necro-ing this post, but how do i make it such that when the target vector is behind the velocity vector, the ship doesn't just stop and turn, and instead keeps going on while turning a towards the direction of the target? is there a way to add a minimum speed limit to the ships? i was interested in the code of IgniFerroque and ajrdesign, and tried integrating it into the drone object's "steering behaviour usage", even renaming the scr_vect2_ stuff back into vect2_ , but for some reason, the ships just stop moving altogether. any help is appreciated. thanks

edit: added other details
 
Last edited:
sorry for necro-ing this post, but how do i make it such that when the target vector is behind the velocity vector, the ship doesn't just stop and turn, and instead keeps going on while turning a towards the direction of the target? is there a way to add a minimum speed limit to the ships? i was interested in the code of IgniFerroque and ajrdesign, and tried integrating it, even renaming the scr_vect2_ stuff back into vect2_, but for some reason, the ships just stop moving altogether. any help is appreciated. thanks

edit: added other details
I was hoping you were having the same issue as I am. The compiler error window keeps returning the error that the script 'collision_circle_list' needs 6 arguments where 8 are provided. All three times it is called, only 6 arguments are actually being provided. I'm not sure where the other 2 are being registered from. It also says malformed assignment statement. That usually happens in my projects when I declare an enumerator or macro incorrectly, but it doesn't seem like that is being done in this project. That being said, I'm out of ideas. It's too bad, because I thought I hit the holy grail finding this post.
 
Alright, guys, i`m reviving this post again.
Today I came across it, and tried to convert the sources for gms 2022.2.0.487. As the guys from 2021 wrote, it didn't start. I spent 15 minutes and rewrote everything that was broken. Function arguments for the current version of GMS have also been added.
A bit of details: position attribute is an attribute of an object passed as an argument to the collision_circle_list function. This attribute is nothing more than a vector created with the help of scripts attached to the project. The same goes for the velocity attribute. Otherwise, the project works, but the FPS leaves much to be desired. In any case, I hope that this will be useful to someone.
Download from google disk
 
Top