Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Here is a case where the new transform syncing system causes problems (and how to fix it!)

Discussion in 'Physics Previews' started by PhilSA, Aug 28, 2017.

  1. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    I've recently encountered a situation where the new transform syncing system creates a problem that we didn't have before, and I may have a few suggestions on how to fix it. It's a very specific and complex case, and I'm not sure if I'll be able to explain it clearly and/or justify why all of this is necessary, but here's my attempt. Just skip to the second post with the test project if this is too much information. I think the code + comments in the project illustrates the problem more clearly.

    I am working on a kinematic character controller that supports being pushed around by "moving platforms", which are also kinematic rigidbodies.

    Back in 2017.1, in the same FixedUpdate() frame, things more or less went as follows in order to implement being pushed by moving platforms:
    1. The moving platform script moves its position to where it would be given its current velocity. This is done with transform.position, which means both the transform and the rigidbody positions are up to date
    2. The character script does a ComputePenetration() to see if it would be colliding with any moving platforms once they have done their movement this frame, and then moves to the calculated resolved position using MovePosition() (for interpolated movement). In order for this to work, we need both the rigidbody and the transform positions of the moving platform to be up to date. ComputePenetration() needs the rigidbody to be up to date in order to detect the collision, but it also needs the transform position to be up to date because we'll need to pass it as an argument to the computePenetration method. We could use the rigidbody position here if our moving platform was only one simple collider, but in the case where you have child colliders that can be offset from the parent rigidbody, it doesn't work anymore.
    3. The moving platform script goes back to its original position at the start of the frame (again, using transform.position), but then moves using MovePosition() so that interpolation will work correctly
    Here's some simplified pseudocode of all this
    Code (CSharp):
    1.  
    2.     public class GameUpdatesManager
    3.     {
    4.         public List<MovingPlatform> movingPlatforms;
    5.         public List<MyCharacterController> characterControllers;
    6.  
    7.         private void FixedUpdate()
    8.         {
    9.             // Simulate placing all platforms at their goal positions
    10.             foreach (MovingPlatform platform in movingPlatforms)
    11.             {
    12.                 platform.transform.position += platform.velocity * Time.deltaTime;
    13.             }
    14.  
    15.             // Make all characters calculate the end position when decollided from all platfoms, and move there with interpolation
    16.             foreach (MyCharacterController character in characterControllers)
    17.             {
    18.                 character.CalculateDecollisionFromOverlappingPlatforms();
    19.                 character.rigidbody.MovePosition(character.calculatedEndPosition);
    20.             }
    21.  
    22.             // Desimulate all platforms and Move with interpolation
    23.             foreach (MovingPlatform platform in movingPlatforms)
    24.             {
    25.                 platform.transform.position = platform.positionAtStartOfFrame;
    26.                 platform.rigidbody.MovePosition(platform.transform.position + (platform.velocity * Time.deltaTime));
    27.             }
    28.         }
    29.     }
    30.  

    The final result is that the platform smoothly pushes the character around, with no visible inter-penetration and all movement is perfectly interpolated.

    Now in 2017.2 with autosync off, this doesn't work anymore, because the rigidbody position isn't updated along with the transform position, and so the ComputePenetration() cannot accurately detect the collision (it is always one frame late). I tried 2 different things to solve this:
    • Instead of only moving the platform with transform.position, I also moved it with rigidbody.position at the same time. That way, all the information is up to date and it's relatively inexpensive. However, the problem is that this seems to prevent the rigidbody.MovePosition() from working for some reason. As if the transform syncing had priority over the deferred movement cause by MovePosition.
    • I used a Physics.SyncTransforms() after I initially moved the platforms, and also after I replaced them at their original position, just before doing MovePosition(). This makes everything work well, but those two SyncTransform calls multiply the time of my FixedUpdate 5x. The performance cost is way too big.

    Basically, the problem boils down to this: ComputePenetration needs both the transform and rigidbody positions to be up to date in order to work properly, but since 2017.2, we cannot have both of those properly in sync without breaking interpolated rigidbody movement or breaking performance.

    So here are the possible solutions that I can think of:
    • Make colliders store their position/rotation in the actual physics simulation (not sure if this is a stupid idea or not.... it probably is). A bit like rigidbody.position/rotation, but for colliders (and readonly). That way, when using ComputePenetration, we can pass collider.position and collider.rotation as arguments and have the actual pose that this collider currently has according to the physics engine
    • Find a way to make MovePosition work correctly with interpolation even if we "dirtied" the transform by modifying its position beforehand
    • Give us an inexpensive Physics.SyncTransform(Rigidbody r) so that we can manually sync a specific rigidbody and un-dirty it for the syncing system
     
    Last edited: Aug 29, 2017
  2. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926


    Here's a test project that will hopefully clear things up. How to use:
    • Open with unity 2017.2 (beta 8 here)
    • Open the test scene and press Play
    • Find the "UpdatesManager" object in the scene
    • Try out the various "TestingModes" in the UpdateManager's inspector. "SyncTransforms" mode is the only one that gives the desired results, but unfortunately it is too expensive on performance
    • Look at the FixedUpdate() method of UpdatesManager.cs for an explanation of why all of these modes have problems
    • Feel free to play around with this project if you think there is an alternative way of doing this that I didn't think of. Basically, we want the same end result as the "SyncTransforms" mode, but without the performance hit of using Physics.SyncTransforms(), and without too much pre-arranged stuff
    note 1: the green/orange material change on the character reprensents if the CapsuleCast returned a hit or not. It is used to illustrate a problem with the "RigidbodyMovement" mode in the UpdatesManager
    note 2: the fixedTimeStep has been exaggerated to more easily notice some of the problems
     

    Attached Files:

    Last edited: Aug 29, 2017
    nindim likes this.