Frustration with Rigidbody2D.AddForceAtPosition

While developing physics based tools I’ve ran into multiple issues regarding physics behavior.

Long story short, I noticed Unity was going forward & backward with their AddForceAtPosition changes.
It’s always getting changed between world space & local space parameters.
This causes objects to fly away with little force added.

Solution

This was always a problem for me because I’ve wanted my tools to work properly on all Unity versions.
Anyway, I’ve written a wrapper as a workaround.

Note: Some statements doesn’t make sense and could be removed.
That’s because I’m using additional statements to mark them tested on that specific version.

Unresolved Issue

With Unity 2018.3.9 there was a new changed introduced - let’s use local positions again!?
I guess this is another regression?

2018.1 - local positions
2018.3.9 - local positions
2018.3.14 - world positions
2018.4 - world positions
2019.2 - local positions
2019.3 - local positions.

The main problem UNITY_2018_3_9 compiler directive does not work.

// Unity Physics Changes
public class PhysicsHelper2D {
    public static void AddForceAtPosition(Rigidbody2D body, Vector2 force,  Vector2 worldPosition) {
        #if UNITY_2019_3_OR_NEWER
            body.AddForceAtPosition(force, body.gameObject.transform.InverseTransformPoint(worldPosition));

        #elif UNITY_2019_2_OR_NEWER
            body.AddForceAtPosition(force, body.gameObject.transform.InverseTransformPoint(worldPosition));

        #elif UNITY_2018_4_OR_NEWER
            body.AddForceAtPosition(force, worldPosition);

        // UNITY_2018_3_9 introduced change to local position
        // body.AddForceAtPosition(force, body.gameObject.transform.InverseTransformPoint(position));

        #elif UNITY_2018_3_OR_NEWER
            body.AddForceAtPosition(force, worldPosition);

        #elif UNITY_2018_1_OR_NEWER
            body.AddForceAtPosition(force, body.gameObject.transform.InverseTransformPoint(worldPosition));

        #elif UNITY_2017_4_OR_NEWER
            body.AddForceAtPosition(force, body.gameObject.transform.InverseTransformPoint(worldPosition));
 
        // Older Unity
        #else
            body.AddForceAtPosition(force, worldPosition);
        #endif
    }
}

I have no idea what you’re referring to as there have been zero changes to that method. It calls directly into Box2D itself and always has. I can post you the actual call into Box2D here:

    if (mode == kForceMode)
        m_Body->ApplyForce(b2Force, b2Position, true);
    else
        m_Body->ApplyLinearImpulse(b2Force, b2Position, true);

b2Force and b2Position are what you pass. These are the respective Box2D calls:
ApplyForce
ApplyLinearImpulse

There were no changes to 2D physics in 2018.3.9 whatsoever. So with that said, I’m not discounting your issue and I’d really like to help but I’ve no idea why you’re seeing these kinds of changes.

I’ll go away and download 2018.4 and 2019.2 and see if I can see what you’re saying is happening.

Those gifs are difference between 2018.3.14 (mine) & 2018.3.9 (customer)

2018.3 - 2018.3.14f1
2018.4 - 2018.4.12f1
2019.2 - 2019.2.3f1
2019.3 - 2019.3.0b6

I could previously clearly reproduce this behavior in clean project
I’ve also been discussing these issues with developers a year back.

Thanks for the info.

Do you have links to those conversations?. Maybe it’ll reveal something useful. I’ll download the versions above and do a simple test.

btw, if you want to send me anything that might help me find out what’s changing then you can upload a test project here: https://oc.unity3d.com/index.php/s/Rn0HjdiLtYoXZCs

For the conversations, I don’t remember. I don’t have have the access to Unity’s slack anymore either.

I might try uploading repro project until tomorrow.

Well I’m the dev who can help you here. You obviously shouldn’t have to jump through the code-hoops you’ve posted above and we can get to the bottom of what’s going wrong here.

So I’ve just done a test comparing 2018.4.12f1 against 2019.2.3f1.

A simple Rigidbody2D with zero gravity scale with a BoxCollider2D attached with a script that applies a force at a position upon start. Here I apply a force of (0,10) at the position (0,0). The Rigidbody is positioned at (4,0,0).

If it were local-space then the body would move upwards with no torque as the force is being applied at the center-of-mass. In both cases, the body rotates and moves upwards as expected.

Something else is causing your behaviour change but I don’t know what it is. I can confirm that both are in world-space and the code for those versions confirms this (as expected).

Here’s the project I used: https://oc.unity3d.com/index.php/s/xiRZ9oPz0Uq1FPa

Maybe when I get your project it’ll reveal what’s changing.

Okay, when I looked at it in depth I noticed that the issue might be a little different.

In my case, the object is created in the same frame as AddForceAtPosition command is executed.
Perhaps in some Unity versions there is one frame delay for Box2D object position update.
So it’s adding force in world coordinates, meanwhile Box2D object is still in default (0, 0) position - that might be it.

I’m sending stripped project via DM… took me a while to clean it up.

About the project I’m sending.

  • Scene is called “Repro Scene”
  • Console is printing out AddForceAtPosition parameters just in case
  • Since camera is parent of the scene, you can move it around, the further camera is from zero coordinates the crappier physics response is
  • In PhysicsHelper2D.cs when using my commented workaround - physics works as expected

