Newbie: space controls - too realistic

Hi all,

Apologies if this is all handled in some Unity tutorial, I’ve watched a few and haven’t seen it yet.

I’ve got this simple controller script that demonstrates two ways of moving a spaceship forward. The problem is that the AddForce() method is too realistic, in that if the player rotates the ship 180’, they will still be travelling backwards until their velocity equalises. If I just set it to zero when the player moves, that isn’t going to be good either, as I don’t know the final outcome is that they want to face the other way.
Thanks Mr Newton.

Do most space games tend to ignore that physics lesson and turn ships on a knife edge, regardless of how much force might be required in the real world?

I’ve got another method which is naughty and directly manipulates the position, I get my desired playability, but now the colliders only half work (they collide, stop me or move around me a little, but generally don’t react the same way as when AddForce is used).

How do I change this code so that I can get the rigid body collision to work as expected, and be able to turn around quickly, without looking like I’ve stopped/started? And setting velocity=0 is probably going to be a harsh result and mess with the results of a natural rigid body collision.

public class PlayerInput : MonoBehaviour {

    // rotation of both pitch and roll
    public float finalRotationSpeed = 3.0f;
    public float rotationAcceleration = 1.00f;
    public float fThrust = 1.0f; // thrust per second? not quite.
    Rigidbody rigidBody;

    public struct Axis
    {
        public string axisName;
        public float fRotation;
    };

    private Axis rollAxis;
    private Axis pitchAxis;

    // Use this for initialization
    void Start () {
        rollAxis.axisName = "Horizontal";
        pitchAxis.axisName = "Vertical";
        rigidBody = GetComponent<Rigidbody> ();
    }

    private void UpdateAxis( ref Axis axis )
    {
        // get the input (smoothed & fractional between -1 and 1)
        float fInput = Input.GetAxis (axis.axisName);

        // move the current rotation value towards the strength of the joystick position
        // and the terminal rotation speed, doing so with an acceleration factor.
        // this allows us to have a feeling of inertia, as we don't immediately stop, nor do we
        // immediately switch direction if the user banks hard in the opposite direction.
        axis.fRotation = Mathf.Lerp (axis.fRotation, fInput*finalRotationSpeed, rotationAcceleration * Time.fixedDeltaTime);
    }

    private void FixedUpdate()
    {
        UpdateAxis (ref rollAxis);
        UpdateAxis (ref pitchAxis);

        transform.Rotate (new Vector3 (pitchAxis.fRotation, 0, -rollAxis.fRotation));

        // this makes it feel like real space - you have to have an equal and opposite reaction
        // but awful to control!
        // bonus: using AddForce, we can use velocity to determine speed and asteroids bounce off nicely
        //if (rigidBody.velocity.magnitude < 3.0f ) {
        //    rigidBody.AddForce (transform.forward * fThrust * Time.fixedDeltaTime, ForceMode.Acceleration);
        //}

        // using this feels like an aircraft rather than shuttle
        // and it's a lot nicer, and collisions kinda work, but not as natural as above.
        // Also, here we have to control the velocity at all times.
        transform.Translate(transform.forward * 0.5f * Time.fixedDeltaTime, Space.World);
    }
}

You should keep working with forces alone. Overall, physic engines doesn’t like when you set position of bodies this way.

You are right, few games perform real physics in space, and if it does some players will not like it.

Anyway, it’s really just a case of finding the forces to achieve your scenario. By what you described, it’s much like an airplane flight(?), where nose should stay aligned with movement vector (or be obliterated by air drag). So, you could set some torque force inverse proportional to dot product (direction . velocity normal), along with some friction to set maximum velocity. Then you can tweak with these forces to find balance between reality x fun

1 Like

Space Engineers handles this pretty well. By default, the ships function like they should in space - if you start moving in a direction, turning your ship with the thrusters off will not affect the velocity in the slightest.

To compensate, the game allows you to turn on something called “interia dampeners”, which automatically fires thrusters to make you stop moving when you’re not holding down the movement keys.

As you have discovered, the physics system does not handle you setting the positions directly very well. That’s to be expected - you’re essentially teleporting the object instead of moving it, and a physics simulation that’s trying to simulate real-world physics doesn’t handle teleportation very well.

