Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice
  2. We've opened up a space to discuss, share feedback, and showcase everything related to the Unity Shader Graph! Come show us what you've made.
    Dismiss Notice

Authoritative all in one FPS network solution

Discussion in 'Connected Games' started by AurimasBlazulionis, Jan 26, 2017.

  1. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Hello everyone,
    Today I would like to share with you something I have been working on for quite a bit. This is an authoritative FPS controller with all sorts of features like prediction and client-side reconciliation. The system works pretty well now, good enough for my needs. There are many things to add, but the current state is great.
    The code is on GitHub, you can use it anyhow you like as long as it follows the MIT license. Any feedback would be appreciated.

    Youtube video:


    Features:
    • Client-side prediction.
    • Client-side reconciliation.
    • Interpolation.
    • Snapping to grid in order to suppress floating point nature.
    • Inputs and results storing to combat big latency changes.
    • Relatively low overhead.
    • Quake-like strafing. But most likely very incomparable.
    • Crouching.
    • Animations.
    • Toleration values, because the floating point errors.
    • Network data analyzer to compare the results between the server and the client.
    • First person and third person camera.
    • Gameplay recording to a file. Works with all objects, easily extensible.
    • AI targets (moves towards the point).
    • Sliding of steep surfaces.
    • Foot IK.
    • Ragdoll.
     
    Last edited: Feb 21, 2018
  2. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    New update. Added quake-like strafing. As well as cleaned up the code. The strafing part probably is nothing like in actual quake or valve games but makes it a little bit more fun to play around.
    My thoughts about the GC allocations were correct. I will probably write byte serializer next week to make the situation better.
     
  3. Tiny-Tree

    Tiny-Tree

    Joined:
    Dec 26, 2012
    Posts:
    1,247
    this is really great, any plan on doing a simple Third person version ?
     
  4. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Yes, I plan. It has been in the TODO list for ages and I think it is about time to make it happen.
     
    Tiny-Tree likes this.
  5. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    A new update to the repository. Now the speed is stored in world space coordinates. This is really important to do more advanced stuff. Also all the player settings are stored in a scriptable object. This might help with memory. Also, the camera now rotates in x axis.

    About the third person mode. I will do it later. First I will add a normal human model with animations, then crouching and other features. And finally third person code. This is because at this stage the code might change quite a lot and third person movement can break, even though it should be really easy to implement.
     
  6. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    Sounds great.. I wish Unity provided an updated fps controller with unet support, I'm sure they'll get around to in another 5years .. until then will check this out :D
     
  7. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    ok.. well the networking side works.. I'll check it out remote server to see how it actually players

    but right now the actual movement/controller default settings are damn awful.. and no amount of tweaking the default data could get it feeling better.. the biggest problem is deacceleration.. while it works in foward movement.. if you gain some speed, jump, 180, the controller doesn't deaccelerate as fast in reverse while you slide backwards.. so lots of slipping and sliding..if you increase the speed, so yeh like I said the default movement is pretty awful.. strafing left/right even without holding shift is like baby steps.. i dunno thats just not right.. even Rust's awful player controller isn't that bad :D

    I'll take more of a look at it some more later.. the nice thing is hey its a working unet fps controller with "features like prediction and client-side reconciliation" ... will see how those work in a real world test with 40-100ms+ ping etc.
     
  8. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    I know I will tweak it. Now I am trying to mimic what quake movement does, in the strafing part. Probably, the acceleration and deceleration should be much faster.
    After all, it is easily tweakable. Just make sure you keep everything inside the movement function and do not set external variables.
     
  9. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    I haven't played quake in a long time to remember what its strafing was like, personally only liked quake2+3 but I do think the current strafing doesn't feel good.

    If you ever played brutal doom, the movement controller for that is pretty nice :D its just missing some floor sliding ...its something I'd like anyway.
     
  10. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    New update. Added crouching and animations. I changed the default values as well. The controller should feel much better now. No side walking animations. If someone could link any free to use ones that would be great. The default unity animations are used.

    Next I will probably add third person movement. Later, I will work on foot IK placement.
     
  11. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    999
    If you're looking for optimization suggestions:
    CharacterController component, StartCoroutine(), using Lists, etc... are all things that potentially generate lots of garbage and/or have performance issues

    That means you could do the following:
    • Create your own CharacterController component using a kinematic rigidbody, CapsuleCastAllNonAlloc, OverlapCapsule, Physics,ComputePenetration, etc.... (this is a lot of work)
    • Handle delayed actions (coroutines) manually by storing start times in class variables and checking on Update()
    • Use pre-initialized arrays instead of Lists
     
    Last edited: Feb 11, 2017
  12. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Rigidbody will never work in this situation. Because you can not tell it to move separately from the main physics stuff. Even after a lot of work, it is simply not worth it, because it already runs good enough. And even then, most likely the character controller will work better.
    There is not much garbage collection now. All of it comes from network sending and recently, from event invoke. I will look into both to solve them.
    I will probably switch to pre-initialized arrays and I only do coroutines when simulate is defined which is purely for debugging.
    Anyways, thanks for suggestions.
     
  13. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    So it seems movement prediction becomes much worse after the commit where I moved the data from the player to scriptable objects. The good thing is that I will probably implement a feature which would allow to set what position difference the server would tolerate in order to prevent these issues on high latencies. Even if the current code is quite deterministic, due to floating point it will not be 100% accurate.
     
    Last edited: Feb 16, 2017
  14. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Here we go, a new update!

    No third person movement because I was working on tracking down the problems with my code. But the good news is that I wrote some tools for comparing client and server inputs and results. The analyzer can be accessed in Window\UNet Controller\Network Data Analyzer. Read the commit message for more info.

    So after fixing it I tested it with simulated latency (using some powerful linux tools) at 150ms latency with 30ms variation and 1.7% packet drop rate so I believe this was pretty much a real life bad case scenario. Got 1 serious prediction error (others were purely small floating point errors) over a minute of playing.

    Next update will be the third-person movement and I will also look into some issues happening with high rate movement (40+ Hz).
     
  15. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Just ran a test with the server running on my phone with 4G cellular and the client on my PC with additional 450ms of latency (20ms variation). Rock solid. At 40ms variation the connection got dropped, most likely because it exceeded the 500ms limit, but I am quite happy with the results.

    After 5 minutes of playing around with 3 clients total connected to my phone, got 13M data usage. I would say it can be improved a lot.
     
  16. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Again, a new update! Now I added sliding of steep slopes and finally - third person movement. There are other fixes and possibly other breakages. But so far, it is looking good!
     
  17. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    So there is some work on minimizing GC allocations. Currently it seems there is no way of actually removing all GC allocations. There is a NetworkWriter used inside UNet to write the message and you it still allocates even if you pass raw bytes. At this moment I made some progress in getting a raw byte array. The length of Results will be 69. And I also managed to reuse the same byte array over and over so no GC allocations occur. The only part is the Network Writer where GC allocations are inevitable. I am looking into making my own implementation of the part where NetworkWriter is used. Maybe even reusing the byte array.
     
    JosephHK and wahyuway like this.
  18. JosephHK

    JosephHK

    Joined:
    Jan 28, 2015
    Posts:
    35
    Thank you. I am really inspired.
    However, I am concerned about the case where sometime few packets sent to the server are congested, as a result the few inputs are processed in successive fixedupdate calls so that the corresponding character is moved extremely fast even in the server side client.
     
  19. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Basically why this happens is because in the last update, the server did not receive any information and in the next one it got 2 inputs. And this is only server side, not client side (well, the clients get the positions from the server). After removing all GC allocations I will work on fixing this. A side note: this is pretty much the way source engine multiplayer works. It moves once it gets inputs, not necessarily if they come in at the same time. As long as the inputs are not ahead of the server it is like that. My bigger priority is smoothness for the client and security than smoothness for others, or smoothness for all with no security.
     
    JosephHK likes this.
  20. JosephHK

    JosephHK

    Joined:
    Jan 28, 2015
    Posts:
    35
    Do you have any idea for implementing mechanisms that are related to animation heavily like vaulting or climbing?
     
  21. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    That is my biggest concern in the future. I have no Idea how I will calculate the top edge of walls and fences. But the hand placement will be done using IK for sure.
     
  22. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Once I have the edge of the wall, I will just make the controller stick to the wall and then climbing over the wall/fence will be a non-issue.
     
  23. JosephHK

    JosephHK

    Joined:
    Jan 28, 2015
    Posts:
    35

    Inspired by your work, I am trying to make my game to have server authoritative movement.
    Currently, my game is fully client authoritative, so to synchronize the vaulting and climbing, I just send the starting/ending position of the vaulting/climbing, and also synchronize the animation by sending the state hash and the normalized time, then the position of the character can be calculated between the starting and the ending positions using the animation normalized time.
    However, for the server authoritative movement, is there any way to synchronize the position and the animation seamlessly?
     
  24. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Nice movement! I am not sure how your system works, but it sure is possible. As you stated that you pass start, end positions and time. That is possible. I would just need to add move vectors to the results, a float for animation time and that is it. Root motion could be enabled, but the position of the player sure has to go back to it's normal place once the animations finish, and maybe the actual controller has to move to the end position. As for your particular case, the hardest part would probably be the missed climbs - when on client side it climbs, but it does not on the server side. At the reconciliation part it would stop the climbing and it would look weird. This is sure hard to explain in a few sentences of words while the implementation includes hundreds of lines of code.
     
    JosephHK likes this.
  25. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    btw if you walk the character along the nearest ramp, ie walk on the ground plane up alongside the nearest ramp edge, when you get to where the ramp and floor meet, the character seems to get shot up into the air, minor bug, also the jump animation as before seems to get played twice or halted if you have a high jump/fall distance.
     
  26. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Actually noticed that. And I have a theory why this happens. Will probably have to take the minimum between the moved speed and the actual speed calculated by how much the character moved.
     
  27. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Again, a new update! Now GC allocations from the UNet side are gone. The only part seems to be Unity Events I use to send an update to the Animation Manager. Also, Controller.Move, but only if deep profiling is enabled. I also fixed the bug where the player gets launched in the air so that is not an issue anymore.
     
    nxrighthere and JosephHK like this.
  28. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Pushed a little update which removes use of UnityEvents and instead I opted for C# delegates.

    @Player7 I was not able to reproduce the jump animation getting repeated.
     
  29. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    I'll check it out again
     
  30. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    yeh so the jump stuff seems to be fixed.. I thought I might have triggered the jump animation repeating once while jumping off a ramp.. but further tests it seems to be working properly now.

    Also if you change your mouse lock to use a different key instead of Escape, it works properly in editor aswel.

    if (Input.GetKeyUp (KeyCode.Escape) && Cursor.lockState == CursorLockMode.None) {
    Cursor.lockState = CursorLockMode.Locked;
    Cursor.visible = true;
    Debug.Log(Cursor.lockState + "-" + Cursor.visible);
    }

    lockState would always be None with that code for some reason..

    I was getting so sick the mouse going outside of the screen while looking in editor.. strange because the mouselock code I use my other game does use escape to toggle curse lockstate and it works fine in editor aswel but I guess its different.
     
    Last edited: Mar 5, 2017
  31. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Thanks for the info, I will probably change it.
     
  32. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    ah the quote the had KeyCode.P which works.. but yeh if you had it at Escape the debug log would show lockstate as None everytime.
     
  33. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Ok, I am back from a hackathon and one of the things I took away was that the code of the UNet controller is not perfect. There seem to be some problems with reconciliation. The player would just teleport back and forward on the client side. So I tried with client trust. Some scripting errors had to be fixed. But the good news is that I made it that other scripts would be able to teleport the player and the player would start from the network start position. Work incoming during this and next week.
     
  34. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    cool sounds good
     
  35. SiliconDroid

    SiliconDroid

    Joined:
    Feb 20, 2017
    Posts:
    296
    Great work so far! Can use a Log2 raycast scan to get top edge within 1mm without slowly scanning up whole wall. So first check is Ym up where Y is max wall height in scene. Then next check Y/2, then Y/4, Y/8... etc, changing direction of ray walk depending on hit result.
     
  36. jessejarvis

    jessejarvis

    Joined:
    Aug 9, 2013
    Posts:
    273
    Yay! I love it! Exactly what I was looking for!

    Edit: Actually, my system loads the character's start position once the scene loads, how do I load the character's position here? player.transform.position doesn't work because it's server authoritive.
     
    Last edited: Mar 30, 2017
  37. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Well, currently I happened not to work on this for over 2 weeks. I might push a little update that will do everything with a single function call (done already, not pushed yet). Basically you have to edit serverResults and tempResults position values and then your position will get updated.
     
  38. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,299
    Well I was looking forward to that update, hoping you carry it on, its not like unity are bothering to make a better fps controller example with unet and authoritative server end, movement smoothing etc to help us out.
     
  39. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    I simply happened to get stuck on other things. Just so you know, I am making it for my game and keeping it open source. So if I want to work on my game, I have to work on this too. Which is the case.
     
  40. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Ok, so now I am commited to working on this for at least next 3 days since I have to finish my game during the next week. There is an update coming out in a few minutes which basically adds SetPosition and SetRotation functions which can be used to set the position and rotation of the player. A perfect example of it is in the start function when everything is initialized, these functions are called to set the results to the NetworkStartPosition.

    My goals for the following days is to create some kind of gameplay recording system (very similar to what valve games have) which would let the users to create amazing content but also allow to analyze all the problems that occur with the code.
     
    wobes likes this.
  41. wobes

    wobes

    Joined:
    Mar 9, 2013
    Posts:
    604
    Hey, May I ask you. How many clients can hold server with this system?
     
  42. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    It should hold as many as you want, but sure there is a limit in UNet itself.
     
    wobes likes this.
  43. wobes

    wobes

    Joined:
    Mar 9, 2013
    Posts:
    604
    The server with 100 players. Is it ok for this authoritative movement?
     
  44. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    You could only try it to find out. I believe it *could* work. If other movement systems work, this one should work as well as well.

    Also, keep in mind that currently there seem to be some problems with reconciliation that I noticed during a hackathon. I will have to investigate that during this week.
     
    wobes likes this.
  45. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    OK so, I made some changes while adding functionality needed for my game. The current changes are AI targets and ability to control the player outside the controller. Also, the default input interface has been changed a bit to make third person movement better.
     
  46. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    As always, more information is in the commit message and I forgot to edit the README.md again.
     
    DungDajHjep likes this.
  47. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    So I updated the system adding gameplay recording. It is pretty straightforward. You can try to play back this file, just put it in the persistentDataPath (~/.config/unity3d/... in Linux, .../AppPata/LocalLow/... in windows).
     
  48. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Soooo... just found out that the system is unusable with more than 2 players. Going to fix it now. It was due to GC allocations and stuff fixing. Now the first connected client connects all connected clients.

    EDIT:
    Here is a hotfix. Add it in the beginning of OnSendInputs function:
    Code (CSharp):
    1. if (msg.conn != connectionToClient)
    2.      return;
    I will do more stuff and then push the update.
     
    Last edited: Apr 13, 2017
  49. JosephHK

    JosephHK

    Joined:
    Jan 28, 2015
    Posts:
    35
    In my opinion, you should serialize also the netId and recover the corresponding Controller in OnSendInputs function. Besides, OnSendInputs should better be a static function.

    Code (CSharp):
    1. inputWriter.StartMessage(inputMessage);
    2. inputWriter.Write(netId);
    3. inpSend.inp = inp;
    4. inputWriter.Write(inpSend);
    5. inputWriter.FinishMessage();
    Code (CSharp):
    1. static void OnSendInputs (NetworkMessage msg) {
    2.          
    3.             GameObject go = NetworkServer.FindLocalObject(msg.reader.ReadNetworkId());
    4.             Controller controller = go.GetComponent<Controller>();
    5.  
    6.             Inputs inp = msg.ReadMessage<InputSend> ().inp;
    7.  
    8.             if (!controller.isLocalPlayer) {
    9.  
    10.                 if (controller.clientInputs.Count > controller.data.clientInputsBuffer)
    11.                     controller.clientInputs.RemoveAt (0);
    12.  
    13.                 if (!controller.ClientInputsContainTimestamp (inp.timestamp))
    14.                     controller.clientInputs.Add (inp);
    15.  
    16.                 controller.currentTFixedUpdates += controller.sendUpdates;
    17.  
    18.                 if (controller.data.debug && controller.lastTick + 1 != inp.timestamp && controller.lastTick != -1) {
    19.                     Debug.Log ("Missing tick " + controller.lastTick + 1);
    20.                 }
    21.                 controller.lastTick = inp.timestamp;
    22.             }
    23.         }
     
  50. AurimasBlazulionis

    AurimasBlazulionis

    Joined:
    Aug 13, 2013
    Posts:
    209
    Well it sounds like a good idea, but I do not like the GetComponent part getting called hundreds or thousands of times per second. Will think about it.
    Also, there are many private variables that are better not to be touched outside the controller.
     
    wobes likes this.