Help With Rotating On Z-Axis

I’m trying to create a simple rotation on the z-axis for my 2D game.
Quaternions and rotations are my weak points in Unity, so I know I may be asking a really simple question, I just cannot for the life of me handle this topic.

Anyway, I have an enemy that looks at the character by switching their transform.right. However, this caused the gun (the thing that I want to rotate and a child object of the enemy) to rotate weirdly when I tried to manipulate the transform.right component of the gun to look at the player.

After a bit of research, I came up with this:

        if (transform.position.x > player.position.x)
        {
            transform.right = Vector2.left;

        }
        else if (transform.position.x < player.position.x)
        {
            transform.right = Vector2.right;
        }

        Quaternion rotation = Quaternion.Lerp(gun.localRotation, Quaternion.LookRotation(player.position - gun.position, gun.TransformDirection(Vector3.up)), accuracy * Time.deltaTime);
        gun.localRotation = new Quaternion(0, 0, rotation.z, rotation.w);

This code works when I’m on the right side of the enemy. When I’m on the left side, the gun rotates on the z-axis with negative numbers. For example, if the player is on the right side, the rotation in the inspector would read 6. If the player was on the left side, it would read -6.

I decided to use Mathf.Abs(rotation.z) which I thought would cause the rotations to be positive, but all this did was make the gun stop rotating altogether when the player is on the left side.

Could someone please help me find a solution?
Again, I know I may be asking a simple question that may be common sense, but this is my weakpoint :slight_smile:

i think you may have an easier time setting transform.forward or something like that as opposed to using quaternions. those are unit vectors, so it’s way easier to wrap your head around them as well as find rotation by simply doing

transform.forward = Vector3.Normalize(objectAimingAt.transform.position - gun.transform.position);

this may differ slightly in 2d, but it should be straightforward with this method

Adding to the above, don’t touch the w, x, y or z components of a quaternion unless you have a math degree. They do not do what you expect them to do.

More context here helps. Is this a side-on 2d game? A top down 2d game? Very often you don’t need to check if something is left or right of an object, you can just work out the direction and go from there.

1 Like

Haha, yeah, I do not have a math degree. Its a side on 2D game.

I feel like the gun being a child of the enemy (which has their transform.right changed based on the player’s position) has something to do with it. Problems arise when the enemy changes direction.

In 2D you don’t need to use quaternions. It’s convenient if you want to, but you don’t have to.

All you need is some basic trigonometry. When I say basic, I do mean that if you want to make legit video games, you have to know a bit of trigonometry. Like unit circle and what sine and cosine do in practical terms.

Aiming in 2D is super easy, you find the angle and rotate a thing by that angle.

transform.rotation = rotateBy(angleOf(enemyDirection));

where

Vector2 enemyDirection = (v2(enemy.transform.position) - v2(player.transform.position)).normalized;
// this is in radians
float angleOf(Vector2 v) => System.MathF.Atan2(v.y, v.x);
Quaternion rotateBy(float rad) => Quaternion.AngleAxis(rad, Vector3.forward)

That’s it!

Edit:
v2 and v3 are just conversion between Vector2 and Vector3
for example

static public Vector2 v2(Vector3 v) => new(v.x, v.y);
static public Vector2 v3(Vector2 v) => new(v.x, v.y, 0f);

For a top down game, you’d change this to XZ

static public Vector2 v2(Vector3 v) => new(v.x, v.z);
static public Vector2 v3(Vector2 v) => new(v.x, 0f, v.y);

Edit:
sry my brain farted, what I gave you initially was a direction rotor :smile:

When I say “you don’t need to use quaternions” you obviously do need to use them to feed the actual API like transform.rotation. What I meant is apart from that.

I’m running into the same issue that I’ve been struggling with.
Here’s how it looks on the right side:
9922443--1435215--Screenshot 2024-07-03 202325.png
And here’s the left side (the issue):
9922443--1435218--upload_2024-7-3_20-25-0.png

I really need to figure out a way to rotate the gun that keeps in mind that the enemy is rotating 180 degrees on the y-axis when switching to look at the player.

Are you rotating the player to flip it?

Usually it works better to flip the X scale instead.

Ok, I’ll try that.

Ok, I got something that “works”:

 if (transform.position.x > player.position.x)
{
     transform.localScale = new Vector3(-1,1,1);
     gun.localScale = new Vector3(-1, -1, -1);
}
else if (transform.position.x < player.position.x)
{
     transform.localScale = new Vector3(1, 1, 1);
     gun.localScale = new Vector3(1, 1, 1);
}