1 Like

You can always use Rigidbody.MovePosition. This lets you manually move things while still respecting the physics engine.

As to most games, space physics tends to depend on the focus of the game. Space simulation games have accurate physics. Space combat games sacrifice physics for fun.

1 Like

Another option, if you always want to travel in the direction you’re facing, is to redirect the velocity.

In your FixedUpdate, after adding all the forces, do this

rigidbody.velocity = transform.forward * rigidbody.velocity.magnitude;
2 Likes

I will try and give all your suggestions a go - but unfortunately my MacBook Pro died yesterday, and is going in for surgery this weekend. Will report back hopefully with high-fives :wink:

Thanks for the suggestion - I tried it and discovered that MovePosition does not result in any velocity.magnitude, thus collisions won’t act properly, which is what I’m after. I’m having some success now with “force only” approaches.

This does work (not sure why adding force and torque isn’t enough), but I may have another problem that my magnitude isn’t being killed when I hit objects, so my model stops dead, but manages to get upto full speed too quickly.
I think I need to go read some basic vector maths books!

+1 for the mention of inertia dampeners, something fun for me to google :slight_smile:

https://en.wikipedia.org/wiki/Inertia

Oh dear - it appears many of my woes are because I used the standard assets as a starting point - there’s control scripts here there and everywhere that affect the physics and position.

When I debugged the rigid body.transform.forward it was going MAD, ramping up/down between 0 and 1 on each axis. I deleted virtually everything from the scene and things are behaving a lot better.

Apologies, and lessons for other noobs - start with a clean scene and some cubes to get your controller physics nailed!

Ok, so I’ve got something that I can work with.

Check it out & here’s the code so far.
This will take a little while to load - press G to start moving.
https://dl.dropboxusercontent.com/u/79636404/spacecontrol/index.html

// press 1 or 2 to view different cameras
    Rigidbody rigidBody;
    public List<GameObject> cameras = new List<GameObject>();
    private int currentCamera = 0;

    public UnityEngine.UI.Text speed;
    public UnityEngine.UI.Text targetSpeed;
    public UnityEngine.UI.Text debugText;

    void Start()
    {
        rigidBody = GetComponent<Rigidbody>();

        // theres a weird bug with box colliders affecting max/force - so re-set it
        // http://forum.unity3d.com/threads/adding-a-collider-stops-rotation.42640/
        rigidBody.inertiaTensor = new Vector3(1, 1, 1);
    }
    private float fTargetSpeed = 0.0f;

    private void FixedUpdate()
    {
        rigidBody.AddRelativeTorque(new Vector3(Input.GetAxis("Vertical"), 0, -Input.GetAxis("Horizontal")*0.5f));
       

        // thanks to Edy for this gem
        float fForwardSpeed = Vector3.Dot(transform.forward, rigidBody.velocity);

       
        if (fForwardSpeed > fTargetSpeed)
            rigidBody.AddForce(transform.forward * -5.0f, ForceMode.Force);
        else
            rigidBody.AddForce(transform.forward * 5.0f, ForceMode.Force);
      

        // ensure we apply forces to make our velocity edge towards our normalized forward vector
        Vector3 vMovement = rigidBody.velocity.normalized;
        Vector3 vFacing = transform.forward;

        Vector3 diff = vFacing-vMovement;
        // apply a gentle push in the opposing direction.
        rigidBody.AddForce(diff * 4.0f);

        speed.text = ((int)fForwardSpeed).ToString()+"m/s";
        targetSpeed.text = fTargetSpeed.ToString();
        debugText.text = "forward: " + vFacing.ToString() + " vel:" + vMovement.ToString() + " gap:" + diff.ToString();
       
    }

    private void Update()
    {
        // G for go go go
        if (Input.GetKeyUp(KeyCode.G))
            fTargetSpeed += 5.0f;
        if (Input.GetKeyUp(KeyCode.B))
            fTargetSpeed -= 5.0f;

        if (Input.GetKeyUp(KeyCode.Alpha1))
        {
            cameras[currentCamera].SetActive(false);
            currentCamera++;
            currentCamera = currentCamera % cameras.Count;

            cameras[currentCamera].SetActive(true);
        }
    }
1 Like