New objects are created in Slicer2D.cs using PerformResult method.

No, it doesn’t work like that. When you create a Rigidbody2D it takes the current Transform XY position and Z rotation and creates it there. A simulation step doesn’t read the Transform to position bodies, it does the opposite, it writes the body position to the Transform (after the simulation step).

I’m concerned a little when you mention “frame”. Physics executes by default at the FixedUpdate and this doesn’t necessarily happen per-frame unless your frame-rate is exactly the same as the fixed-update. If you’re issuing force changes during the frame-update then you’ll get varying results of force. You can have several frames in-between the actual fixed-update simulation step. I’m assuming you know this alreadty but thought it worth mentioning.

I’ll take a look at your project today though to figure out what’s going wrong.

So there’s a lot going on in the cut-down project you submitted but some things stand out.

The first is that there’s a complete absense of usage of FixedUpdate which I presume is when you’re running the simulation (this is the default). I see forces being issued in “Update” (frame-rendering) only. This isn’t necessarily the source of the issue if you can guarantee that more forces won’t be applied to the same Rigidbody2D until the simulation has run which isn’t necessarily going to be before the next “Update”. This’ll only be the case if you’re running the simulation manually per-frame. If the physics is running 50hz and your frame-rate is 100hz then you’ll get two frame updates for every one simulation step (approx). I presume that because the slicing happens when the user releases the touch/mouse and that it’s once only then it shouldn’t matter but it’s important for me to state that this can and is an issue to be aware of.

The second is where it seems the code is going wrong. You have a method named “PerformResults”. Here you create a GameObject and copy the Rigidbody2D/Slider2D components from it. After all that iteration/reflection copying is done you then set the Transform of the GameObject. This is bad. You should never modify the Transform on a GameObject that has a physics component on it. By adding a Rigidbody2D you are asking for it to be in charge of the Transform i.e. act as a proxy to the Transform and you should let it do that work.

If you modify a Transform then the Rigidbody(2D) isn’t updated there and then. This is what AutoSyncTransforms is all about and I’m not going to go over in detail what all that is about but suffice to say, this should be off. If it’s on then we have to check to see if a user has changed a Transform on a GO with a physics component (bad behaviour) anytime you read spatial state from the physics system. If it’s off (should be) then this doesn’t happen (good behaviour as it’s fast). We automatically sync-transforms prior to a simulation step but again, we shouldn’t have to do anything because again it’s bad behaviour to modify Transforms with physics components on them (use the bodies to effect pose changes). The only way this can be changing for you is if you project has AutoSyncTransforms on in one versions and off in another. This is a physics setting and we don’t change this for you. New projects have this setting off by default.

So with that explanation aside, going back to your “PerformResults” and given the fact that all you want is for your Rigidbody2D to be at the Transform pose, you should set the GameObject Transform before you copy the components (including the Rigidbody2D) to it. As I’ve said previously, when you add a Rigidbody2D then it reads the Transforms pose there and then so if you’ve already set the Transform, it’ll be created at the correct location. Obviously you can not change this around and just do the extra step of directly setting the Rigidbody2D.position/rotation from the Transform but that’s doing extra work you don’t need to do but would work.

Debugging your “PerformResults” and going into “CopyRigidbody2D” you can see the “newRigidBody” position is (0,0). This is the same for the 2018.4.12f1 and 2019.2.3f1 so it’s likely related to the project settings of AutoSyncTransform in your full project.

1 Like

So to conclude, I modifed your code so that the Transform on the new GameObject (gObject) slice is set first prior to copying over the Rigidbody2D/Slice2D.

Before result: Screen capture - f9faa0d96e12308c8a69f28ec2eafd4c - Gyazo
After result: Screen capture - 8f4f848901be4a895408587c1d87b75d - Gyazo

Great, it seems setting up transform before rigidbody’s code fixes the issue in this case.

Changing new rigidbody position to original rigidbody’s position also seems to be working, however I believe that’s not a good way to solve it.

// Slicer2DComponents.CopyRigidbody2D
newRigidBody.position = originalRigidBody.position;

Using Instantiate (instead copying components manually)

I’m also using another method to create new objects using instantiate method.
Instantiate seems to be working a little bit faster than my own CopyComponents method, so that’s the reason.

1st solution
The issue in that case is fixed if I set transform position inside Instantiate method.

2nd solution
When I set transform after instantiation, clunky force movement can be fixed by just accessing rigidbody’s position:

Vector2 pos = newRigidBody.position;

Perhaps there is transform sync when accessing the variable?

Anyway, my issue seems to be fixed, I really hope.
Probably I should rename thread title to match an actual problem.

I’ve also been doing some tests to find what’s causing different behavior between versions. Sync transform didn’t seem to be the case. I still wonder about the cause of this.

Thanks!

Glad it’s working for you!

There’ll only be a transform sync (an attempt to ensure any Transform changes affect the respective Rigidbody2D/Collider2D) if AutoSyncTransforms is on. You should have this option turned off though for this very performance reason and never modify the Transform when a Rigidbody2D/Collider2D is present if it can be avoided.

1 Like