TransWikia.com

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

Game Development Asked by LacklusterToady on November 2, 2021

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.
            v_rigidBody2D.AddRelativeForce(v_movement * v_speed);
 
            //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
            v_rigidBody2D.AddRelativeForce(v_movement * v_speed);
        }
    }
}

2 Answers

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

Rotating gradually in response to input, instead of snapping

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.
            v_rigidBody2D.AddRelativeForce(v_movement * v_speed);

            //Update our target rotation to move towards.
            v_targetRotation = Quaternion.Euler(0f, 0f, v_lookAngle-90f);
            
            //gradually rotates the ship towards the mouse pointer
            transform.rotation = Quaternion.RotateTowards(transform.rotation,
                                                          v_targetRotation, 
                                                          v_rotationSpeed * Time.fixedDeltaTime);
        }

Answered by LacklusterToady on November 2, 2021

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);

Answered by Kevin on November 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP