2

I don't understand how rotation works in Bevy. I just want to rotate a sprite to follow my cursor position.

What is the piece of code to make it work?

I tried that, but it's not working: Bevy rotation in 2D

use bevy::math::{Quat, Vec2};

let pos = transform.translation.truncate(); // Player position
let target = event.position;                // Cursor position

let angle = (target - pos).angle_between(pos); // NaN
transform.rotation = Quat::from_rotation_z(angle);

The above makes an angle = NaN so it is not working.

let pos = transform.translation.truncate(); // Player position
let target = cursor_position; // Cursor position
            
let direction = target - pos;
let angle = direction.y.atan2(direction.x); // Calculate angle between direction and x-axis
transform.rotation = Quat::from_rotation_z(angle);

Here is the result:

Enter image description here

Here is the repository if you want to try: SpaceGameRust

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Theo Cerutti
  • 779
  • 1
  • 10
  • 33

2 Answers2

1

You're almost there! The main issue is that you're calculating the angle between the position vector and the direction vector, but you should calculate the angle between the direction vector and the x-axis:

use bevy::math::{Quat, Vec2, Vec3};

let pos = transform.translation.truncate(); // player position
let target = Vec2::new(event.position.x, event.position.y); // cursor position

let direction = target - pos;
let angle = direction.y.atan2(direction.x); // Calculate angle between direction and x-axis
transform.rotation = Quat::from_rotation_z(angle);

The atan2 function is used to get the angle between the direction vector and the x-axis, which is the correct angle for rotation

Kozydot
  • 515
  • 1
  • 6
  • Thanks for you answer. It seems it is working when I put hardcoded values in event_position but when I set the cursor_position it not working. I updated my answer and uploaded a gif to show what's going on – Theo Cerutti May 30 '23 at 07:40
1

What is needed is to rotate the front of the player towards the cursor. In order to do that the vector that describes the orientation of the front of the player is required, also.

angle = (target - pos).angle_between(pos) doesn't help because pos doesn't describe the orientation of the player. pos is only the position of the player.

Finding the angle of the vector target-pos with respect to the x-axis is useful but not enough. From that angle, the angle that the orientation of the player makes with respect to the x-axis, should be subtracted. This will give the required angle of rotation. The player should be rotated based on the resulting angle, about the z-axis that passes through the player.

To get a working solution, the following need to be done:

Make sure the coordinates of the target and the player are in the same reference frame.

atan2 returns a value in (-Pi, Pi]. It has to be transformed into a value in [0, 2*Pi). Make sure to do this when getting the angle to the target with respect to the x-axis.

Have a separate variable that keeps track of the object's orientation with respect to the x-axis. Subtract this angle from the angle of the target with respect to the x-axis.

See below for observations about whether the orientation of the player has to be updated each time the player is rotated.

use bevy::math::{Quat, Vec2};
use std::f32::consts::PI;

// Initialize player orientation at the very beginning.
// It is the angle with respect to X-axis.
// IMPORTANT NOTE: Check what value it is (0 degrees or 90 degrees etc)
// and set it here.
let player_orient = PI/2.0;

...

let pos = transform.translation.truncate(); // player position
let target = Vec2::new(cursor_position.x - window.width() / 2., cursor_position.y - window.height() / 2.);                // cursor position
let direction = target - pos; 

// obtain angle to target with respect to x-axis. 
let mut angle_to_target = direction.y.atan2(direction.x);
// represent angle_to_target in [0, 2*PI)
if angle_to_target < 0. {
  angle_to_target += 2.0*PI;
}

let angle_to_rotate = angle_to_target - player_orient;
transform.rotation = Quat::from_rotation_z(angle_to_rotate);

// Update the player's orientation.
// Always, represent it in the range [0, 2*PI).
player_orient = (player_orient + angle_to_rotate) % (2.0*PI);

The current system works only if player_orient is not updated, i.e., player_orient = (player_orient + angle_to_rotate) % (2.0*PI); is commented out. At the same time, angle_to_rotate has to be set as angle_to_target - player_orient. Otherwise, a 90 degree offset is observed. Probably, in each frame, before displaying the player, the system assumes that the player is at 90 degrees with respect to the x-axis and then the angle to rotate is computed and the player is rotated.

Hari
  • 1,561
  • 4
  • 17
  • 26
  • It's not working... I edited my post to add my github repo if you want to try... – Theo Cerutti May 30 '23 at 08:42
  • 1
    @TheoCerutti, I checked your code. The problem is with the cursor position. It is given in the coordinate system of the window, where origin is in the bottom left corner. However, the player is in a different coordinate system, whose origin is in the center of the window. If you represent the cursor in the coordinate system of the player, it should be fine. – Hari May 30 '23 at 09:53
  • Links like these are useful: https://www.mikechambers.com/blog/2022/10/29/understanding-the-2d-coordinate-system-in-bevy/ – Hari May 30 '23 at 09:53
  • 1
    Yes big thanks: I updated your code with let target = Vec2::new(cursor_position.x - window.width() / 2., cursor_position.y - window.height() / 2.); However if I don't comment the player_orient line it doesn't work well. – Theo Cerutti May 30 '23 at 10:05
  • "However if I don't comment the player_orient line it doesn't work well." One of the reasons for this to happen is because of the order of evaluation of the operations while taking the modulus. `(player_orient + angle_to_rotate) % 2.0*PI` leads to `((player_orient + angle_to_rotate) % 2.0) * PI`, while we want to `(player_orient + angle_to_rotate) % (2.0*PI)`. However, the output is still not ok. The code using `player_orient` in order to adjust the player needs to be checked. – Hari May 30 '23 at 11:29
  • 1
    Indeed, the expected behavior happens when not updating `player_orient` (i.e., commenting out `player_orient = (player_orient + angle_to_rotate) % (2.0*PI);`). At the same time, `angle_to_rotate` has to be`angle_to_target - player_orient`. Otherwise, there is a `90 degrees` (which is exactly the value in `player_orient` if it is not updated) off-set in the movement of the player. – Hari May 31 '23 at 08:57