# Gradual strafe rotation with focus on mouse (circle-strafe)

I'm pretty new to Unity and C# programming, and am trying to get a player-controlled object to do a controlled rotation around the mouse pointer. The functional purpose is to be able to circle-strafe around that pointer and remain facing the pointer, but if the player object's rotation speed isn't fast enough, they can't track it. As a space shooter, I intend to have large and small ships that turn at different speeds, and the intent is to make it harder for larger ships to track smaller objects.

My code right now does everything for movement except control rotation speed. I'm really struggling to get that single element into place. It switches from free rotation smoothly to circle-strafe rotation around the mouse cleanly... it's just that the ship then rotates as fast as I whirl the mouse around it.

I've tried a number of different techniques recommended in searches, but none of them actually work for my scenario when implemented. I'm almost certain I need "Time.deltaTime * v_rotationSpeed" blended into this somewhere, but can't get it working.

I'm hoping someone can help, or at least guide me in the right direction.

using System.Collections;

public class Space2DStrafeMove : MonoBehaviour

{
//movement variables
public float v_speed = 3;
public float v_rotationSpeed = 0.25f;

//private calculation variables
private Vector3 v_cursorLocation;
private float v_lookAngle;

//object variables
private Rigidbody2D v_rigidBody2D;

// Use this for initialization
void Start()
{
//store a reference to the attached Rigidbody2D object
v_rigidBody2D = GetComponent<Rigidbody2D> ();

}

//FixedUpdate is called at a fixed interval and is independent of frame rate. Put physics code here.
void FixedUpdate()
{
//Store the current horizontal input in the float moveHorizontal.
float v_moveHorizontal = Input.GetAxis ("Horizontal");

//Store the current vertical input in the float moveVertical.
float v_moveVertical = Input.GetAxis ("Vertical");

//strafe toggle, ship remains oriented towards mouse cursor
if (Input.GetKey(KeyCode.LeftShift))
{
//Establish the mouse position in the camera relative to the position of the ship
v_cursorLocation = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;

//Create an angle that the ship will need to move towards in order to look at the mouse cursor
v_lookAngle = Mathf.Atan2(v_cursorLocation.y, v_cursorLocation.x) * Mathf.Rad2Deg;

//Use the two store floats to create a new Vector2 variable movement.
Vector2 v_movement = new Vector2 (v_moveHorizontal, v_moveVertical);

//Call the AddForce function of our Rigidbody2D rb2d supplying movement multiplied by speed to move our player.

//Rotate the ship towards the established angle, -90f to account for ship alignment, over time
//Rotates instantly, not desirable
transform.rotation = Quaternion.Euler(0f, 0f, v_lookAngle - 90f);

//turns the ship as fast as I move the cursor, and in the right direction
//transform.rotation = Quaternion.AngleAxis(v_lookAngle-90, Vector3.forward);

//instantly snaps the ship "up"
//transform.rotation = Quaternion.Euler(0f, 0f, v_lookAngle - 90f * Time.fixedDeltaTime) ;

//instantly snaps the ship "up"
//transform.rotation = Quaternion.Euler(0f, v_lookAngle - 90f * v_rotationSpeed * Time.deltaTime, 0f);

//undesirable result
//transform.eulerAngles = Vector3(0, v_lookAngle, 0);

//undesirable result
//transform.rotation = Quaternion.Slerp(v_lookAngle, v_lookDirection.y, Time.time - startTime);

//undesirable result
//transform.rotation = Quaternion.RotateTowards(transform.rotation, v_lookDirection, Time.deltaTime * 10f);

//smooth rotation to the Z axis (flips the ship over)
//transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(v_lookDirection - transform.position), v_rotationSpeed * Time.deltaTime);

//undesirable result
//transform.rotation = Quaternion.AngleAxis(v_lookAngle, Vector3.forward);

//ship spins smoothly over onto it's back
//transform.rotation = Quaternion.Slerp(this.transform.rotation, v_lookAngle, Time.deltaTime * v_rotationSpeed);

}

//this creates smooth up and down, rotate left and right, fly in direction the transform is pointing
if (!Input.GetKey(KeyCode.LeftShift))
{
//Use the two store floats to create a new Vector2 variable movement.
Vector2 v_movement = new Vector2(0, v_moveVertical);

//rotate the transform on the horizontal axis
transform.Rotate(0, 0, -v_moveHorizontal);

//apply force relative to the existing heading of the ship
}
}
}

