Stabilize physics results?
I am making a 2D physics puzzle game where the player sets specific settings in the scenario and then clicks a button to execute a plan, like a Goldberg machine or the The Incredible Machine games.
Now I added a new object (a Spring) which takes into account into account the last known velocity of the object that hit it based on the latest FixedUpdate in order to calculate the force to apply on the rigidbody hit.
For my game to work, I need the physics to work the same way whenever the player selects the same scenario twice. For now, this is going well, but the Spring mentioned above is not working well. I suspect that this is because my FixedUpdates run on slightly different times each time I play, and then the velocity registered is slightly different, leading to different results in the puzzle.
Are there any known solutions to work around this?
Thanks!
Answer by ulissesnascim · Dec 02, 2019 at 03:36 AM
Note: after a lot of testing, I noted that zeroing the drag and angular drag of the game objects seemes to solve the issue. However, I cannot use this as a solution since the puzzle uses friction as an important component in some of the puzzles.
Answer by lgarczyn · Dec 02, 2019 at 09:04 PM
The more complex your interactions, the more likely it will be that small differences accumulate. If you want something to act the same every time, you need a scripted event and as little complex math as possible. This is called being deterministic. Unfortunately Unity physics are not deterministic, or at least not if you deal with them in "bad" ways.
Dangerous actions that might break determinism include user input, calls to Random having any effect on the simulation, and doing anything physical in Update.
Using Time.deltaTime instead of Time.fixedDeltaTime in update is considered bad, but won't affect your results. See the documentation.
One of your problem might be that the object does not always hit your spring at the same time of the frame, and therefore the action is slightly different. For example, it may bounce further away during one frame, and therefore receive the force later, giving it a stronger boost. You can solve that by replacing the unity bounce by your own:
When receiving an OnCollisionEnter event with the spring, calculate the position of the ball during collision. This can be done in multiple ways, but the easiest would be to get the last position and velocity, and to do a raycast in the direction of the velocity.
Once that is done, simply Mirror the velocity along the normal of your spring pushing surface.
Set your rigidbody's position to the calculated position, and the velocity to the mirrored velocity.
If you're ok with replacing all bounces by your system, simply set the bounce factor of your ball to 0, and use the collision information to get the impact velocity. Set the velocity to be used for the next frame.
Another simpler way is to detect a collision, then revert your rb's position and velocity back one frame. For the next frame, set the bounce factor of your physics material to a value higher than 1. This is simpler, but if two bounces happen in a single frame you'll have problems. You could try this first to check if it helps you at all.
Thanks for the help! I was actually already doing my own "bounce" system, which does something really similar to what you suggested: it inverts the ball's velocity vector depending on the sin/cos of the Spring angle.
$$anonymous$$y issue seems to be that, as you said, the collision sometimes registers at different times during the frame, thus getting a different last$$anonymous$$nownVelocity to be inverted. Currently, I'm using FixedUpdate to update my last$$anonymous$$nownVelocity. Should I be using LateUpdate ins$$anonymous$$d?
Also, you have a great point about Unity's native bounce against the Spring which may be a cause for the issue as well. Currently, my ball has a Physics$$anonymous$$aterial2D with zero bounce and my Spring has no custom Physics$$anonymous$$aterial2D. In that scenario, I think the native bounce is already not being simulated, right?
Here is my current code for registering my last$$anonymous$$nownVelocity. The method down below called SetLastImpactForce is called when the spring collides with the ball. The velocityWhenHitVariable is then inverted (according to the Spring angle) and applied to the ball.
private void FixedUpdate()
{
foreach (SpringInteractableData
springInteractable in
rigidbodiesInRange)
{
Rigidbody2D rb = springInteractable.rigidbody;
springInteractable.currentVelocity =
rb.velocity;
}
}
private void
SetLastImpactForce(Rigidbody2D
rigidbody)
{
foreach (SpringInteractableData
springInteractable in
rigidbodiesInRange)
{
if (springInteractable.rigidbody ==
rigidbody)
{
velocityWhenHit = springInteractable.currentVelocity;
break;
}
}
}
Do not use LateUpdate for physics.
Unless a collision happens, the last known velocity won't change through the frame. The object will simply continue in a straight line, as forces are only applied to the velocity in-between frames. This is very important, objects in unity only follow an approximation of a parabola, made of segments for each frame. The only thing that can change that velocity during the frame is a collision or maybe a Joint (not sure).
I don't know when a rigidbody applies its gravity, but it likely happens at the end of a frame, before the next FixedUpdate. Simply disable it and implement your own gravity to make sure.
I thought about it, the problem might actually come from your custom bounce. From what I'm seeing here it is pretty much spaghetti code. There is absolutely no need for a giant loop or public variables to do this, and I have trouble seeing what you're trying to do. This can all happen using OnCollisionEnter.
If ins$$anonymous$$d of using a script to make the spring bouncy you used a physics material on the head of the spring, you could get the exact same effect. Simply make the ball bounce normally, and the head of the spring very bouncy, with a "$$anonymous$$aximum" bounce combine.
I'm sorry for switching from "don't use Unity's bounces" to "use Unity's bounces", but the reveal of your own bounce system makes it a more likely culprit than Unity's physics engine.
Actually, I can't do that inside the OnCollisionEnter because the velocity is already much slower due to the collision itself. What I then needed to do was create a class that would store the last known velocity. This was also useful for doing a "moving average" of sorts in order to get a more stable last velocity each time. Oh, and the loops aren't an issue as my game has very few interactable objects near the springs.
I like both your other suggestions (making a custom gravity or using Unity's bounce). I've run a couple of tests with the latter and it seems to work. I will post my progress when I'm sure it works.
Thanks!