Pushing objects with AddForce (relative mass issue)

Hello there,

I'm having a bad time figuring how to make this work.
I'm creating a "Ballance" like game, where you control a ball which "state" can be either paper, wood, stone.

In the original game, this state would affect :
1) its velocity (stone ball at full speed is faster than paper ball)
2) its manoeuvrability (paper ball is extremely manoeuvrable, meaning that its momentum is light and its easy to make it stop when at full speed, whereas the stone one would take much more time to stop)
3) and this is where I fail : its influence on other objects during collisions

What I did is that I set up different mass on the rigidbodies of my balls, which does a good work at making them more or less manoeuvrable. For the velocity, I set a start speed, and a maximum speed that can be reached.

Now about pushing objects, what I expect is that the lighter ball (paper) can't push a box of higher mass, whereas the stone can. But what happens is precisely the contrary ! I guess it's because of the velocity of the paper ball increasing faster (acceleration ?), but it seems strange to me since if we take the relative mass of the two objects, that result shouldn't happen.

Does somebody have an idea ? What do I do wrong ?

Here is my code :

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class Ball : MonoBehaviour
{

    public float speed, max_velocity = 8f, brake_force;
    [SerializeField]
    private Rigidbody rb;
    private Renderer rd;
    [SerializeField]
    private Camera cam;
    public Transform cameraJig;
    private float rot = 0;
    [SerializeField]
    private float sensivity = 100;
    [SerializeField]
    private string type = "wood";
    public Text veltext;

    void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;
        rb = GetComponent<Rigidbody>();
        rd = GetComponent<Renderer>();
        Transform("wood");
    }

    private void Update()
    {
        if (Input.GetKey(KeyCode.I)) {
            Transform("wood");
        }
        else if (Input.GetKey(KeyCode.O)) {
            Transform("stone");
        }
        else if (Input.GetKey(KeyCode.P))
        {
            Transform("paper");
        }
    }

    void Transform(string new_type)
    {
        type = new_type;
        rd.material = Resources.Load<Material>("Materials/" + type);
        if (type == "wood")
        {
            rb.mass = 0.5f;
            speed = 4;
            max_velocity = 6;
            brake_force = 5;
        }
        else if (type == "stone")
        {
            rb.mass = 1;
            speed = 5;
            max_velocity = 8;
            brake_force = 1.1f;
        }
        else if (type == "paper")
        {
            rb.mass = 0.1f;
            speed = 3;
            max_velocity = 5;
            brake_force = 10;
        }
    }

    void FixedUpdate()
    {

        // Rotate the camera object around the x-axis
        //rot = Input.GetAxisRaw("Mouse X");
        //cam.transform.RotateAround(cameraJig.position, new Vector3(0, rot, 0), sensivity * Time.fixedDeltaTime);

        // Move the ball
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");

        //Decelerate
        if (moveHorizontal == 0 && moveVertical == 0)
        {
            //Stop
            if (Mathf.Abs(rb.velocity.x) < 1 && Mathf.Abs(rb.velocity.z) < 1)
            {
                rb.velocity = new Vector3(0, rb.velocity.y, 0);
            }
            else
            {
                //Auto brake
                if (rb.velocity.x > 1)
                    moveHorizontal = -1f;
                else if (rb.velocity.x < -1)
                    moveHorizontal = 1f;
                if (rb.velocity.z > 1)
                    moveVertical = -1f;
                else if (rb.velocity.z < -1)
                    moveVertical = 1f;

                Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
                movement = movement * Time.fixedDeltaTime * 50;
                rb.AddForce(movement);
            }
        }
        //Accelerate
        else if (rb.velocity.magnitude < max_velocity)
        {
            //Manual brake
            if (Mathf.Sign(moveHorizontal) != Mathf.Sign(rb.velocity.x))
            {
                moveHorizontal *= brake_force;
            }
            if (Mathf.Sign(moveVertical) != Mathf.Sign(rb.velocity.z))
            {
                moveVertical *= brake_force;
            }
            Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
            //movement = movement.normalized * speed;
            movement = movement * speed * Time.fixedDeltaTime * 50;
            rb.AddForce(movement);
        }


        veltext.text = "Type : " + type +
            "\nMass : " + rb.mass +
            "\nSpeed : " + Mathf.Floor(rb.velocity.magnitude) + "/" + max_velocity +
            "\nVelocity : " + rb.velocity +
            "\nHorizontal : " + moveHorizontal +
            "\nVertical : " + moveVertical;

    }
}

