0
\$\begingroup\$

Demo GIF

I have a smooth 6DoF controller from following a tutorial (this is the tutorial) and tweaked it to be exactly what I want with a few exceptions.

I found myself wanting the ability to lock the horizon line by pressing a toggle button so while the player is freely rotating around the horizon stays level until the lock is disabled.

I'd also like to have a button that smoothly rotates the horizon to be level with the Ship object/camera but keep the yaw and pitch the same. I'm not sure how it would work but I want this button to simply smoothly upright the Ship when the button is pressed once and have the horizon lock be a separate optional feature.

I have no idea where to even start on the math/logic for these two features but I have the buttons and functions mapped already hoping I might just gleam someone's smarter code and just having it magically work eventually. I have a feeling there's a better way of going about this than guessing or trial and error haha.

The entire script for the Ship object is self-contained and I'll put it here along with some images and GIFs of various hopefully useful details.

As an aside, I'm sure there's a simple solution I'm overlooking but the ship does roll when colliding with other objects (spherical collision mesh). Is there a way to either disable it or have less friction?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent (typeof(Rigidbody))]

public class Ship : MonoBehaviour
{
    // Parameters
    [Header("=== Ship Movement Settings ===")]
    [SerializeField]
    private float pitchTorque = 500f;
    [SerializeField]
    private float yawTorque = 1000f;
    [SerializeField]
    private float rollTorque = 1000f;
    [SerializeField]
    private float thrust = 100f;
    [SerializeField]
    private float strafeThrust = 50f;
    [SerializeField]
    private float verticalThrust = 50f;
    public bool horizonLock = false;

    [Header("=== Boost Settings ===")]
    [SerializeField]
    private float boostMultiplier = 5f;

    [Header("=== Thrust Reduction Settings ===")]
    [SerializeField]
    private float drag = 1f;
    [SerializeField]
    private float angularDrag = 0.5f;
    [SerializeField, Range(0.001f, 0.999f)]
    private float thrustGlideReduction = 0.9f;
    [SerializeField, Range(0.001f, 0.999f)]
    private float strafeGlideReduction = 0.1f;
    [SerializeField, Range(0.001f, 0.999f)]
    private float verticalGlideReduction = 0.1f;


    // Variables
    Rigidbody rb;
    float glide = 0f;
    float strafeGlide = 0f;
    float verticalGlide = 0f;
    bool boosting = false;
    bool altMove = false;
    bool altRoll = false;
    bool stop = false;
    bool autoUpright = false;