• "Rigidbodies can receive forces and torque, but Transforms cannot. Transforms can be translated and rotated, but this is not the same as using physics. You’ll notice the distinct difference when you try it for yourself. Adding forces/torque to the Rigidbody will actually change the object’s position and rotation of the Transform component . This is why you should only be using one or the other. Changing the Transform while using physics could cause problems with collisions and other calculations." from the docs, i.e. undefined behaviour. – Engineer Jul 19 '20 at 8:25
• I would attempt to get this working first in a minimum verifiable example WITHOUT RigidBody (just Transform) so you know what you're about, then attempt to replicate the same setup using RigidBody - which could be somewhat more difficult given the need to use finely-tuned rotational forces. – Engineer Jul 19 '20 at 8:29

EDIT: D'oh! You've apparently already tried this in your commented-out code that I ignored. Hmm, re-thinking.

You probably want to Lerp or Slerp:

var targetRotation = Quaternion.Euler(0f, 0f, v_lookAngle - 90f);
var lerpRate = .15f;
var newRotation = Quaternion.Lerp(transform.rotation, targetRotation, lerpRate);
transform.rotation = newRotation;


Lerp and Slerp smoothly interpolate from one value to another. The third argument (labelled t), is a normalized value (0 - 1) representing the percentage to move from the start value to the end value. Note that adjusting the lerp rate even a small amount may drastically affect the animation. It's usually good to start with a very low value and play around with it from there.

Idea #2:

Maybe something like this?

//Transform cursor position to world coordinates
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = 10;
mousePosition = Camera.main.ScreenPointToWorldPoint(mousePosition);
mousePosition.y = transform.y;

//Rotate towards the cursor
float lerpRate = .15f;
Quaternion targetRotation = Quaternion.LookRotation(mousePosition - transform.position, Vector3.up);
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, lerpRate);

• I tried both of your methods, but they didn't quite hit the mark. The first one flipped the ship 180 deg and rotates x/y axis, not z. The second one rotates the ship along the z axis, but drifts to the left (v_lookAngle -90) or right (v_lookAngle) and gets slower as it approaches the angle. – LacklusterToady Jul 18 '20 at 0:46

This thread gave me the line I needed. It just happened to pop up a few hours after my post and grabbed my eye.

The answer there was to put the rotation inside a Coroutine. I tried that, but while it did successfully rotate my ship on the correct axis after a little tinkering, it didn't care how fast it rotated.

I pulled the rotation line out of the Coroutine and stuck it into FixedUpdate. It still didn't work. In the end I had to re-declare v_rotationSpeed inside the FixedUpdate for it to work. I'll have to figure that one out. Now that section from my first post looks like this:

if (Input.GetKey(KeyCode.LeftShift))
{
//set v_rotationSpeed; not sure why it's not using the public variable
float v_rotationSpeed = 60f;

//Establish the mouse position in the camera relative to the position of the ship
v_lookDirection = Camera.main.ScreenToWorldPoint(v_mousePosition) - transform.position;

//Create an angle that the ship will need to move towards in order to
//look at the mouse cursor
v_lookAngle = Mathf.Atan2(v_lookDirection.y, v_lookDirection.x) * Mathf.Rad2Deg;

//Use the two store floats to create a new Vector2 variable movement.
Vector2 v_movement = new Vector2 (v_moveHorizontal, v_moveVertical);

//Call the AddForce function of our Rigidbody2D supplying movement
//multiplied by speed to move our player.