Thank you for your answers

Up...

Pushing by applying a force will follow the equation Force = mass x acceleration, which you can re-arrange to give you acceleration = Force / mass . So you can see this will be dependent upon the variable "speed" (perhaps not a good choice of variable name as it is being used as a force) and the combined masses of the two objects.

If a collision occurs, assuming your collisions are inelastic (the objects stick together), you can assume conservation of momentum, which leads to:
object1_mass x object1_velocity + object2_mass x object2_velocity = (object1_mass + object2_mass) x velocity

Even if the collision is just elastic you can check the results with conservation of momentum:

object1_mass x object1_init_velocity + object2_mass x object2_init_velocity =
object1_mass x object1_final_velocity + object2_mass x object2_final_velocity

I suspect your problem is with the relative masses of the objects and the ratio of mass to your variable "speed".

Edit: changed the bit about collisions for correct wording and a bit more information, though I suspect your problem is covered by the first bit anyway.

Thank you very much for your answer AITheSlacker. I figured it would be an issue of physics which I admit not being really good at.
I'll try to describe my problem the best I can :

When the different balls are stuck with the box (there is no momentum), what I expect is :
- the paper ball cant push the box
- the wood ball can push it but slowly
- the stone ball can push it easily

I don't know if this behaviour are "realistic" according to the physic equations you gave me, but this is the behaviour used in the original game. If I had to explain it I would say that because of the mass of the stone ball, the box isnt such "heavy" an obstacle so it can gain momentum, but for the paper ball, because of the relative masses, it would not be possible to gain momentum.

But now what happens in my game is the contrary... Does it mean that the "speed" and "force" are to be differentiated for each ball ? But how could I achieve that, since I'm using unity physics system and it seems very tricky to me...

From the F = ma you can see that force is directly proportional to acceleration with mass being the scalar. Speed is just a function of acceleration and time.

I would suggest that you set a maximum speed for each ball (you can easily do this by not applying any more force if the ball has reached its maximum speed).

Now you need to choose some forces and masses, how about starting with something simple like this:

/*
Ball    F       m
paper   1       1
wood    10      10
stone   100     100

Box mass: 10kg

*/

With this starting point, they will all have the same acceleration (1 m/s/s). Now by choosing the mass of the box appropriately, e.g. 10 kg, the stone ball will not change much, the wood ball will accelerate at half the normal rate and the paper ball will only accelerate at 10% the normal rate.

Note: They all still move. As you can see from the equation F = ma any level of force will result in some acceleration. To prevent this you need to introduce another force that opposes movement. That will be friction.

Look at this: https://docs.unity3d.com/Manual/class-PhysicMaterial.html
I believe that to get it to work in a robust way you need to Edit - ProjectSettings - Physics - FrictionType - Two Directional. But perhaps this is better now, so just have a play and see.

The friction force is simple to work out: mass of object x gravity (9.81) x (static)friction coefficient.

Example: a 10kg object, with a coefficient of friction of 0.5 will require a force of 10 x 9.81 x 0.5 = 49.05 N to get moving.

So now you can see how to play with their masses, forces and friction to get what you want.

Last bit: You say that the push speed limit needs to vary with ball type. Remember, at the moment any net force acting on the system will accelerate as long as the force is applied. The only cap at the moment is the ball maximum speed. To fix this you will need to add drag force. A drag force is a velocity dependent force that opposes motion e.g. aerodynamic drag.

This can be done two ways:

1) Increase the drag value on the box rigid body by experiment until you get something that works. I do not know how PhysX works with drag, so you may have to guess a bit here.

2) On the box, add a script with a FixedUpdate method and write your own drag code.

Once drag force = the applied force - friction force there will be no further acceleration, i.e. speed is capped.

Hopefully, that is enough for you to get a solution with. Good luck!

Thank you so much for taking the time to answer me. I will read it very carefully and should be able to make it work.