Search Unity

Physics and prediction

Discussion in 'Multiplayer' started by MariuszKowalczyk, Nov 14, 2012.

  1. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    I have a game: www.ball3d.com
    It has an input lag, because I don't use any client side prediction at the moment.
    I am going to implement this now.

    Questions:

    1) Is there any chance that we will have in the future something like Rigidbody.Simulate (it could work similar to this: http://docs.unity3d.com/Documentation/ScriptReference/ParticleSystem.Simulate.html) ? Anyone from Unity Team can say anything about this? There are some other topics about this and people would love to see something like that. It would be useful in many ways.

    2) http://docs.unity3d.com/Documentation/Manual/ExecutionOrder.html
    Quote from this site:
    Code (csharp):
    1. while (stepping towards variable delta time)
    2.     All FixedUpdate functions
    3.     Physics simulation
    4.     OnEnter/Exit/Stay trigger functions
    5.     OnEnter/Exit/Stay collision functions
    Is there any way to execute that "Physics simulation" at will?

    3) Maybe someone here has any ideas about client side (physics) prediction and Unity?

    At the moment I have 2 ideas how to make the client side prediction: Time.timeScale and the other one is to make the ball physics myself.
     
    Deleted User likes this.
  2. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
  3. foxter888

    foxter888

    Joined:
    May 3, 2010
    Posts:
    530
    technically there isn't a global button for turning on and off the simulations but you can make them sleep and wake them up if you want them to react and even turn on and off collisions and some people simply just make the rigidbodies kinematic. just look at the different functions in the scripting manual.
    rigidbody.Wake(); and yes it's not a joke you can make rigidbodies wake up and go to sleep.
     
  4. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    Hmm I don't really understand how WeakUp and Sleep would help to my problem. Could you explain?
     
  5. damagefilter

    damagefilter

    Joined:
    Nov 6, 2012
    Posts:
    23
    Rigidbody has Sleep() and WakeUp() methods which are used to optimize physics performance.
    If your Rigidbody sleeps it will not be processed (and will therefore not move).
    Invoke Sleep() on a Rigidbody manually and it will fall asleep (stop processing) for at least one frame.
    If you call WakeUp() on it, it will be forced to simulate again.
    However, in case it is sleeping it will automatically wake up if there's a collision.

    In short: You can use Sleep() to make it stop simulations for a bit. You can then also use obj.WakeUp() as substitute for your proposed Simulate() method.

    If you wrap that up in some controller script it might even be a nice solution.

    About the client-side prediction, have you tried the Networking examples over at the asset store?
    There's a thing called NetworkRigidbody along the files, which is used to do prediction on rigidbody objects.
    That might help you out.
     
  6. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    Collisions are the main problem. Fortunately I was able to make my own version of PhysicsSimulation (I use Rigidbody.SweepTest() to detect collisions).
    I have to make further tests (I will post this function here later). It doesn't work exactly like the built-in physics, but it should be enough for prediction.
     
  7. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    I was able to make the client side physics prediction. Here is how:

    1) The client works on higher Time.timeScale all the time (or at least for one frame after a new packet arrive from the server). If you want to simulate 500ms (0.5sec) into the future, you should use this equation to calculate what the timeScale should be: (timeScale = 0.5 / Time.fixedDeltaTime).
    2) When the timeScale is higher, the FixedUpdate function is called many times each frame. I set the initial position in the special script that is first in the script order. Then I remember position, rotation, velocity and angular velocity from each FixedUpdate, I also remember timeStamp. First frame has timeStamp = 0, the second one has timeStamp equal to the previous one plus Time.fixedDeltaTime. Thanks to this I have informations about rigidbody 500ms into the future.
    3) Then in the Update function (which is called still once per frame, even though the timeScale is higher), I use the positions from above and set the final position of the object before rendering (I can for example set it 200ms into the future). I am doing interpolation to make it as smooth as possible.
    4) Because the client works at a higher speed, you may need to make your own delta, to use it in the places, when the time should pass normally. You can do this using Time.realTimeSinceStartup.

    This is basically how it works. It's easier than it sounds.
     
  8. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    Actually the best way of doing it is to do the calculations on server and hold clients back 200ms or 300ms instead of doing simulation on client for prediction.
    You know, The floating point simulations can become quite different many times and then snapping the values back will produce not great results.
     
  9. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    The whole point of prediction is to not hold the client back. I already have what you described, the game is waiting for two states from the server and then is interpolating between them. It causes the input lag, that's why I am doing the prediction. Input prediction has to be done on the client.

    I am not sure how your method would work, but the way I am doing this now is quite accurate.
     
  10. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    There are many ways around this, sorry but 300ms input lag in an action game is not playable, imagine Call of Duty with 300 MS delay + your ping?
     
  11. fholm

    fholm

    Joined:
    Aug 20, 2011
    Posts:
    2,052
    But, on the topic of the thread: I've found it incredibly hard to use the built in Unity PhysX solution, as it does not offer enough control for you to be able to rewind/correct positions of the local player. I make my player character a kinematic rigidbody, and then control him manually using raycasts, etc, giving me full control and allowing proper client prediction + smoothed correction and by keeping your own, manual fixed time step you can get rid of all silly temporal artifacts also :;)
     
  12. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    @tholm
    We are talking on physics based gameplay which u are controlling a ball in or ...
    As u said the best way is to use a deterministic physX system or at least just have a physics engine that works with reversed time steps.

    in good network with ping of 30ms and added 100 ms you'll have 130ms delay and it's not a great thing but might be acceptable in many online games and also is better that constantly predicting and correcting/snapping in a huge amount.
     
  13. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    My method lets me do exactly this.
     
  14. GalZohar

    GalZohar

    Joined:
    Nov 28, 2012
    Posts:
    11
    Marius, any chance for a working example project?

    This is the closest thing I could find for proper network prediction for Unity physics that don't involve re-writing the physics engine, but due to lack of experience I'll probably have a hard time implementing it without any working example to look at, and I haven't found this concept mentioned anywhere else.
     
  15. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    This method works fine when you don't have lots of changes in rigidbody's velocity vector (specially in direction).

    If you have it then i think clients should go out of order much and correcting them always might not be that beautiful.
     
  16. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    I was working on this for more than two weeks, but in the end I decided not to use it in the game for now. It's because when someone kicked the ball, it changed the direction rapidly and since it's not predictable, players (testers) have noticed that there was something wrong. I was trying lerps and other stuff, but when I solved one problem, the other one came out. I will probably add this in the future as an option.

    I will try to give you the code with the simulation part (the network part would not help you, since I was doing it just to test the concept, it's one big mess, but it's working pretty well).

    So as far as I remember (it was some time ago :)) the idea is to keep the client running at a higher timeScale. It means that all the Time.deltaTime will not work so you have to make your own delta:

    In some Update function:
    Code (csharp):
    1. delta = Time.realtimeSinceStartup - lastRealtimeSinceStartup;  
    2. lastRealtimeSinceStartup = Time.realtimeSinceStartup;
    Thanks to the increased timeScale, every FixedUpdate function will be called many times, but the Update function will be called only once after all the FixedUpdates. The execution order is the key here.


    My example works only for one object. If you want to make it work for more, then you need to add another ballRigidbodyStates variable.


    You need to create special script, called FirstScript:
    Code (csharp):
    1. #pragma strict
    2.  
    3. function Update()
    4. {
    5.     GameManager.instance.RigidbodySimulationUpdate();
    6. }
    7.  
    8. function FixedUpdate()
    9. {
    10.     GameManager.instance.RigidbodySimulationFixedUpdate();
    11. }
    12.  
    After creating it, go to Edit->Project->Script Execution Order. In the inspector add that FirstScript and make it running before the Default Time (so put it above, with a negative number like -100). Then attach that FirstScript to some GameObject


    Here is the code (I am copying and pasting from different files, I hope it will work):
    (You can past this to the FirstScript if you want.)

    Code (csharp):
    1.  
    2.  
    3. @System.NonSerialized rigidbodyStatesCount : int = 50;
    4. @System.NonSerialized var ballRigidbodyStates : CRigidbodyState[];
    5.  
    6. @System.NonSerialized var rigidbodyTimestamp : double = 0.0;
    7. @System.NonSerialized var rigidbodyTimestampTmp : double = 0.0;
    8.  
    9. class CRigidbodyState
    10. {
    11.     var position : Vector3;
    12.     var rotation : Quaternion;
    13.    
    14.     var velocity : Vector3;
    15.     var angularVelocity : Vector3;
    16.    
    17.     var timestamp : double;
    18. }
    19.  
    20. //call this once in Awake()
    21. function InitRigidbodySimulation()
    22. {
    23.     ballRigidbodyStates = new CRigidbodyState[rigidbodyStatesCount];
    24.    
    25.     for (var i = 0; i < ballRigidbodyStates.length; i++)
    26.     {
    27.         ballRigidbodyStates[i] = new CRigidbodyState();
    28.     }  
    29. }
    30.  
    31.  
    32. //You will call this function to prepare your rigidbody object, so give this function the initial position, rotation, velocity and angular velocity.
    33.  
    34. private var prepareRigidbodyBallPosition : Vector3;
    35. private var prepareRigidbodyBallRotation : Quaternion;
    36. private var prepareRigidbodyBallVelocity : Vector3;
    37. private var prepareRigidbodyBallAngularVelocity : Vector3;
    38. function PrepareRigidbodyBall(position : Vector3, rotation : Quaternion, velocity : Vector3, angularVelocity : Vector3)
    39. {
    40.     prepareRigidbodyBallPosition = position;
    41.     prepareRigidbodyBallRotation = rotation;
    42.     prepareRigidbodyBallVelocity = velocity;
    43.     prepareRigidbodyBallAngularVelocity = angularVelocity;
    44. }
    45.  
    46. function SetRigidbodyTimestamp(timestamp : double)
    47. {
    48.     rigidbodyTimestampTmp = timestamp;
    49. }
    50.  
    51.  
    52. function RigidbodySimulationUpdate()
    53. {
    54.     currentRigidbodyState = 0;      
    55. }
    56.  
    57. function RigidbodySimulationFixedUpdate()
    58. {
    59.     if (Network.isClient  !ReplayManager.instance.isPlaying  (!GameManager.instance.localPlayer || GameManager.instance.localPlayer.team != Team.Spec))
    60.     {
    61.         if (currentRigidbodyState == 0)
    62.         {
    63.        
    64.             validRigidbodyStates = 0;
    65.            
    66.             rigidbodyTimestamp = rigidbodyTimestampTmp;
    67.  
    68.                 if (!GameManager.instance.ballObject.rigidbody.isKinematic) //przez chwile po zmianie ze specta zle bedzie, wiec trzeba sprawdzac
    69.                 {
    70.                     GameManager.instance.ballObject.transform.position = prepareRigidbodyBallPosition;
    71.                     GameManager.instance.ballObject.transform.rotation = prepareRigidbodyBallRotation;
    72.                
    73.                     GameManager.instance.ballObject.rigidbody.velocity = prepareRigidbodyBallVelocity;
    74.                     GameManager.instance.ballObject.rigidbody.angularVelocity = prepareRigidbodyBallAngularVelocity;
    75.                 }
    76.  
    77.            
    78.         }
    79.        
    80.         if (currentRigidbodyState < rigidbodyStatesCount)
    81.         {
    82.             if (GameManager.instance.ballObject)
    83.             {
    84.                 ballRigidbodyStates[currentRigidbodyState].position = GameManager.instance.ballObject.transform.position;
    85.                 ballRigidbodyStates[currentRigidbodyState].rotation = GameManager.instance.ballObject.transform.rotation;
    86.            
    87.                 ballRigidbodyStates[currentRigidbodyState].velocity = GameManager.instance.ballObject.rigidbody.velocity;
    88.                 ballRigidbodyStates[currentRigidbodyState].angularVelocity = GameManager.instance.ballObject.rigidbody.angularVelocity;
    89.                
    90.  
    91.                 if (currentRigidbodyState == 0)
    92.                     ballRigidbodyStates[currentRigidbodyState].timestamp = 0;
    93.                 else
    94.                     ballRigidbodyStates[currentRigidbodyState].timestamp = ballRigidbodyStates[currentRigidbodyState - 1].timestamp + Time.fixedDeltaTime;
    95.  
    96.             }
    97.                        
    98.            
    99.                
    100.             currentRigidbodyState++;
    101.             validRigidbodyStates = currentRigidbodyState;
    102.  
    103.            
    104.         }
    105.  
    106.     }
    107. }
    108.  
    109.  
    OK now as you can see I am using GameManager.instance.ballObject, you should change this to your own rigidbodyObject, the one you want to simulate. It should be non-Kinematic.

    If this code will work, then you will have the predicted states of your rigidbody in the array ballRigidbodyStates.
    Now in the Update function you can interpolate between these states and use it to change the position of your rigidbody. You can even use the same rigidbody you have used in the simulation, it doesn't matter.

    Here is an example:
    Code (csharp):
    1. #pragma strict
    2.  
    3. private var testTimer : float = 0.1;
    4.  
    5. private var currentIndex : int = 0;
    6.  
    7.  
    8. function Update()
    9. {
    10.    
    11.     if (Input.GetKey(KeyCode.C))
    12.     {
    13.         GameManager.instance.ballObject.transform.position = Vector3(0, 0.4, 0);
    14.         GameManager.instance.ballObject.rigidbody.velocity = Vector3(0.1, 0.1, 0);
    15.     }
    16.            
    17.     if (Input.GetKey(KeyCode.Z))
    18.     {
    19.         testTimer -= 0.03 * StaticVariables.delta;
    20.        
    21.         if (testTimer < 0.1)
    22.             testTimer = 0.1;
    23.            
    24.     }
    25.    
    26.     if (Input.GetKey(KeyCode.X))
    27.     {
    28.         testTimer += 0.03 * StaticVariables.delta;
    29.     }
    30.    
    31.     //Debug.Log("testTimer: " + testTimer);
    32.    
    33.    
    34.     if (Input.GetKey(KeyCode.V))
    35.     {
    36.         currentIndex--;
    37.        
    38.         if (currentIndex < 0)
    39.             currentIndex = 0;
    40.            
    41.     }
    42.    
    43.     if (Input.GetKey(KeyCode.B))
    44.     {
    45.         currentIndex++;
    46.        
    47.     }
    48.    
    49.     if (currentIndex >= GameManager.instance.validRigidbodyStates)
    50.         currentIndex = GameManager.instance.validRigidbodyStates - 1;  
    51.    
    52.    
    53.     if (GameManager.instance.validRigidbodyStates > 0)
    54.     {
    55.  
    56.         var extrapolationLength : float = testTimer;
    57.        
    58.         for (var i : int = 0; i < GameManager.instance.validRigidbodyStates; i++)
    59.         {
    60.             //Debug.Log(GameManager.instance.ballRigidbodyStates[i].timestamp);
    61.             if (GameManager.instance.ballRigidbodyStates[i].timestamp >= extrapolationLength || i == GameManager.instance.validRigidbodyStates - 1)
    62.             {
    63.                 var lhsBall : CRigidbodyState = GameManager.instance.ballRigidbodyStates[Mathf.Max(i - 1, 0)];
    64.                
    65.                 var rhsBall : CRigidbodyState = GameManager.instance.ballRigidbodyStates[i];
    66.                
    67.                
    68.                 //Use the time between the two slots to determine if interpolation is necessary
    69.                 var length : double = rhsBall.timestamp - lhsBall.timestamp;
    70.                 var t : float = 0.0F;
    71.                
    72.                 if (length > 0.0001)
    73.                     t = (extrapolationLength - lhsBall.timestamp) / length;
    74.                    
    75.    
    76.                 GameManager.instance.ballObject.transform.position = Vector3.Lerp(lhsBall.position, rhsBall.position, t);
    77.                 GameManager.instance.ballObject.transform.rotation = Quaternion.Slerp(lhsBall.rotation, rhsBall.rotation, t);
    78.  
    79.                 break;
    80.                
    81.                    
    82.                 }
    83.                
    84.         }
    85.     }
    86.  
    87.    
    88.     GameManager.instance.PrepareRigidbodyBall(Vector3(6, 0.4, 0), Quaternion.identity, Vector3(10, 10, 0), Vector3(4, 6, 1));
    89.    
    90.    
    91. }
    92.  
    I hope it will help you. It may look weird, but this is all quite simple :)
     
    Last edited: Jan 26, 2015
  17. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    So what do u do now Marius?
    Your case seem what i've said is not good for this algorithm.
     
  18. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    The "kick" happens once in a few second, so it's not that often :) I will probably make it as an option in the menu. The game works good enough if your ping is smaller than 100 for good players even 120 is ok. But it's not possible to play on the ping above 200. And here is when someone could turn this code on.

    I have to rethink all this. I don't like this timeScale to be that high at all. It may have negative impact on performance, I would have to optimize all this.
    Maybe I will not do anything for now and wait. The game is fun without it. The only problem is that it's hard to play intercontinental games :)

    PS: My name is Mariusz :)
     
  19. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    I would like to inform you, that I was able to implement the prediction (more or less as I described above) in my game: www.ball2d.com
     
  20. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    I would like to inform you that I have improved the method described by me in this topic and was able to implement a fully working client side prediction using Unity builtin physics engine. If you want to see it in action, go to www.ball3d.com

    It took me more than 3 months of hard work to implement it and a lot of time to even figure out how it all should work. If you don't need a realistic physics in your game, the best option is not to use this method. But if you are desperate enough like I was, you can read a few clues how to do this:

    - Client is working at Time.timeScale = 100 all the time
    - Setting Time.fixedDeltaTime to 99999 breaks the physics loop, but you have to set it to a normal fixed delta time before the next frame.
    - You should not use OnCollisionStay and other functions like that, because they allocate memory and when your game will do the physics for example 15 times per frame, then it will add up, so you have to overcome this somehow, I did this by creating my own optimized collision detection, using simple ifs and distance function.
    - Particles will no longer be working as you want, you have to use Simulate() function to go around this.
    - Animation will no longer work as you want, so again you have to use some tricks to overcome this.
    - Other things you need to know are described in my previous posts.

    It's working perfectly and it's even possible to optimize it to a point when it's working as it should without any fps drops.

    So if you have no other choice this is probably your only chance to do this. But I don't recommend it, it's only for desperate people ;)

    I wrote this post just to let you know that it's possible.
     
    Last edited: May 4, 2016
    PrimalCoder, hippocoder and mgear like this.
  21. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    Nice job man!
     
  22. omarmoh

    omarmoh

    Joined:
    Apr 18, 2014
    Posts:
    16
    Can you release an example project? because i don't think i understand how you did it, and this could be exactly what i need, so it would help alot if you released one.
     
  23. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    Tell my what type of game are you making? If you don't need realistic physics, you shouldn't even try this method. In most of the games out there (FPS, RPG etc.), you don't need this type of physics. For games like that you should make your own simple collision detection/physics (as simple as possible).

    It's hard for me to provide an example project, this code is not too elegant. When I will be back from my vacation I will try to explain in more details what is going on under the hood. But first tell me what type of game you are making. If you don't need the physics, I will tell you some tips how to make the alternative, because I have made it too for my other game (prototype) and it's much simpler.
     
  24. omarmoh

    omarmoh

    Joined:
    Apr 18, 2014
    Posts:
    16
    I didn't start working on the game yet because i am doing some tests before starting to work on the project, currently i am just trying to make a simple rigidbody cube to move with client side prediction.
     
  25. MariuszKowalczyk

    MariuszKowalczyk

    Joined:
    Nov 29, 2011
    Posts:
    301
    So tell me what game are you going to make?
     
  26. omarmoh

    omarmoh

    Joined:
    Apr 18, 2014
    Posts:
    16
    I want to make a third person shooter using a rigidbody.
     
  27. Stanchion

    Stanchion

    Joined:
    Sep 30, 2014
    Posts:
    269
    @omarmoh @MariuszKowalczyk You can now do prediction for auth rigidbodies thanks to the new Unity beta, message me for details