Rotating slowly toward a target while also moving toward that target

This one has me a bit stumped, as I’m not great with this type of math…

So, I have two pieces of code that do one of the other. One moves towards the target, and the other rotates the object towards that target at a set rotational speed.

Rotational code -

            Vector3 targetPos = target.transform.position;
            float angle = Mathf.Atan2(targetPos.y - transform.position.y, targetPos.x - transform.position.x) * Mathf.Rad2Deg;
            Quaternion targetRotation = Quaternion.Euler(new Vector3(0f, 0f, angle + 90));
            transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotationSpeed);

Move-towards code -

            Vector2 toTarget = (target.transform.position - transform.position).normalized;
            projRB.velocity = toTarget * moveSpeed;

I would just like to combine the two so that I can have an object keep it’s velocity while slowly rotating towards the target, if that makes sense.

With Physics (or Physics2D), never manipulate the Transform directly. If you manipulate the Transform directly, you are bypassing the physics system and you can reasonably expect glitching and missed collisions and other physics mayhem.

Always use the .MovePosition() and .MoveRotation() methods on the Rigidbody (or Rigidbody2D) instance in order to move or rotate things. Doing this keeps the physics system informed about what is going on.

https://discussions.unity.com/t/866410/5

https://discussions.unity.com/t/878046/8

If you want an example of moving and turning, see the attached complete one-scene one-script game.

8240049–1077429–MissileTurnTowards.unitypackage (12.1 KB)

1 Like

Ah, that makes sense. I’ll keep that in mind. Thanks Kurt :wink:

So, Kurt, you said that I shouldn’t be manipulating the transforms directly, but the code in that package does this -

Missile.transform.rotation = Quaternion.Euler(0, 0, CurrentMissileHeading);

Is that not the same thing, only on a specified game object? Perhaps I’m confused, because it looks like you’d be assigning a game object in the inspector with this script, but in my case the script was already attached to the object.

What am I missing? Is GameObject.transform different from just using transform?

Anyhow, I managed to get things working. I appreciate the examples :wink:

Need to resurrect this, as I thought that I had things working correctly, but it appears not.

The problem is simple - What I’d like is to have the projectile rotate in the direction of the player. So, if the projectile was moving straight up, and the player was to the left of the projectile, it would rotate to the left towards the player. If the player was to the right of the projectile… well, you get the idea.

Right now, If the projectile starts off moving to the left, it will stop dead in its tracks and immediately start moving/turning to the right towards the player. If the projectile starts off moving to the right, it will sort of work, but looks a bit jarring.

Based on the project given in Kurt’s post, I am using this code.

        if (target != null)
        {
            Vector3 delta = target.transform.position - transform.position;
            float headingToTarget = Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg;
            float angleError = Mathf.DeltaAngle(headingToTarget, currentHeading);

            if (Mathf.Abs(angleError) > 2.0f)//Mathf.Abs
            {
                currentHeading = Mathf.MoveTowardsAngle(currentHeading, headingToTarget, (rotationSpeed) * Time.deltaTime);
            }

            transform.rotation = Quaternion.Euler(0, 0, currentHeading);
            transform.position += transform.right * moveSpeed * Time.deltaTime;
        }

I’m not sure what I’m doing wrong here, except messing with transforms directly, but in the examples, at least from what I can tell, it’s doing the same thing? I must be missing something there.

EDIT: OK, so I have fixed this issue by making a couple of changes to the code above. First, I change the line

currentHeading = Mathf.MoveTowardsAngle(currentHeading, headingToTarget, (rotationSpeed) * Time.deltaTime);

into this

currentHeading += (rotationSpeed * Time.deltaTime);
currentHeading %= 360;

//or

currentHeading -= (rotationSpeed * Time.deltaTime);
currentHeading %= 360;

I use either one based on which direction the projectile was moving when it started flying. Then, I can switch back to the previous code if the projectile rotates past the player. I can determine if the projectile rotates past the player by using a Raycast2D, and checking for the player layer. If it finds the player, I switch back to use the other code. A simple bool should suffice there. This way, the projectile can once again rotate towards the player.

There might be a better way to handle this, or even a much simpler way, but this is what I came up with, with the help of a friend of mine.

1 Like

After further testing, this only works if the projectiles were initially heading in the positive X direction.

If the projectile starts moving in the negative X direction, they suddenly make a right turn 90 degrees before rotating towards the player…

I just… can’t win today…

