Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Struggles reproducing physics simulation with Physics.Simulate()

Discussion in 'Physics' started by SuperChihuahua, May 18, 2023.

  1. SuperChihuahua

    SuperChihuahua

    Joined:
    Mar 3, 2015
    Posts:
    8
    Hello! I'm simulating balls and want to reproduce a simulation so it becomes the same every time by using Physics.Simulate(). The code looks like this (this is just a minimal test example to figure out why the real project is not working as I want it to):

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class PhysicsBugController : MonoBehaviour
    7. {
    8.     public GameObject ballPrefabObj;
    9.  
    10.     private bool hasAddedBalls = false;
    11.     private bool hasAddedBalls2 = false;
    12.     private bool hasClearedBalls = false;
    13.  
    14.     private int seed = 0;
    15.     private int numberOfBalls = 20;
    16.  
    17.     private List<GameObject> listWithAllBalls = new();
    18.  
    19.     private void FixedUpdate()
    20.     {
    21.         if (!hasAddedBalls)
    22.         {
    23.             AddBalls(seed);
    24.             SimulateBalls(500);
    25.             FreezeBalls();
    26.  
    27.             hasAddedBalls = true;
    28.         }
    29.         else if (!hasClearedBalls)
    30.         {
    31.             foreach (GameObject ball in listWithAllBalls)
    32.             {
    33.                 ball.SetActive(false);
    34.             }
    35.  
    36.             listWithAllBalls.Clear();
    37.  
    38.             hasClearedBalls = true;
    39.         }
    40.         else if (!hasAddedBalls2)
    41.         {
    42.             AddBalls(seed);
    43.             SimulateBalls(500);
    44.             FreezeBalls();
    45.  
    46.             hasAddedBalls2 = true;
    47.         }
    48.     }
    49.  
    50.     private void AddBalls(int seed)
    51.     {
    52.         Random.InitState(seed);
    53.  
    54.         //Spawn area
    55.         float minX = -2f;
    56.         float maxX = minX * -1f;
    57.         float minY = -3f;
    58.         float maxY = 6f;
    59.  
    60.         for (int i = 0; i < numberOfBalls; i++)
    61.         {
    62.             GameObject newBall = Instantiate(ballPrefabObj);
    63.  
    64.             float randomX = Random.Range(minX, maxX);
    65.             float randomY = Random.Range(minY, maxY);
    66.  
    67.             Vector3 randomPos = new(randomX, randomY, 0f);
    68.  
    69.             newBall.transform.position = randomPos;
    70.  
    71.             listWithAllBalls.Add(newBall);
    72.         }
    73.     }
    74.  
    75.     private void SimulateBalls(int iterations)
    76.     {
    77.         Physics.autoSimulation = false;
    78.  
    79.         for (int i = 0; i < iterations; i++)
    80.         {
    81.             Physics.Simulate(Time.fixedDeltaTime);
    82.         }
    83.  
    84.         Physics.autoSimulation = true;
    85.     }
    86.  
    87.     private void FreezeBalls()
    88.     {
    89.         foreach (GameObject ball in listWithAllBalls)
    90.         {
    91.             ball.GetComponent<Rigidbody>().isKinematic = true;
    92.         }
    93.     }
    94. }
    95.  
    The ball is a sphere locked in z-position to make it easier to see what's going on. The walls are three stretched cubes, and I'm using LTS 2021.3.25f1. If I click pause and then play and step forward two times, the balls end up at the following positions every time:



    Is it a bug, is it how it is supposed to work, or did I miss something?
     
  2. arkano22

    arkano22

    Joined:
    Sep 20, 2012
    Posts:
    1,605
    Hi there,

    Not sure what your question is. Is it about deterministic simulation ("[...] so it becomes the same every time")?

    Is the problem that the balls end up at these positions every single time? isn't that what you're looking for?
     
  3. SuperChihuahua

    SuperChihuahua

    Joined:
    Mar 3, 2015
    Posts:
    8
    As you can see from the two images with balls they DONT end up at the same position which is the problem because calling the same lines of code should generate the same result... I want the balls to end up at the same positions over and over again when I call those three lines of code to add, simulate, and freeze the balls!
     
  4. arkano22

    arkano22

    Joined:
    Sep 20, 2012
    Posts:
    1,605
    Determinism won't come from calling Physics.Simulate() manually, there's no difference in how the simulation is internally performed regardless of how this method is called.

    Internally, Unity calls Physics.Simulate() passing a fixed time delta -just like you're doing- a variable number of times per frame, something like:

    Code (CSharp):
    1.  
    2. InternalUnityPhysicsUpdate()
    3. {
    4.     FixedUpdate();
    5.  
    6.     accumulator += Time.deltaTime;
    7.     while (accumulator >= Time.fixedDeltaTime)
    8.     {
    9.         Physics.Simulate(Time.fixedDeltaTime);
    10.         accumulator -= Time.fixedDeltaTime;
    11.     }
    12. }
    Passing a fixed delta value to Physics.Simulate only ensures that time integration is roughly consistent, but it is not enough to achieve determinism. Determinism usually comes (or goes) based on floating point precision issues, that's why a popular option for determinism is to use integer/fixed point math.

    PhysX is not fully deterministic, and to the best of my knowledge there's no way to ensure cross-platform determinism using it.

    Edit - from PhysX documentation:
    https://docs.nvidia.com/gameworks/c...l/RigidBodyDynamics.html#enhanced-determinism

    Enabling "Enhanced Determinism" in Unity's physics options should yield deterministic results between different runs in the same machine:
    https://docs.unity3d.com/2022.1/Documentation/Manual/class-PhysicsManager.html

    This however doesn't guarantee deterministic results across multiple devices/platforms.
     
    Last edited: May 25, 2023
    SuperChihuahua likes this.
  5. arkano22

    arkano22

    Joined:
    Sep 20, 2012
    Posts:
    1,605
    Looking at Physics.Simulate's documentation, I see where the confusion might come from:
    https://docs.unity3d.com/ScriptReference/Physics.Simulate.html

    This is just plain wrong. Updating a physics engine using a fixed timestep value isn't enough to ensure determinism, so whoever wrote this had a rough day.

    Passing a fixed timestep just ensures that the piecewise linear approximation used by the engine for time-dependent quantities (such as velocities) doesn't depend on framerate fluctuations. This is what Unity does by default, but it's not the same as determinism.
     
    Last edited: May 25, 2023
  6. SuperChihuahua

    SuperChihuahua

    Joined:
    Mar 3, 2015
    Posts:
    8
    But if I press Stop and Play it reproduced the same physics simulation over and over again no matter how many times I press Stop and Play, so there's some determinism going on. So I still think it's a little odd you can't restart the physics simulation after pressing play.
     
  7. arkano22

    arkano22

    Joined:
    Sep 20, 2012
    Posts:
    1,605
    Results are completely up to chance when the behavior of something is not deterministic. You could theoretically run the same thing 10000 times and get the exact same results, then run it one more time and get a completely different result. This is why it's dangerous to assume deterministic results without some guarantee.

    This also happens with Unity's event call order, which is a fun source of bugs: Start(), Awake() etc. can be called in different orders for different instances of the same object, and while they are often called in a consistent order -enough to fool you into thinking your code is correct- you often end up discovering that the same build that works fine for you doesn't work at all for someone else. All because object's A Start() is called before object's B Start() on your machine, but order is reversed on some other.

    https://docs.unity3d.com/Manual/ExecutionOrder.html#UpdateOrder
    All of this makes for a lot of "but it works on my computer!" situations.