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.
What I was doing in 2017.1
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:
- 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
- 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.
- 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
public class GameUpdatesManager
{
public List<MovingPlatform> movingPlatforms;
public List<MyCharacterController> characterControllers;
private void FixedUpdate()
{
// Simulate placing all platforms at their goal positions
foreach (MovingPlatform platform in movingPlatforms)
{
platform.transform.position += platform.velocity * Time.deltaTime;
}
// Make all characters calculate the end position when decollided from all platfoms, and move there with interpolation
foreach (MyCharacterController character in characterControllers)
{
character.CalculateDecollisionFromOverlappingPlatforms();
character.rigidbody.MovePosition(character.calculatedEndPosition);
}
// Desimulate all platforms and Move with interpolation
foreach (MovingPlatform platform in movingPlatforms)
{
platform.transform.position = platform.positionAtStartOfFrame;
platform.rigidbody.MovePosition(platform.transform.position + (platform.velocity * Time.deltaTime));
}
}
}
The final result is that the platform smoothly pushes the character around, with no visible inter-penetration and all movement is perfectly interpolated.
How it became a problem in 2017.2
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