I think you want to revisit the MissileTurningTowards code… it is pure math and doesn’t care about minuses or pluses… did you perhaps mis-implement it, perhaps give the wrong X or Y or Z or something? It really should work… and you can test it by making the chasing object have zero velocity, so you just move the target around and get it working. Sometimes you have to negate one of the axes going into Mathf.Atan2() depending on how to order it and what your “zero rotation” really is (north, east, south, west).

Hey Kurt,

The only things I left out are these two lines -

MissileTurnRate += MissileTurnRateIncrease * Time.deltaTime;

MissileSpeed += MissileAcceleration * Time.deltaTime;

It appeared to me that these were just velocity and turn rate manipulators. Am I wrong there?

Here’s my current implementation -

        if (target != null)
        {
            Vector3 delta = target.transform.position - transform.position;
            float headingToTarget = Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg;
            float angleError = Mathf.DeltaAngle(headingToTarget, currentHeading);

            RaycastHit2D hit = Physics2D.Raycast(transform.position, transform.right, 500f, playerLayer);

            if (Mathf.Abs(angleError) > 2.0f)
            {
                if (hit.collider != null && !raycastPlayerHit)
                {
                    raycastPlayerHit = true;
                }
                if (raycastPlayerHit)
                {
                    currentHeading = Mathf.MoveTowardsAngle(currentHeading, headingToTarget, (rotationSpeed) * Time.deltaTime);
                }
                else
                {
                    if (moveInAltDir)
                    {
                        currentHeading += (rotationSpeed * Time.deltaTime);
                        currentHeading %= 360;
                    }
                    else
                    {
                        currentHeading -= (rotationSpeed * Time.deltaTime);
                        currentHeading %= 360;
                    }
                }
            }

            transform.rotation = Quaternion.Euler(0, 0, currentHeading);
            transform.position += transform.right * moveSpeed * Time.deltaTime;
        }

Here’s the original (without comments for simplification purposes) -

        Vector3 delta = Player.transform.position - Missile.transform.position;


        float DesiredHeadingToPlayer = Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg;


        float AngleError = Mathf.DeltaAngle(DesiredHeadingToPlayer, CurrentMissileHeading);


        if (Mathf.Abs( AngleError) > AcceptableAngleError)
        {
            CurrentMissileHeading = Mathf.MoveTowardsAngle(CurrentMissileHeading, DesiredHeadingToPlayer, MissileTurnRate * Time.deltaTime);
        }


        MissileTurnRate += MissileTurnRateIncrease * Time.deltaTime;


        Missile.transform.rotation = Quaternion.Euler(0, 0, CurrentMissileHeading);


        MissileSpeed += MissileAcceleration * Time.deltaTime;


        Missile.transform.position += Missile.transform.right * MissileSpeed * Time.deltaTime;

I would welcome your input here. Obviously I’ve done something wrong.

OH, and you know what… this… might be important to include. This is the initial movement of the projectile. This is run on a timer, so once that timer ends, it starts the above rotate-and-move-toward-player code.

transform.position += new Vector3(moveSpeed * sourceLookDirection, 10f) * Time.deltaTime;

//This is the initial movement of the projectile

//sourceLookDirection is the direction the enemy was facing when it instantiated the projectile - either a 1 or -1 (obviously)

Maybe the initial movement has something to do with it?

Correct, those are just “game accelerators” to make it steadily get harder.

You just gotta instrument it and set it up with a mouse position as the target, and stationary needle to face it. No other way is going to solve what is going wrong… it’s sort of like inventing fire. Once you get it working you can save that file and use it again and again in every game, but sometimes setups change (like maybe you’re working on X/Z plane) and you have to re-discover fire. In all case, strip everything out, make a testbed, plug stuff in and get it going.

Well, what’s interesting is that the original code from that file… yes, it does work, and it did work fine. Except for one thing - No matter where the player was, the projectile always rotated to the right - clockwise. I couldn’t get it to rotate counterclockwise. That was what led me down this path of… whatever this path currently is.

That’s just not true.

I just tested now.

Stop the missile.

Set its turn rate super slow (5 degrees/sec)

You’ll see it works as advertised all the way around the clock as you “sneak behind” the missile and come out on its other side and it starts rotating the other way. Here was my patch:

after adjusting turn rate:

MissileTurnRate = 5.0f;

after adjusting speed:

MissileSpeed = 0;

Perhaps I should have been a bit more specific here…

In my testing, with my code, the projectile always rotated clockwise. Sorry for the confusion.

