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.