Game Maker: Gradually rotating an object towards a target

You can get the GML script here. This post will explain the code.

When I was developing enemies in Yxi, I was trying to get turrets to aim at you, but I didn’t want them  to possess “instant aim”. I wanted them to rotate towards you given a maximum rotation speed. If you move too fast, it should turn towards you gradually, not instantly.

It seems that it wasn’t as simple as I thought it was. I had the basic algorithm down, but because of the way Game Maker handled angles, the turrets would behave strangely when you would hover around the 0°/360° point with respect to the turret.

I figured a tutorial would be necessary for something that isn’t so obvious to implement. This only pertains to Game Maker and thus uses the GML language.

There are three steps involved when gradually rotating an object (let’s call it a turret for now) towards a target object:

  1. Calculate the target’s direction with respect to the turret’s position.
  2. Calculate the angle difference between the direction derived from step 1 (let’s call “target direction”) and the turret’s facing direction.
  3. Apply angular rotation towards the target so that the target direction is the facing direction.

Calculating Target Direction

This step is relatively easy if you know Game Maker’s point_direction() function. It takes in the object’s position and the target’s position and returns the direction pointing from the object to target (which we already dubbed “target direction”).

var targetDir = point_direction(objToTurn.x, objToTurn.y, target.x, target.y);

Here’s a picture to visualize (turret is objToTurn):

yxi22

Calculating the Difference between Target and Facing Direction

This part was personally the hardest for me.  The problem is that Game Maker changes angles from 359° to 0° (assuming you’re incrementing the angle) and that throws off calculations.

The angle difference is usually the facing direction subtracted by the target direction:

var facingMinusTarget = facingDir - targetDir;
var angleDiff = facingMinusTarget;

yxi23

It’s more complicated when the difference involves one of the direction being  near 360° and the other direction being near 0°.  The angleDiff is way off and would cause the turret to spin all the other way around in order to aim at you. You can see that would make the turret look pretty dumb.

The solution is to adjust the angleDiff if the difference between the facing and target directions are greater than 180°. If the angleDiff is greater than 180°, the adjustment is different depending on whether the facing direction has the greater angle value or if the target direction does.

Basically, you have to measure the shortest angle between the facing direction and 360º, then the shortest angle between the target direction and 360º, and add those two differences together. This is best explained the the diagram below:

yxi24

The result has to be negated if the facing direction is greater than the target direction because we want the angle difference as defined by facing - target. In that particular situation, the difference is negative, but is not factored into the algorithm explained above.

Here’s that whole explanation in code:

if(abs(facingMinusTarget) > 180)
{
    if(facingDir > targetDir)
    {
        angleDiff = -1 * ((360 - facingDir) + targetDir);
    }
    else
    {
        angleDiff = (360 - targetDir) + facingDir;
    }
}

Applying Angular Rotation towards the Target

Now that we have the proper angle difference (whose absolute value should be less than 180º), we can apply a change in rotation to the turret’s direction (its facing direction). My code considers accuracy, so I’ll explain that now.

We could always compare if the angleDiff is greater or less than 0, but sometimes you don’t want the turret to have deadly precision. That’s why the script I linked above allows you to adjust the accuracy. What “accuracy” means here actually defines the angular range the turret considers being locked onto its target. If the target is in this angular range, the turret will stop rotating.

yxi25

This accuracy range is defined as the least accurate aiming angle multiplied by an accuracy percentage (in the code, a number between 0 and 1). At 100% accuracy, the range would be very slim, giving the turret high precision. At 0% accuracy, the range would be as wide as the defined worst accurate aiming angle. By default, it’s 30º in my script. You give an accuracy of 1.0 (most accurate) to 0.0 (least accurate) and the script inverses them (0 becomes 1 and 1 becomes 0) so it can define the accuracy range.

If the angle difference is greater than the accuracy range, rotate clockwise. Otherwise, if the angle difference is less than the accuracy range, rotate counterclockwise. Not that the accuracy range will always be a positive number, while the angle difference can be either positive or negative.

var leastAccurateAim = 30;
if(angleDiff > leastAccurateAim * accuracy)
{
    objToTurn.direction -= turnSpeed * delta_time;
}
else if(angleDiff < leastAccurateAim * accuracy)
{
    objToTurn.direction += turnSpeed * delta_time;
}

And here’s a diagram:

yxi26

Using the Script

Using the script itself is pretty easy. Assuming the object you want to gradually rotate is a turret, you put the script in the turret object’s step event. Here’s how the script should be invoked:

if(instance_exists(target_obj))
{
    // FORMAT:
    // gradually_turn(objToTurn, target, turnSpeed, accuracy);
    gradually_turn(self.id, target_obj, 5, 1.0);
}

You should always check if the target object exists, otherwise Game Maker will signal a runtime error. The first parameter is the object you want to rotate. The second parameter is the target object. The third parameter is the turn speed; adjust this number to your liking. And the fourth number is the accuracy ratio; it has to be a decimal number between 0.0 and 1.0.

Hopefully that explains how to gradually rotate an object towards a target in Game Maker. Feel free to use the code I linked above.

Advertisements

15 thoughts on “Game Maker: Gradually rotating an object towards a target

    • Did you name the script “gradually_rotate” (without quotes) in Game Maker’s resource browser? Usually creating a new script names it something like “script1”, or with some other number. You can right click the script in the resource browser and select Rename to change the script’s name.

  1. Dear Jibran Syed,

    What an interesting algorithm! I’m trying it on my computer, but it’s not working.

    I use Gamemaker 8.0 (pro edition). This version of Gamemaker somehow doesn’t know the variable ‘delta_time’. Do you know how this is possible? Should I upgrade to GM 8.1 or GM Studio? Are there any alternatives?

    Keep up the good work!

    Greetings from The Netherlands,

    Wout

    • Hello Wout,

      Game Maker 8.0 and 8.1 don’t have a built-in variable “delta_time”. The simplest fix to this problem is omit “delta_time” from lines 50 and 54. However, if you do this, you have to adjust the “100000” on line 17 in order to get a reasonable turn speed. You’ll have to play around with that number.

      Implementing this fix should allow you to use the script in Game Maker 8/8.1. But because you’re not using delta_time, there is a chance that the turning won’t be as smooth looking. Hopefully it won’t be very noticeable.

      • Jibran,

        Thanks for your reply! I removed ‘delta_time’.
        There is, however, a few more issues:

        – The objToTurn doesn’t turn at all in my game.
        – Gamemaker 8.0 doesn’t recognize the function ‘clamp()’ (line 18)

        I really don’t know how to fix this, because I’m not familiar with all these new functions.
        Do you know how I can fix this? Or are there any alternatives?

        • If you made the “100000” on line 17 bigger, the “objToTurn” will probably not turn. It would be a better idea to make the number smaller (like as small as 10 or something).

          As for clamp(), what you can do is omit clamp() by changing line 18 to:

          var accuracy = argument3;

          Then right after this line, write the following if-else-if statement, which does the same thing as clamp():

          if(accuracy > 1.0)
          {
          accuracy = 1.0;
          }
          else if(accuracy < 0.0)
          {
          accuracy = 0.0;
          }

          Hopefully that will help. As an alternative, if you want to run my script without modifying it and you're willing to pay for Game Maker Studio, you can try my script unmodified there. It should work.

        • Dear Jibran!

          It works now, even on Gamemaker 8.0, thanks to your good advice! Thank you so much! What an amazing algorithm, very interesting indeed!

          All the credits to you!
          Keep up the good work!

          Greetings from the Netherlands,
          Wout

  2. Hi Jibran,
    Thanks for posting this! I’m trying to use the gradually_turn function to have my object turn rotate towards an object when it jumps and rotate towards a different object when it’s not jumping. I’m not getting any errors when running the script and I’m instantiating the function in the step as you suggested. The problem is my objToTurn is not rotating at all what is actually happening is it is moving left and right in a very odd manner.

    Any help would be really appreciated.

    Thanks!

    Mike

    • You will probably need to post your code in order for me to understand what is going on. Can you explain what you mean by “moving left and right in a very odd manner” ?

      • Hi Jibran,

        I’m working with Mike and the code we are using is as follows:

        if (mouse_check_button_pressed(mb_left))
        {
        gradually_turn(obj_birdgame, obj_jumpangle, 5, .1);
        }

        else
        {
        gradually_turn(obj_birdgame, obj_fallangle, 5, .1);
        }

        We are using your script exactly how you wrote it. The only difference I can see is that the objects we want our obj_birdgame to rotate towards are objects that move along with the bird. They have the same set vertical speed that the bird does when the left mouse is pressed.

        As far as going side to side what Mike meant is that the bird travels along the x axis even though he is supposed to not do so. The bird only moved along the y axis until we implemented your script.

        • Hello,

          I was making a little prototype to replicate the problem you guys had. It’s by no means an exact replication, but I noticed a few things while testing and I would like to list some suggestions. I hope these suggestions help. They are:

          (1) Change mouse_check_button_pressed() to mouse_check_button(). The former checks only once if the mouse button was down since mouse release. The latter checks every frame (step) if the button is down.

          (2) From what you described, it seems that the bird object is moving only along one axis, but its sprite is rotating. You would have to change some things in my script in order to just change the sprite’s angle instead of the object’s angle. To do that, you change every mention of the variable direction to image_angle. They are two separate variables, and unless you specify, they should not interfere with each other. References to direction are on lines 29, 50, and 54.

          (3) I’m not sure if you guys did this already or not, but be sure that the bird sprite has its origin at the center.

          Tell me if these suggestions help or not.

  3. Pingback: Game Maker: Gradually rotating an object towards a target | professionalgame

  4. I went through this, implemented the code, and got everything to work. I now have bad guy objects who periodically rotate to face the player object, and shoot at him. The problem is these bad guy sprites, have 2 guns on them. Ideally if they were to remain facing one direction, the object would be creating two projectiles at a given + and – value off of X. However creating a movement sprite at +/- X means that no matter which direction the firing object faces, both shots always come from their set value off of the sprite’s origin. In other words, the closer the bad guy object faces towards the X plane, the more the shots look like they’re originating from the object’s center, and not the two guns on it’s sides.

    So I know I need to give the Create Moving Object commands (that are generating the projectiles) delta values for it’s X/Y variables. An ever shifting value for both projectile creation events that ensures the projectiles always come from a value + and – in a perpendicular direction from the object’s facing direction. Bleh… Do you have any idea on how to code this?

Comments are closed.