I’ll run my own tests on a new project and see if I can’t get get something working :wink:

OK, so some progress here…

Three things I’ve discovered. One - I left out part of the code I should have included -

    protected void FaceMoveDirection()
    {
        float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
        transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    }

That code was running in the initial movement of the projectile before the timer had ended, and the turning code began.

Two - My projectile sprite is facing to the right. Is that generally how it’s done in Unity? I’m so used to modding Terraria, it’s a whole other beast. Before I get to number three, here’s the code from my new project -

public class MissleController : MonoBehaviour
{
    public GameObject target;
    public Rigidbody2D rB;
    private float timer;
    private float moveSpeed;
    private float rotationSpeed;
    private float currentHeading;
    private Vector2 moveDirection;

    void Start()
    {
        rB = GetComponent<Rigidbody2D>();
        moveSpeed = 1f;
        rotationSpeed = 30f;
        currentHeading = transform.eulerAngles.z;
    }

    void Update()
    {
        timer += Time.deltaTime;

        if (timer < 2f)
        {
            transform.position += new Vector3(0.1f, moveSpeed) * Time.deltaTime;
            moveDirection = new Vector3(0.1f, moveSpeed); // - this is part of the FaceMoveDirection() function
            //FaceMoveDirection(); - this might have been causing issues..
        }
        else
        {
            Vector3 delta = target.transform.position - transform.position;
            float headingToTarget = Mathf.Atan2(delta.y, delta.x) * Mathf.Rad2Deg;
            float angleError = Mathf.DeltaAngle(headingToTarget, currentHeading);
            if (Mathf.Abs(angleError) > 2.0f)
            {
                currentHeading = Mathf.MoveTowardsAngle(currentHeading, headingToTarget, (rotationSpeed) * Time.deltaTime);
            }

            transform.rotation = Quaternion.Euler(0, 0, currentHeading);
            transform.position += transform.right * moveSpeed * Time.deltaTime;
        }
    }

    protected void FaceMoveDirection()
    {
        float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
        transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
    }
}

With this code, the projectile moves up, but with the sprite facing to the right, so it appears to be going sideways… up. If that makes sense. When I start the game, I move the player left or right, and the projectile always rotates clockwise. Now let’s get to number three…

Three - I changed the Z-rotation in the inspector to 90, instead of 0 (I didn’t look at the original missile object in the project file - if I had, I would have seen it was rotated 90 degrees on the Z axis). The projectile is now facing up as it should, and rotates clockwise or counterclockwise depending on the player being to the right or left of the projectile when it starts to rotate. I thought this last part was interesting.

Anyhow, talk about a learning experience…

There’s only one problem left that I can’t seem to figure out.

This is how I was setting the projectile’s initial movement when instantiated -

private void SetInitialVelocityAndDirection()
{
  if (moveTimer < stopTimer)
  {
     transform.position += new Vector3(moveSpeed * sourceLookDirection, 10f) * Time.deltaTime;

     moveDirection = new Vector3(moveSpeed * sourceLookDirection, 10f);

     FaceMoveDirection();
  }
}


public void FaceMoveDirection()
{
    float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg;
    transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}

If I disable the FaceMoveDirection() function, everything works much better, but then the projectile doesn’t face the direction of movement until the “missile” code kicks in after the timer has elapsed.

If I enable that function, I’m right back where I started, where the projectile would suddenly turn clockwise before homing in on the target.

Is there a way to set the look direction other than this, and not affect the missile seeking code?

The only thing I can really think of to do is rewrite the seeker code so that it seeks out a target point that is up and to the right (which would be to the left if facing the other way, as long as the target was a child object…) of the enemy that instantiated it until the timer has elapsed, and then give it a new target. Hmm, that might work…

Forgive me, I’m still learning… A lot all the time. I’ll first, call attention to,

“I would just like to combine the two so that I can have an object keep it’s velocity while slowly rotating towards the target, if that makes sense.”. - OP,

& also make the “notice” I have not “poured” over your code, but I did speedily glance at everything in hopes of sheading light on my own problems related to quaternions… That part, not important to what brings me to here:… *chase being cut *… Slerp. I think you want to be Slerping not altering rotations over time and, well space. At least not so,… forcefully? Atan2 as I under stand it will “net” you an arc like , sure, but your jet li in a line fighting jet li in a circle. Marijuana is a hell of a drug, so I don’t trust this next bit explicitly,… Yeah that didn’t work out this is better :

yeah Slerp it’