Practical questions about Integrating physics on a standalone server.

Started by
5 comments, last by Christian Tucker 2 years, 7 months ago

This is not a “How do I integrate server-side physics?" post. This post contains questions about practically working with server-side physics and ways to make my life easier.


I've been using Unity for awhile now and as such I have made use of the Unity PhysX* implementation to create server-authoritative gameplay for several different projects, however these all are projects that have run within the context of the Unity Game Engine and have used their Object system & Tools to construct the physics world.

I'm sure a lot of you are aware that Unity is quite heavy even when running in “headless” mode which doesn't even perform most of the optimizations that you would expect it to, so I'm looking to start writing a multithreaded standalone server for my next project as I'm targeting a larger concurrent userbase than any of my other projects, instead of having one running within the context of Unity and all of the overhead associated with it.

Here's my concerns and the things that I'm not really sure how to solve, and the methods that I've mentally drafted to solve these problems. I'm looking for advice regarding if these solutions are a good route to pursue or if they've already been tried and better solutions have been developed by the other developers out there.

How do I get the Game World into my server?
This is probably my largest concern. I've considered writing some form of script that goes through the Unity hierarchy and exports collider data using the default PhysX implementations. Then reconstructing the colliders on the server using some form of generated configuration file, but I'm still not sure how I want to go about this, or what the best methods are.

This is very likely going to mean I can't use things like the built-in terrain editor, as well. Just struggling to wrap my head around the best way to approach it.



Advertisement

Probably the easiest way to do exactly what you want is, as you say, to create a tool in Unity which exports the collider data in a form you can read elsewhere. The primitive colliders are easy enough to re-construct and the mesh colliders have a sharedMesh property so you can extract all the faces. Just write the colliders for each game object out and on the server you can read them in and rebuild them with PhysX. The hard part is traversing the Unity scene and prefab hierarchy, assigning some sort of unique ID to each object, and making it all correspond on the server. In fact, this is a problem you'll face when using a non-Unity server anyway - you need some way of representing the scene regardless of physics concerns. This might involve manually adding components to your scene objects to give them a unique ID, or perhaps you can come from the other direction and just attempt to parse the scene and prefab files directly.

In your situation, where it seems like you are committed to a Unity-based client, I would probably just focus on making the Unity headless server run in a performant way. You can switch many of the systems off via SetPlayerLoop and other inefficiencies can usually be worked around.

I'm curious as to how you made a server-authoritative physics simulation using Unity and PhysX in the past however - usually a server-authoritative simulation will mean the clients have to sometimes snap objects back into position to match the server, which is slow, or re-step through physics to replay mispredicted inputs, which is very hard to do given how Unity wraps PhysX.

The last project I was on attempted some compromises by sometimes applying forces to correct a misprediction over time, instead of snapping objects back. This performs a lot better than moving the Rigidbody and/or Transform directly, but means you have a lot of small discrepancies at the best of times and you can get unstable oscillations at the worst of times.

@Kylotan Thanks for the response, as I'm sure you've already seen this or have had it shoved down your throat at some point I'm not going to preach the basics, but I'll drop it here just incase you haven't seen it. There's a great post on server authoritative design in general by Gabriel Gambetta you can find here.

It sounds like your question is pertaining more towards the implementation in Unity however, so let me try to answer any questions you may have. PhysX is generally deterministic however you can expect floating-point precision errors to occur, some will argue that a floating-point implementation makes a physics engine not deterministic, I'm not here to say that it does or does not break the simulation, but that it can easily be worked around using reconciliation in most cases. The implementation that I use works great for 3rd-person games where you're controlling a single character, however if you're trying to build a RTS that needs a deterministic multi-unit solution I recommend finding a fixed-point physics engine that suits your needs.

I'm assuming that when you say “snapping back” you're talking about reconciliation. This is the process that I chose to go with and there were not any noticeable performance problems even on low-end clients running in the browser over OpenGL. This is the process of storing simulated & input states on the client and looking them up when a response comes from the server, if there a mismatch between the clients predicted simulation and the servers simulation, the client is reset back to the predicted simulation and then the Physics calculations are ran manually. To do this you need to disable the Physics loop either in the editor or in code using Physics.autoSimulation = false or Physics2D if you're working with Box2D instead of PhysX. You can then call the Physics simulation manually using Physics.Simulate. When doing this it's important to iterate back through the collection of stored input states over the last n frames to ensure that the proper client input is used for each frame in the simulation.

I started writing an article on this on medium awhile back and never got around to finishing the series, but a very basic & psuedo implementation can be found on that post here. Keep in mind that this implementation is an example doesn't make use of Physics and has it's own set of issues and can be better optimized. It is not a one-size fits all solution and it's not the one that I used in my project. You can also find a simple video I uploaded awhile back that I used for showing some people the difference in states between client prediction, server position, and reconciliated position. This was just using a very basic implementation at the time I was writing for a tutorial series that I never finished, but it was using Physics. You can see that the local client (no orb over head) is moving smoothly, while the server position is choppy. This was done using some smoothing on the reconciliated position, since you can execute multiple physics steps before a frame is rendered, the client never sees the player teleport back and catch back up, and in 99.9% of cases unless there was a large precision error you'll never notice it even when it is making corrections.

There's also other things I didn't mention such as using Physics.SyncTransforms to ensure that your Triggers and Colliders are working as expected during the reconciliation process. You don't need to worry about using Rigidbody.MovePosition over just setting the transform.position when getting a state back from the server. You also don't want to use rigidbody.position because that position isn't updated until after the next Physics simulation and you need that immediately available for the next Simulate call for reconciliation.