gun.right = player.position - gun.position;

I really don’t like this though, because it messes up the upward transform:
9922467--1435227--upload_2024-7-3_20-54-42.png

Is the gun a child of the player? You only need to invert the x-component of the player’s scale, and everything as a child will be flipped as well.

When I didn’t do that, I ran into the same problem as before:
For example, this code:

       if (transform.position.x > player.position.x)
       {
           transform.localScale = new Vector3(-1,1,1);
       }
       else if (transform.position.x < player.position.x)
       {
           transform.localScale = new Vector3(1, 1, 1);
       }

       gun.right = Vector2.Lerp(gun.right, player.position - gun.position, accuracy * Time.deltaTime);

resulted in this when the enemy flips
9922479--1435230--Screenshot 2024-07-03 210107.png

Eventually I got this:

        if (transform.position.x > player.position.x)
        {
            transform.localScale = new Vector3(-1,1,1);
            gun.localScale = new Vector3(-1, -1, -1);

        }
        else if (transform.position.x < player.position.x)
        {
            transform.localScale = new Vector3(1, 1,1);
            gun.localScale = new Vector3(1, 1, 1);
        }

        if(Vector3.Distance(gun.right, player.position-gun.position) > 90)
        {
            gun.right = Vector3.Lerp(gun.right, player.position - gun.position, accuracy * Time.deltaTime);
        }
        else
        {
            gun.right = player.position - gun.position;
        }

It works, but I do have a sinking feeling that I’m not doing it the best way.

And this method doesn’t allow for lerping.

Ok, I landed on this:

        if (transform.position.x > player.position.x)
        {
            transform.localScale = new Vector3(-1,1,1);
            gun.localScale = new Vector3(-1, -1, -1);

        }
        else if (transform.position.x < player.position.x)
        {
            transform.localScale = new Vector3(1, 1,1);
            gun.localScale = new Vector3(1, 1, 1);
        }

        Vector3 aimDiretion = Vector2.Lerp(gun.right, (player.position - gun.position).normalized, accuracy * Time.deltaTime);
        float angle = Mathf.Atan2(aimDiretion.y, aimDiretion.x) * Mathf.Rad2Deg;
        gun.eulerAngles = new Vector3(0,0, angle);

I really need help with the lerping. When the enemy switches directions, the gun does a full circle around. It only does this when I lerp.

I would really appreciate any suggestions. I’m basically pulling my hair out with this one.

Lerp isn’t always the right tool. You might want to look at Quaternion.RotateTowards instead.

Thinking back, when I did something like this for my player character’s robot arms, I would produce an angular rotation based on the angle between a direction based on the player’s facing, and the direction to point the arms.

Not at home to look at the code, but might’ve looked like this:

Vector3 direction = (cursorPosition - transform.position).normalized;
Vector3 facing = GetPlayerFacing();

float angle = Vector3.SignedAngle(facing, direction, Vector3.forward);
Quaternion rotation = Quaternion.identity * Quaternion.AngleAxis(angle, Vector3.forward);

transform.rotation = rotation;

Will have to dig out the actual code later. In this case you want to work out a direction based on the NPC’s facing, either Vector3.right or Vector3.left.

No touchy!

Okay found the code that aim’s my robot player’s arms:

private void UpdateArmAim()
{
    bool isArmInput = this.ArmInputIsPressed;

    Quaternion targetRotation;

    if (isArmInput == false)
    {
        targetRotation = Quaternion.identity;
    }
    else
    {
        Vector3 aimDirection = Reticule.GetDirectionToReticule(transform.position);
        Vector3 facingDirection = _playerRoot.FacingDirection;
        float angle = Vector3.SignedAngle(facingDirection, aimDirection, Vector3.back);
        targetRotation = Quaternion.identity * Quaternion.AngleAxis(angle, Vector3.back);
    }

    float detla = _armPartBehaviour.TurningSpeed * Time.deltaTime;

    Quaternion currentRotation = transform.rotation;
    Quaternion rotateTowards = Quaternion.RotateTowards(currentRotation, targetRotation, detla);
    transform.rotation = rotateTowards;
}

So basically Vector.back as opposed to Vector3.forward, but I don’t think that’d make a difference?

Recording of the result: https://i.gyazo.com/2f81f97c647cc2d2597ce64aee9b1963.mp4

1 Like

“I feel like the gun being a child of the enemy (which has their transform.right changed based on the player’s position) has something to do with it. Problems arise when the enemy changes direction”

okay! I’ll try this when I have the chance! Thank you so much! :smile: