Announcement

Collapse
No announcement yet.

Networked "Physics-Vehicle Movement Component": existing examples or implementation hints?

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    #31
    Yeah my implementation that's pasted above is the same for other player input, so it works with other players - the call before the RPC just works out if it's a local player or not and if so, send the input, and update local input from the server (which fails to work since server overrides local player input and it defeats the point altogether lol).

    The major problem to start with seems to be the ability to sync latency times between the server and clients... pain in the ****. I'm going to have a look at UT's code when I get back to this and see how they determine ping.

    @elfprince13 - I think that's right, and the same is also true to UT code (at least for projectiles). They create a dummy projectile on the clients rather than replying on replicated movement too.
    Last edited by TheJamsh; 07-30-2015, 02:07 PM.

    Comment


      #32
      Okay update time: I'm finally getting somewhere! I won't directly post code for now because it's definitely going to have to change and in no way useable or finished... but here's some idea of how to get it working.

      The first step is to sync time-stamps between client and server. In order to do this I use the Player Controller, and send an RPC to the server with nothing in it, all it does is ask the server to call a Client function. It logs the local time (in milliseconds) that it send the packet, and then logs the time when it receives it back (aka, runs the client function). From that, you can work out exactly what the Servers' time is, on the Client. I've tied it into Ping, so as Ping is updated (every 0.5 seconds or so via unreliable RPC), it bounces the timestamp RPC again to keep it accurate, but only if the ping is different to what it was previously. You can find the calculation for working out the offset practically anywhere online, but here's how you get the different times.

      Code:
      // Local Time on Client
      int32 ANTPlayerController::GetLocalTime()
      {
      	return FMath::RoundToInt(AccumulativeDeltaTime * 1000.0f);
      }
      
      // Estimated time on server
      int32 ANTPlayerController::GetNetworkTime()
      {
      	return GetLocalTime() + T_ServerOffsetTime;
      }
      Now the tricky part, you create a Buffer of moves on the client which is essentially it's 'Move History' (which comprises of input and the 'state' of the object at that time). The key part is that you actually store that history with the ESTIMATED server Timestamp. The idea is that you're literally 'predicting' what the Server is going to say your move was at time X. At the same time, you send the input to the server so that it can process it.

      So, the Client processes the input locally and the Server processes it as soon as it receives it. The server then sends the move back to the client with the LOCAL time-stamp for that player controller (aka, actual server time). When the client receives the move, they go through the move buffer and pass over any moves that are stamped before that time (no point erasing them, just move on to the next index). Now they receive the move in the past but check in the history to see if the move was correct at that point.

      So yeah.. that's where I'm at. The tricky part now is that the Client receives the corrected move at (Ping / 2) time late, so I now need to work out how to offset it's current position, based on previous move data. Currently I'm just 'snapping' to the corrected move so the Client updates for a while until it gets the move, then 'snaps' to an old position, which is of course not right. However, early results are promising (the red outlines are the move history buffer).

      This is simulating PktLag of 250Ms (So total 500ms Ping), and only sending packets 5 times a second. Bandwidth useage can definitely be optimized here, but it's showing promising results. Let's be honest, if you're playing on a server with 500ms ping and 95% packet loss, you're probably not going to stick around for long anyway...



      EDIT: Oh and one more thing, I have 'Replicates Movement' checked still because I want to update other clients and proxies as well, but I ignore it if I'm the local client. This is kind of lame because it means I'm sending data that I don't need to to one of the clients, and regularly, so I'll change the way it's done eventually. For now though:

      Code:
      void ANTPawn::OnRep_ReplicatedMovement()
      {
      	if (GetNetMode() == NM_Client && IsLocallyControlled())
      	{
      		return;
      	}
      	else
      	{
      		Super::OnRep_ReplicatedMovement();
      	}
      }
      Last edited by TheJamsh; 08-09-2015, 06:36 AM.

      Comment


        #33
        Hey guys, apologies for the silence, I haven't had much time to work on this in the past month or so. I did get a chance to get into it yesterday though, so here's a brief progress update:

        I am extending the WheeledVehicle and WheeledVehicleMovementComponent classes (since I'm focused on wheeled vehicles at the moment). I'm able to simulate physics on the client and server (and simulated proxies) by passing the inputs from the client to the server, and then the results from the server to all simulated proxies. I'm also able to determine if the server disagrees with the move it has received from the client and identify the move that needs adjustment on the client.

        At the moment I'm stuck finding a nice way to replay physics states. I have the moves that need to be replayed, but I haven't sorted out the replay logic yet. I attempted to just use a delta but it causes a feedback cycle (a correction causes a bigger delta causes a bigger delta, etc). I then tried simulating physics ticks but they refuse to occur faster than global delta time, so passing the stored move's delta time in and attempting to tick N times didn't work. I will probably spend some more time today trying to get the delta approach to work, although I would definitely prefer simulating real physics ticks so the engine could reconcile collisions appropriately.

        @TheJamsh nice work! Please let me know if you solve the replay problem, I am perplexed at the moment and am feeling like I'm going to have to dig deeper.

        @HateDread I've been using the default physics actor replication technique so far, which means that when you have a bad ping your input experience is terrible. The actual update to the physics state for each player happens in AActor::PostNetReceivePhysicState() (in ActorReplication.cpp). I overrode and disabled that for the autonomous proxy role so I could handle the replay, but I also had to create a new replicated movement struct with a timestamp so I could do the move syncing. In any case, there's a handy function for syncing the simulated proxies against the server in PrimitiveComponentPhysics.cpp called ConditionalApplyRigidBodyState that I use to smooth the simulated proxy movement. I'm a bit scrambled at the moment but I hope that answers the question you had a few posts back. Also after I get this implemented WE SHOULD PLAY!
        Work in Progress: King of Kalimpong
        piinecone.com | youtube | @p11necone

        Comment


          #34
          I think what I need to do in order to solve the replay problem, is fix my Physics simulation time-step and I'd be able to successfully replay through all the moves. The problem is actually fixing that timestep is easier said than done, and really I'd probably want to completely decouple it from the main thread which probably isn't completely possible. I almost reached the point of writing my own physics system while still latching into PhysX collisions.

          I'm gonna be away for the rest of the week probably so won't get back to this until the weekend... but when I do I'm going to look into replaying the moves. I kept trying to find a way to do the correction without actually replaying moves at all, but it's **** near impossible unless the input hasn't changed at all since the packet was first sent.

          Comment


            #35
            Very cool update One thought comes to mind in response to this:
            Originally posted by TheJamsh View Post
            Currently I'm just 'snapping' to the corrected move so the Client updates for a while until it gets the move, then 'snaps' to an old position, which is of course not right. However, early results are promising (the red outlines are the move history buffer).
            One thing you can do that ought to work pretty well is snap the corrected derivative quantities (i.e. velocity), but lerp the corrected positional updates. This smooths the rendering jumps, but keeps the handling characteristics server-accurate.

            piinecone: ConditionalApplyRigidBodyState is a sweet find

            Comment


              #36
              I have made this point to TheJamsh via twitter, but for you guys, here are said tweets:

              @_TheJamsh Idea: for client-side input resimulation, copy PhysX code and roll forward w/ inputs manually. Same code = similar results.

              @_TheJamsh You won't get collision with other things, tho. Even better; each client maintains a sep. PhysX scene for self + nearby obj.

              @_TheJamsh You resimulate using your local client scene, then set the real object on client to the resulting pos and let PhysX act normally.
              Basically, try making an FPhysScene on each client, and only contain within it that client's player + the nearest x number of objects. You can then simulate forwards and backwards with whatever timestep you need (try your best to match server, i.e. if server 'should' tick physics at 60fps, you do the same locally and hope the server wasn't slowed down too much at any point). When you get the results from resimulating, you set the 'real' position in the full-scale scene on the client, or better yet, you interpolate between your calculated result and the result of the server, as per normal client-server reconciliation.

              Why have two scenes on each client? Well, it's important to maintain a full-scale scene on each client so they can simulate collision of projectiles and FX, etc, otherwise explosions and the like will occur a bit weirdly - you want the clients being able to decide when a fake projectile they make on their end should go 'bang', rather than waiting for the server to tell them where and when, since that will most likely not be in-time.

              Give it a try!
              Last edited by HateDread; 08-20-2015, 09:59 PM.

              Comment


                #37
                Originally posted by HateDread View Post
                I have made this point to TheJamsh via twitter, but for you guys, here are said tweets:



                Basically, try making an FPhysScene on each client, and only contain within it that client's player + the nearest x number of objects. You can then simulate forwards and backwards with whatever timestep you need (try your best to match server, i.e. if server 'should' tick physics at 60fps, you do the same locally and hope the server wasn't slowed down too much at any point). When you get the results from resimulating, you set the 'real' position in the full-scale scene on the client, or better yet, you interpolate between your calculated result and the result of the server, as per normal client-server reconciliation.

                Why have two scenes on each client? Well, it's important to maintain a full-scale scene on each client so they can simulate collision of projectiles and FX, etc, otherwise explosions and the like will occur a bit weirdly - you want the clients being able to decide when a fake projectile they make on their end should go 'bang', rather than waiting for the server to tell them where and when, since that will most likely not be in-time.

                Give it a try!
                When you say "(...) interpolate between your calculated result and the result of the server (...)", by calculated result do you mean the result from the resimulation?

                And by "result of the server" do you mean the current position sent by the server? Assuming that calculated result is in fact the result from the resimulation, why interpolate between a result calculated with inputs yet to be acknowledged by the server and the current result of server, which was calculated with an older input?

                Comment

                Working...
                X