Hopefully I answered most of your questions, since you didn't really list any. If not I'll try to answer you in more detail when I get some time. Feel free to add me on Discord if you'd like to talk more in-depth about it. Tucker#9309
I should add that the same implementation works in both Physics and Physics2D by just replacing the class name for static functions. I use reconciliation/prediction in my current project which is a 2D Platformer and it works well across different operating systems & computers. This is using Unity as a server & client and is the same project I'm looking to move away from Unity in the near future.

I should also state that the reason I'm looking to move away from Unity headless is purely the overhead. As our world is getting closer to housing thousands of entities per instance and being stress tested with around 1500 CCU of Bots with some NPCs sprinkled about for testing interest management Unity is really getting to become a bit heavy, both on memory and CPU. There's a lot of things that go in within Unity that we don't need, but also creating Unity instances allocates a lot more resources than your traditional managed application. We're working really hard to keep our costs down as an indie-game and have made a lot of optimizations & design decisions around horizontal scalability and using cheap hardware to deliver a great networking experience for a low cost. This includes server meshing, which allows users to transfer from one server to another seamlessly without even realizing it while traversing our game world. This isn't a new concept, and a lot of our design decisions have been made in backend design for years, but not implemented in the gaming space for some reason, however having Unity tacked on to our server at any given time increases the amount of resources we need to allocate and call me paranoid but I don't like having black-box code on my servers for projects that are intended to handle a large amount of users.

I guess I wasn't clear - I already implemented and shipped a physics-based game but we didn't make it fully server-authoritative due to various problems. What we found was that both the step of “the client is reset back” and “the Physics calculations are ran manually” was just far too expensive when handling a lot of characters and a relatively complex scene including moving obstacles, walls and other geometry, etc. We tried all combinations of how to move and reposition the objects and I think moving the transform directly was the slowest, while other methods defer the update as you say. Additionally there would be instability where the replayed simulation would end up moving things a significant distance from where the server thought they should be, meaning the next update would have a lot of correction to do. And the inevitable fun when a correction would try to put a remotely-controlled character into exactly the same position as our local one, meaning the physics would ‘pop’ them apart at high speed at the next update! (Improved by reducing the deinterpenetration velocity, but still…)

TL;DR - the usual rollback-and-replay systems that were designed for single characters in a shooter situation with relatively static geometry just didn't and couldn't work for us in a highly dynamic physics world where lots of things could affect each other, and where Unity's PhysX wrapping was making things slower.

Back to your real question - we honestly didn't find the overheads of Unity servers too extreme but that's not to say they aren't there. So if you want to get the world geometry into your custom application then you have the usual choices:

  • Make an editor tool to export from within the Unity editor, iterating over scenes/prefabs and exporting in a format of your choice
  • Make a standalone tool to export by reading Unity files - mostly just a YAML parser but it's much harder to debug. Advantage is that you don't need to be running the Unity editor.
  • Export to a common format from your 3D digital content creation tool - this only works well if you aren't using the Unity scene editor and prefabs to handle your objects

None of these are particularly complex to implement. It's mostly just a case of iterating or recursing through the elements, reading the collider values, and exporting them - and what you're exporting is pretty trivial data. But the details depend on your preferred way to do this.

@Kylotan

Ah alright, I completely understand what you mean now. I don't have any issue regarding performance even with quite a large amount of physics-driven actors, but depending on your world and what you're simulating your mileage may vary.

To address what you said about your type of game, client-prediction is only really feasible in a mostly-static environment. Whenever something changes with the terrain or the physics that the client is simulating that was caused by another player or even the server, the client isn't aware of that and thus can't properly simulate it causing for corrections. I'm not really sure where in your implementation this comes into play though.

And the inevitable fun when a correction would try to put a remotely-controlled character into exactly the same position as our local one, meaning the physics would ‘pop’ them apart at high speed at the next update! (Improved by reducing the deinterpenetration velocity, but still…

Unless there's a reason for your clients to be running simulations for the other players, their simulations should be disabled on the local client. You should almost never run corrections for a remote player, or even run simulation for them if all you're doing is client-prediction server-reconciliation. The idea is to keep the local player ahead of the server game-state, and by simulating other players you try to move them from being behind the server state to being either in-front of or even with the server state using extrapolation. The flip-side to this is attempting a form of lockstep implementation, where all clients are simulating the entirety of the game state, but PhysX isn't capable of this due to the lack of floating-point precision and games will become massively out of sync. Lockstep should only really be used in a few types of games though, and isn't very scalable and is extremely performance intensive, but also doesn't require corrections since the physics behind it are deterministic.

If your physics world is completely dynamic and the worldspace in which the local player is operating is adjusted there will always be a reconciliation, but none of the remote players (unless in the same space) should ever be effected under a normal implementation, and the server should never stack them together causing a pop-effect since it's the authoritative voice on physics and knows that two solid objects cannot occupy the same space.

If I had to assume your performance issues came from needing to rollback the entire state of the world (Since it's changing) when running reconciliation, causing colliders to need to be rebuilt several times within a single frame. It's not really something that can't be done due to limitation of the PhysX wrapper, just more-so extremely expensive. There's also the possibility that you were simulating items other than just the player, for example Physical Projectiles, Remote Players, NPCs, whatever which would tack on to the performance requirements and wouldn't really offer any benefit to the implementation.

-------

Thanks for your answers to my main question though ?

This topic is closed to new replies.

Advertisement