    // Input Values
    private float thrust1D;
    private float strafe1D;
    private float vertical1D;
    private float roll1D;
    private Vector2 pitchYaw;


    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        HandleMovement();
    }


    void HandleMovement()
    {
        /* The reason for all the altRoll and altMove statements is to have alternate momentary
         * way of moving and rolling the ship.
         * 
         * AltMove shifts horizontal and forward/backward movement inputs to vertical/horizontal movement.
         * It also shifts vertical movement inputs to forward/backward movement.
         * 
         * AltRoll shifts the yaw/pitch movement inputs to roll/pitch.
         * It also maps roll inputs to yaw for convenience.
         */


        // Roll
        if(!altRoll) rb.AddRelativeTorque(Vector3.back * roll1D * rollTorque * Time.deltaTime);
        else rb.AddRelativeTorque(Vector3.back * Mathf.Clamp(pitchYaw.x, -1f, 1f) * rollTorque * Time.deltaTime);

        // Pitch
        rb.AddRelativeTorque(Vector3.right * Mathf.Clamp(-pitchYaw.y, -1f, 1f) * pitchTorque * Time.deltaTime);

        // Yaw
        if (!altRoll) rb.AddRelativeTorque(Vector3.up * Mathf.Clamp(pitchYaw.x, -1f, 1f) * yawTorque * Time.deltaTime);
        else rb.AddRelativeTorque(Vector3.up * roll1D * yawTorque * Time.deltaTime);


        // Thrust
        if (!altMove && (thrust1D > 0.1f || thrust1D < -0.1f))
        {
            float currentThrust;
            if (boosting) currentThrust = thrust * boostMultiplier;
            else currentThrust = thrust;

            rb.AddRelativeForce(Vector3.forward * thrust1D * currentThrust * Time.deltaTime);
            glide = thrust;
        }
        else if(altMove && (vertical1D > 0.1f || vertical1D < -0.1f))
        {
            float currentThrust;
            if (boosting) currentThrust = thrust * boostMultiplier;
            else currentThrust = thrust;

            rb.AddRelativeForce(Vector3.forward * vertical1D * currentThrust * Time.deltaTime);
            glide = thrust;
        }
        else
        {
            rb.AddRelativeForce(Vector3.forward * glide * Time.deltaTime);
            glide *= thrustGlideReduction;
        }


        // Strafe
        if (strafe1D > 0.1f || strafe1D < -0.1f)
        {
            float currentThrust;
            if (boosting) currentThrust = thrust * boostMultiplier;
            else currentThrust = thrust;

            rb.AddRelativeForce(Vector3.right * strafe1D * currentThrust * Time.deltaTime);
            strafeGlide = strafe1D * strafeThrust;
        }
        else
        {
            rb.AddRelativeForce(Vector3.right * strafeGlide * Time.deltaTime);
            strafeGlide *= strafeGlideReduction;
        }


        // Vertical
        if (!altMove && (vertical1D > 0.1f || vertical1D < -0.1f))
        {
            float currentThrust;
            if (boosting) currentThrust = thrust * boostMultiplier;
            else currentThrust = thrust;

            rb.AddRelativeForce(Vector3.up * vertical1D * currentThrust * Time.deltaTime);
            verticalGlide = vertical1D * verticalThrust;
        }
        else if (altMove && (thrust1D > 0.1f || thrust1D < -0.1f))
        {
            float currentThrust;
            if (boosting) currentThrust = thrust * boostMultiplier;
            else currentThrust = thrust;

            rb.AddRelativeForce(Vector3.up * thrust1D * currentThrust * Time.deltaTime);
            verticalGlide = thrust1D * verticalThrust;
        }
        else
        {
            rb.AddRelativeForce(Vector3.up * verticalGlide * Time.deltaTime);
            verticalGlide *= verticalGlideReduction;
        }

        // What I call the "stop on a dime" button. Momentarily increases drag to freeze the ship in place.
        if (stop)
        {
            rb.drag = 50;
            rb.angularDrag = 50;
        }
        else
        {
            rb.drag = drag;
            rb.angularDrag = angularDrag;
        }
    }



    // Input handlers
    #region Input Methods

    public void OnThrust(InputAction.CallbackContext context)
    {
        thrust1D = context.ReadValue<float>();
    }

    public void OnStrafe(InputAction.CallbackContext context)
    {
        strafe1D = context.ReadValue<float>();
    }

    public void OnVertical(InputAction.CallbackContext context)
    {
        vertical1D = context.ReadValue<float>();
    }

    public void OnRoll(InputAction.CallbackContext context)
    {
        roll1D = context.ReadValue<float>();
    }

    public void OnPitchYaw(InputAction.CallbackContext context)
    {
        pitchYaw = context.ReadValue<Vector2>();
    }

    public void OnBoost(InputAction.CallbackContext context)
    {
        boosting = context.performed;
    }

    public void OnAltMove(InputAction.CallbackContext context)
    {
        altMove = context.performed;
    }

    public void OnAltRoll(InputAction.CallbackContext context)
    {
        altRoll = context.performed;
    }

    public void OnStop(InputAction.CallbackContext context)
    {
        stop = context.performed;
    }

    public void OnHorizonLock(InputAction.CallbackContext context)
    {
        horizonLock = !horizonLock;
    }

    public void OnAutoUpright(InputAction.CallbackContext context)
    {
        autoUpright = context.performed;
    }

    #endregion
}



Inspector Window for the Ship object.

Inspector window for ship

\$\endgroup\$
8
  • \$\begingroup\$ You may be interested in past Q&A about implementing such auto-righting features \$\endgroup\$
    – DMGregory
    Nov 20, 2022 at 21:22
  • \$\begingroup\$ I was hoping you'd still be around haha. I was looking at that exact post but was having a really hard time wrapping my head around it since I don't fully understand the code I've written so far to begin with. So I decided to write my own post specific to my project. \$\endgroup\$
    – Mazz1224
    Nov 20, 2022 at 21:39
  • \$\begingroup\$ I'm a moderator here - you can click on any user's profile to see how recently they've visited the site. I'd be happy to help you with my older post, but I'd need to know where you're running into difficulty, specifically. \$\endgroup\$
    – DMGregory
    Nov 20, 2022 at 21:52
  • \$\begingroup\$ Thank you for taking the time to help me, I appreciate it! I'm having trouble applying the logic and math from your older post(s) to the code that I've already written. I also don't fully understand how Unity's rotation system works and which built-in variables and such do what since I don't have a lot of experience working in this software. Specifically I don't understand how I'd get a plane of rotation to be separate from the ship and somehow lock the rotation around it, not to mention being able to smoothly upright the ship to that plane any time a button is pressed. \$\endgroup\$
    – Mazz1224
    Nov 20, 2022 at 22:05
  • \$\begingroup\$ I really just don't know the most efficient way of going about this. I'm not sure if I have to use another empty to track the separate plane of rotation or if it can all be contained in a single empty/object/script. \$\endgroup\$
    – Mazz1224
    Nov 20, 2022 at 22:12

0

You must log in to answer this question.