Unity Netcode 100% Server Authoritative with Client Prediction and Reconciliation

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
keeping everything in sync between your server and each client is a challenging problem especially with rigid bodies and this is the main focus of this video I know this is an advanced topic one of my goals is that all of my subscribers reach the point where they can take on large code bases like mega cities and not feel overwhelmed if you fall into that category don't worry a little bit at a time and you'll get there let's start by learning a few new things today first we'll spend five minutes configuring netcode because this is our first day having multiple real players in our card game hit that like button and let's get into it [Music] now before we can add multiple players to our game here we first need to import a few more dependencies one being text mesh Pro and I imported all the extras too because I like some of the extra fonts there are and we also need netcode for game objects of course and you can see here I'm using version 1.4 so once those are added into your project the next thing to do is we need to add a network manager I'm just going to go back over to the scene here and create a new empty game object I'll just call it network manager and that requires a network manager component so I add that we don't need to fiddle around with the settings too much here but you do have to pick a transport so let's just go with unity transport and make sure that the address is set to your local host and then we can drag our player prefab in here which we've called player cart now player cart doesn't have a network object on it or anything yet but we'll get to that next let's disable our cart spawner because we only want to work with real players during this episode and next episode and then we'll bring it all together after that for this next part I'm going to bring in a package that I've already put into the repository it's just a few little scripts and helpers and one prefab that you can use in any netcode project and they're very simple go over each of them really quickly and you can code them from scratch if you want or you can go up to the repository and grab this package and import it into your project I'm just going to drag it in quickly here so as you can see it just has a UI prefab and a UI script to go along with that that'll start up a host or a client and it also has a client Network transform so we don't have to write that for every single project let's have a quick look at this network start UI prefab that I've built if you look in the game view you can see it's just two buttons but let's take a quicker look at the prefab itself very simple just a few text mesh Pro buttons and a background if we jump into the code for this you can see there's just references to the buttons the buttons have listeners when they're clicked they'll call the network manager Singleton and either start the game as a host or a client and then hide the UI now let's jump over to the other script that was imported with the package and that's the client Network transform this is an override of a normal Network transform which syncs transforms over a netcode network but what we've got here is we're going to return on his server authoritative we're going to return false in most cases because we want the client to be controlling all the movement without a round trip to the server so it's nice and snappy and then we're also going to implement reconciliation so that the server is 100 authoritative so now we'll have to add a few more components to our player prefab in order to get it working for multiplayer so that all the players have their own camera and their own networked objects so over here on the player prefab first of all I'm going to disable the skid mark Handler for now then I'm going to add a network object component you can leave all the default settings then we'll add the client Network transform that we just pulled in we don't really need to sync the scale but I'll leave all the other ones for now and then I'm also going to add a networked rigid body which is helpful just for syncing things between the client and the server and actually seems to work pretty well after this we need to deal with cameras so we don't need a virtual camera in the scene just for one player we need a virtual camera for every player so let's just delete that from the scene and then let's also remove the cinemachine brain from our main camera and turn off the audio listener we only want the audio listener on for each player's individual camera let's jump into the prefab and make those changes so we can add a new camera here onto the player prefab we'll disable the audio listener on all player prefabs by default and we'll add a cinemachine brain to this one and then we'll add a virtual camera with exactly the same settings that we had before which was 5 and minus 30. now we just need it to Target the actual player so it's dragging the references there and then we're going to need to configure these like turn the audio listener on and off and we also want to jack up the priority on this camera so that for this particular player this the virtual camera attached to their cart is the one that's rendering for them so we need references to both those things let's jump over to code so in our cart controller script let's add two new serialized Fields one for the cinemachine virtual camera and another one for the audio listener and that way when this card spawns into the game we can make some changes to those things so let's change our start method to be a public override void on network spawn and in here we can handle things of that nature now we have to make one very important change to our cart controller before we can start using this and that is we need to inherit from a network Behavior which is in turn inherits from monobehavior so back down here in on network spawn first of all let's make sure that we're the owner if we're not the owner then we want to turn off that audio listener and we want to set the camera priority to zero but if we are the owner let's set it to a higher priority like 100 and let's turn on the audio listener now all these components um having to do with the rigid bodies and the axles and getting finding components let's move that out of here let's put it right into the awake method that way we're 100 sure that the rigid body component is going to be found and we'll be able to start using it okay back in unity we can assign references to those serialized fields so drag cinemachine in there drag the camera with the audio listener in there and that's pretty much it let's have a quick look at build settings because it's time to try it out if we jump into our project settings quickly uh under player there's a section here called resolution and presentation and under there you can make some settings about how you want your output to be so let's change it from full screen window just to a windowed build and let's check uh running background and let's also take off resizable window that way you can kind of fool around with it while you've got Unity open and play with two players now I'm just going to save my scene in Project okay I'm just going to hit Ctrl B and run a build but you can come up to the menu and do it that way too if you need to I'll kick that off and that should open our game so we can test this out okay here's our build I'm going to run this one as the host looks like we could adjust that spawning point but we're going to work on the Spawner in a future episode today is just about multiplayer so I'm going to park this guy over here and then I'll jump back over to Unity and run the editor version as the client and let's have a look so we've got two players in the world now that looks all right let's pull over here and bring up the other window so we can just see how it's performing so yeah definitely they're driving independent the cameras are independent everything's great now we can turn to the topic of client prediction and Reconciliation so first let's talk about the why before we start digging into the how not all the machines that connect to your game are created equal some players will have lag sending their inputs to the game and some people will try and cheat by sending incorrect data for these reasons you want to have 100 server Authority most of the time but to keep the snappy movement you need for a racing game or an FPS for example you need the client to be able to predict their own movements as well now somehow we have to synchronize the information coming from the client with the reality happening on the server so if these things get out of sync we have to apply a correction and that's called reconciliation where we rewind to the last correct point and replay the inputs again on the client so to do this we need a standardized measure of time between everyone in the game that is totally frame weight independent since the speed at which the players experience the game will be different for everyone to do that we're going to implement a network timer class that simply counts off ticks then we'll take snapshots on the client at every tick for the input values as well as the state of their cart the position rotation velocity on the server will also capture the state every tick and we'll keep a queue of the player's inputs as we receive them the server will simulate the physics and send the correct State back to the client that's when a Reconciliation will happen if necessary make sense let's start creating our data structures which won't take long and then we'll Implement and test that it works by adding our own teleport cheat in our implementation we need to ensure that we have a synchronized way of ticking both the server and the client and that's where this network dimer class will come in so Min time between ticks represents the minimum time in seconds that should pass between each tick on the server and the frequency of these texts determines the server's tick rate in the Constructor we're defining this as the reciprocal of the server tick rate so if the server tick rate is 60 frames per second the minimum time between ticks would be about 16.67 milliseconds or so it keeps the server updates at a frequency equivalent to 60 frames per second now the should tick method we'll check this to see that at least the minimum time has passed since the last tick and that will ensure a consistent tick rate for the server here the time is incremented by each frame's Delta time and then check if it's greater than or equal to the minimum time between ticks if it is the timer gets reduced by the minimum time between ticks and a new tick is appended and the function returns true if this sounds really confusing just think of it like each tick is a little time stamp we're going to put on every input and every state that we're going to pass back and forth between the client and the server just synchronize everything so the next data structure we need is a generic type circular buffer now circular buffer is just going to be an array which Loops back upon itself once it reaches the end so by having an abstraction here we don't have to worry about doing all these little modulus operators in our code now subscribers to this channel will know that I Stray away from primitive Obsession and you'll find as you become more proficient at coding whenever you see value stored in a primitive object and there's Behavior associated with it such as adding or getting items from it you should think about putting that into some kind of structure whether it's a class or a struct or otherwise so we're going to use this to store all of our input data in all of our States because we don't need to store them forever we just need you know enough to synchronize things having a circular buffer is an extremely efficient way to do that okay next we're going to Define two structs that implement the I network serializable interface the first one is going to be for input and we're going to send in our tick and a vector 2 about our move Vector for now and we'll implement the missing members and copilot will fill this in we're just going to serialize those values per transport State payload very similar what's the state of our cart a position rotation velocity angular velocity so again we'll implement the missing numbers there's our two data structures that we're going to pass back and forth between the client and the server so now all we have to do is just implement this in our cart controller let's have a quick look at the flow again of how this is going to work first of all the client is going to move the cart just like they do right now but we're going to send that input afterwards to the server on the server side we're going to simulate the physics of the cart movement and then send that back to the client when the client receives that data we'll decide if we need to reconcile or not so let's move a bit further down and we'll declare all of these variables we've got our timer we've got the FPS that we expect the timer to be running at on the server we've got the size we want to Define for all of our different circular buffers so for the client we want to keep track of what state we're at at every tick we want to keep track of the input used at every tick and we want to know which state we processed last that came in from the server so we'll get the one that came in from the server and we'll also store which one we successfully reconciled to we'll call that last process state then server specific stuff the server needs to keep track of all of its simulated States and it also needs a queue to process all the inputs as they come in now we can scroll down to our awake method and we can initialize each of those variables so our timer the client State buffer the client input buffer and the server side ones as well now in the update method we're going to update our timer but in fixed update we're no longer going to run all of our move code instead we're going to extract that into a move method that just accepts the move Vector that way we can run all the code in the move method whether we're on the server simulating it or we're on the client and actually running it so then we'll come back up to the fixed update method let's say if we're not the owner just get out of here bailout otherwise as long as our timer is telling us that we should be producing ticks we'll handle the client tick and handle the server tick for every tick that it pumps out until it's done and that'll be the conclusion of our fixed update so let's implement this more or less in the flow of the diagram so first of all every time we want to move we should actually take the state of the movement and store it into a payload so I'm going to wrap the move method with process movement method that just returns that payload it'll run the move and then get all the information about our state and put it into a state payload and pass it back now we can Implement our handle client tick if we're not the client return next let's get our current tick from the timer now based on the tick we can figure out where the buffer index is in our client input buffer to store this information so assemble the payload put it into the buffer and then we want to send that information to the server so that it knows what we've been doing so to do that we'll create a server RPC in a minute and we'll send it the input payload now we actually want to process this input on the client side and then we'll take that state payload and save it on the client side so that we can handle reconciliation at the end so I'm just going to add a commented out method here for that we're going to do the reconciliation part very last let's quickly fill out the server RPC actually copilot already knows what to do I'm just going to tab complete this so on the server side when the server receives the input payload it's just going to enqueue it into its input queue So speaking of which let's handle the server tick so on the server tick what we want to do is as long as there's input in the queue that has been sent to us from the client we're going to run that so here we'll start a while loop that will just run until the queue is empty on every iteration let's DQ and input payload we'll figure out a buffer index based off of The Tick that's in that payload and then we can get a state payload by simulating the physics from what we expect to have happened from that input payload so we'll write a method for that in a second once it's done we can actually store that state in the server State buffer so now if our buffer index moved at all we need to send whatever's been processed back to the client so we'll do that with the client RPC we'll grab out the state wherever the index is at send it over there so for our client RPC let's make sure we actually are the owner of this if we're not bailout and then we can set the last server State and equal to whatever the state payload was that we just passed over there now to simulate the physics we're going to accept the input payload that we want to simulate for so to simulate physics first we're going to change the simulation mode to be script now we can run our move method based on the input payload we can step the simulation ahead one fixed Delta time then we'll change the simulation mode back now we can just return a state payload of whatever the current state is all right let's jump back into unity and see how this affects our game now keep in mind the number of ticks coming out of our timer is much greater than any fixed Delta time update or any regular Delta time so Watch What Happens here the cart moves extremely fast so it just Zips down the track turning is out of control it's just way way too fast so let's jump back into code when we're handling our grounded movement we're actually lurping a force onto our rigid body's velocity to speed things along and we have that in a little section here called acceleration logic so instead of just regulating this by time dot Delta time we want to regulate it by the minimum tick time which we have stored in our timer so instead what we'll do is get a lerp fraction here and we'll just split it down and that way every time the tick goes off we're just incrementing by that fractional amount and not the entire Delta time so let's go back to Unity refresh the assets and try it out at this speed so this is much more reasonable but the Turning is still quite strong and that's because we haven't regulated it at all and it's turned up to quite a high amount so I'm just going to stop here let's come over to our player cart prefab now we could handle this a little bit with code but we can also just reduce the Turning strength here so I'll turn it down to about half of what it was and that should do us for right now okay so in just a couple minutes of work to do left here and that's to implement our reconciliation so I'm going to uncomment this method and let's start writing it down here we'll make a few little helpers for this too the first one being let's create just a method to abstract whether or not we should reconcile or not because we could just bail out of here if there's nothing to do if there is we need a couple variables and primarily among them we need to know which state are we actually going to rewind backwards in time to so we'll get a buffer index so we can handle this um if there's not enough information that is the buffer index that falls really close to zero then there's not enough data to really reconcile with so we can also bail out at that point now a special condition is what if we are the host of the game in host server rpcs execute immediately so there's no latency between the client and the server so what we can do there is just get the most recent state that was actually in the server State buffer otherwise we'll use the last server state that was passed to the client now we can calculate the distance if the distance you know is within a certain range which we haven't defined yet and just jump up to the top here and let's make this a float and we'll just say it's say 10. so what we care about is whether or not the two states have a mismatch of a distance that's greater than that threshold and if that's the case we need to reconcile the state and all the states from that point up to where we are now so we'll make that in another method let's come up to the top and we'll Define our should reconcile method there's really two reasons we would want to reconcile so I'm going to put them into their own variables just for readability the first one is is this a brand new server state that we use like other than the default and the default for a value object is just generally all of its value is equal to zero so it's never been initialized or the other condition would be is this state different than the last process State or the last process State doesn't exist never never been processed so if so then we're going to actually reconcile the state let's come down here and we'll start declaring that method so when we're reconciling a state we want to say we're going to reset our position to be exactly what it was at the state that we're going back to so let's set all those values first and now if the rewind state is the last state that we were received from the server then this is where we're going to stop we've done our duty we're going to get out of here and typically that would mean that we are the host so let's take that rewind State and put it into our client State buffer as if we had actually performed that state in the beginning and now we're just going to go through all the way all the inputs from that tick to right now our timer dot current tick and we are going to replay all the actions with our process movement method and we'll be able to put a new state into our buffer and we should be totally caught up with whatever the server said we should be now it's possible that there are still deviations and if there are the next reconciliation would make up for that as you develop this kind of system into much more complicated multiplayer scenarios you're not going to be trying to call server rpcs every frame that would be crazy you want to send them in bigger packages and you know reduce Network traffic and employ some compression and lots of other things this is just probably the most Bare Bones example of this that uh we need for this card game now as I was talking there I realized that uh copilot filled in something incorrect here we're not trying to find the distance between our current position and the rewind State we want to find our state that was in our client State buffer at the same index position as that rewind state so fix that up okay so this is all great but how can we really see that it's working in the in the unity editor well I think what I'm going to do is come up here and right where we initialize the threshold variable I'm just going to add a header and a few more serialized fields and what we can do is just add a few cubes into our scene that'll ride along with the cart and one will represent where the client thinks the card should be and the other one can represent where the server thinks the cart should be so we can update those positions when we're handling the server tick and when we're handling the client tick and then I'll just come back into Unity here and in the player prefab I'll just create two two new game objects here they don't need colliders that'll mess up our cart but uh they can just be regular cubes uh maybe a little bigger than this let's make them maybe three I'm moving them up in code but just so we can see them now I'll move them here and I made a few materials so red will be the server and green will be the client let's drag those references into the new fields that I made now to make sure this goes off I'm going to introduce a cheat so in the update method if the q key is pressed that will move the transform of the client forward by 20 which is past our threshold and then down in the right before we're going to reconcile if we've decided it's time to reconcile let's break and pause the editor so we can see exactly where those cubes are and what happens when we Step Ahead all right so let's jump back into Unity refresh the assets and then hit play so I'll just move up ahead a little bit onto the track here and pause and let's focus in on the cart and have a look so you can see the client and the server at this point are just a little bit off from each other but nothing to really worry about if I go back and now I hit the q key it's going to pause the game for me and let's go have a look now you can see they're pretty much exactly 20 apart if I step ahead now the reconciliation by the time this break went off already happened and you might have been able to see it in the video the cart got snapped ahead and back uh and then just the way the code is set up right now the cubes don't get updated until the next round but uh you can clearly see that they're the discrepancy in why it corrected itself so I'm going to turn the debugging off and go back into here now I'm going to hit it as I'm driving so you can see I'm I'm fast forwarding myself into the future by 2 by 20 and uh here if I can get back on the road again uh I'll come back out here onto the track and then I'll start hitting it and forward and try it a few in Reverse so you can kind of see it's it's so fast it's only one frame so potentially if your Racers were trying to cheat and they moved themselves up by 20 or 100 or whatever teleported to somewhere else in your game they're not supposed to be then you can correct them in this manner so this is all working pretty good next week's video will be on the same topic but more specific to clients that are not the host we'll be discussing the topic of extrapolation which is a form of lag compensation and we'll be making a few other changes to the project don't forget to subscribe to the channel if this kind of content appeals to you and hit the like button if you haven't yet I hope you have fun implementing this netcode stuff in your own project and I'll see you in the comments below
Info
Channel: git-amend
Views: 12,195
Rating: undefined out of 5
Keywords: unity tutorial, unity arcade game, unity game tutorial, unity, game development, game dev, game development unity, programming, c#, software development, learn to code, learn programming, unity tutorials, unity devlog, indiedev, gamedev, unity3d, unity multiplayer kart, unity netcode for gameobjects tutorial, unity netcode kart tutorial, unity multiplayer kart tutorial, server authoritative movement unity, client prediction unity
Id: -lGsuCEWkM0
Channel Id: undefined
Length: 26min 17sec (1577 seconds)
Published: Sun Aug 27 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.