Rotation as a visual effect only?

I would like to create a movement script for a spaceship. This spaceship should always move forwards. The player is able to use the WASD keys to control the horizontal (move to the left / right) and vertical movement (go up / down).

So for the movement part I created this script with the new input system, I think this is fine

public class PlayerMovement : MonoBehaviour
{
    private const float FORWARD_MOVEMENT_SPEED = 1;
    private const float HORIZONTAL_MOVEMENT_SPEED = 1;
    private const float VERTICAL_MOVEMENT_SPEED = 1;
   
    [SerializeField]
    private Rigidbody rigid;
   
    private Vector2 movementInput;
   
    private void FixedUpdate()
    {
        float horizontalMovement = movementInput.x * HORIZONTAL_MOVEMENT_SPEED;
        float verticalMovement = movementInput.y * VERTICAL_MOVEMENT_SPEED;
       
        rigid.velocity = new Vector3(horizontalMovement, verticalMovement, FORWARD_MOVEMENT_SPEED);
    }

    public void UpdateMovementInput(InputAction.CallbackContext inputActionCallbackContext)
    {
        movementInput = inputActionCallbackContext.ReadValue<Vector2>();
    }
}

I want to add a rotation based on the movement. When the player moves to the left or right I want to rotate the spaceship a little bit as shown in this sample

When the spaceship goes up I want to rotate the front up a little bit when it goes down I want the front to rotate down a little bit, as shown in this sample

  • I think the left/right rotation won’t affect the movement. But what about the up/down rotation?

  • For both ways I would have to limit the rotation so that the player won’t be able to turn around 180 degrees.

  • When there is no input or the input changes to the other direction the spaceship should go back to a zero rotation.

How can I achieve a rotation as described? I don’t know much about lerping but would this be a usecase for it?

An easy way to accomplish this is by having your movement logic on a parent GameObject, and the mesh that draws your ship as a child GameObject.
This way, you can rotate the child GameObject in any direction, and the ship mesh will follow the rotation, but the movement logic will not.

Lerping the rotation could work, but it’s probably easier to use Quaternion.RotateTowards instead.
You could define 4 rotation vectors for each direction the ship moves in and have the child GameObject transition to these vectors when receiving user input.
You’d also probably need a 5th rotation vector to return the GameObject back to its original rotation when not receiving user input as well.

1 Like

@Vryken thanks for your reply. The first part is a great idea!

I didn’t get the rotation part. Should I define one empty child GameObject per side? Let’s say

  • left

  • right

  • top

  • bottom

  • top left

  • bottom left

  • top right

  • bottom right

and when the input is 1 | -1 I would rotate to the bottom right GameObject?

Sorry, I was actually over-thinking the rotation part.
It should actually just be as simple as creating a Quaternion from your movement input Vector2 and updating the child GameObject’s rotation with it.
Something like this:

public GameObject childVisual;
public float rotationMultiplier; //How far the object rotates
public float rotationSpeed; //How fast the object rotates

public void UpdateRotation() {
   Quaternion currentRotation = childVisual.transform.rotation;
   Quaternion targetRotation = Quaternion.Euler(movementInput.x * rotationMultiplier, 0f, movementInput.y * rotationMultiplier);

   childVisual.transform.rotation = Quaternion.RotateTowards(currentRotation, targetRotation, Time.deltaTime * rotationSpeed);
}

@Vryken thank you very much! I changed your code a little bit and attached it to the model, this works :slight_smile:

public class PlayerRotation : MonoBehaviour
{
    private const float ROTATION_SPEED = 120;
    private const float HORIZONTAL_ROTATION_LIMIT = 45;
    private const float VERTICAL_ROTATION_LIMIT = 45;

    private Vector2 movementInput;
   
    private void Update()
    {
        float horizontalRotation = movementInput.x * HORIZONTAL_ROTATION_LIMIT;
        float verticalRotation = movementInput.y * VERTICAL_ROTATION_LIMIT;
        Quaternion targetRotation = Quaternion.Euler(-verticalRotation, 0, -horizontalRotation);

        transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, Time.deltaTime * ROTATION_SPEED);
    }

    public void UpdateMovementInput(InputAction.CallbackContext inputActionCallbackContext)
    {
        movementInput = inputActionCallbackContext.ReadValue<Vector2>();
    }
}

There is one problem left … The collider is attached to the parent because the rigidbody needs it for collision detection. But the collider itself obviously won’t rotate … Any ideas how to fix this constellation?

Attaching the collider to the child GameObject should work.
It shouldn’t break any collision checks, but if it does, make sure the child GameObject is on the same layer as the parent GameObject.

@Vryken Ah yes, it still works :slight_smile:

I tested it with

    private void OnCollisionEnter(Collision other)
    {
        Destroy(gameObject);
    }

on the parent container. I just thought that the Rigidbody on the parent needs a Collider on the same GameObject to check for collisions.

Thank you very much