Announcement

Collapse
No announcement yet.

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

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

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

    I'm working on a multiplayer project that will include vehicle types such as hovercrafts + spaceships. I would like to be able to enable client-side prediction in the same vein as used by the existing CharacterMovementComponent, but it looks like this really hasn't been done elsewhere in the engine for non-character anything, even for the stock wheeled-vehicle class. Flying vehicle types seem like they should actually be a bit easier than the character movement (and certainly easier than wheeled vehicles), so two questions here, in decreasing order of priority:
    1. I'm wondering if there is a prototypical example for this floating around (even if just for 6DOF, without additional physics constraints), or if I'm stuck rolling my own? It feels like a common enough problem that there ought to be, but Googling hasn't turned up anything.
    2. If I'm stuck rolling my own, it seems like implementing INetworkPredictionInterface (and maybe RVOAvoidance) is the place to start in a PawnMovementController subclass. Am I missing any of the major moving pieces (pun intended) here?

  • replied
    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?

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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!

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    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.

    Leave a comment:


  • replied
    Sorry I've been a bit quiet in my own thread - I have some research deadlines (plus wedding + honeymoon) that have been eating up my time. I'm glad to see this discussion going though.

    TheJamsh - my recollection is that it will be helpful for you to look into CMC for another reason, which is I think they have some code to resolve the replication/prediction fighting. Something along the lines of replication being applied to a proxy rather than the local physics object. (Unless I'm completely forgetting which codebase I saw that in, which is possible, since it's been a while since I looked at this first hand).

    Leave a comment:


  • replied
    Originally posted by TheJamsh View Post
    Well according to their suggestion/implementation as I understand it, if a packet is delayed by say 250 ms - at that point in their example the car could be far enough away from the correct position that moving it over too few frames would break the illusion, and doing it over too many frames would mean that you'll still be blending to the old position when you get the next packet, so you'll constantly be slightly out of sync if you don't time the blending between packet updates. It could become that you're actually never in the right position for any of those frames because you're spending all your time blending to the next packet.

    The idea is that they work out how long they have to blend to the correct position over time so that it's a) smooth and b) accurate. Divide 250ms / 60 frames and you get 15 frames to perform the update, so you blend to the position over the next 15 frames, at which point you should have the next packet of data. Problem is that if your framerate isn't pretty consistent and your latency isn't either, you'll never really get the 'time I have to blend' correct.

    Really this isn't a problem since if you're shipping something, it nearly always makes sense to lock the framerate anyway (which BTW drives me insane when people complain about capping at 60 in some games). Most people probably have a stable enough connection that packet latency will stay fairly constant for a game session anyway too. There will definitely be some slight discrepancies though.

    Also, correct on the input. I got confused looking at Rama's implementation and this one at the same time
    Hmm, I haven't gotten that far in, although I feel like our games wouldn't necessarily need it - you can just use the ol' Entity Interpolation method (my goal), for example, and smoothly interpolate (even using splines with velocities as part of the control points). Cars would definitely be harder, since it's more difficult to seem realistic if they 'correct' from one position to another, particularly horizontally - my space ships and your hover tanks kinda move that way already.

    The more I look into methods like the above, and considerations such as fixed timestep physics, having a synced network time for the interpolation buffer, and so on, the more I realize we're gonna need to get in there in the engine and make some changes. First, though, how are you guys all handling the movement of other players? I know you haven't done it over the internet yet, TheJamsh, but you have, piinecone, and I'd be interested in how.

    Tough stuff!

    Leave a comment:


  • replied
    Well according to their suggestion/implementation as I understand it, if a packet is delayed by say 250 ms - at that point in their example the car could be far enough away from the correct position that moving it over too few frames would break the illusion, and doing it over too many frames would mean that you'll still be blending to the old position when you get the next packet, so you'll constantly be slightly out of sync if you don't time the blending between packet updates. It could become that you're actually never in the right position for any of those frames because you're spending all your time blending to the next packet.

    The idea is that they work out how long they have to blend to the correct position over time so that it's a) smooth and b) accurate. Divide 250ms / 60 frames and you get 15 frames to perform the update, so you blend to the position over the next 15 frames, at which point you should have the next packet of data. Problem is that if your framerate isn't pretty consistent and your latency isn't either, you'll never really get the 'time I have to blend' correct.

    Really this isn't a problem since if you're shipping something, it nearly always makes sense to lock the framerate anyway (which BTW drives me insane when people complain about capping at 60 in some games). Most people probably have a stable enough connection that packet latency will stay fairly constant for a game session anyway too. There will definitely be some slight discrepancies though.

    Also, correct on the input. I got confused looking at Rama's implementation and this one at the same time

    Leave a comment:


  • replied
    Originally posted by TheJamsh View Post
    Yeah I just read over the code and unfortunately it does mean that it's client-authoritative - which is fine for most people but for anything AAA or with a focus on Multiplayer it's probably going to invite cheating. Also means it's probably not suitable for me...

    Thanks for that link from nVidia, that's actually pretty useful! Never really considered that SetActorPosition() would cause it to be re-inserted into the broadphase collision check but that makes perfect sense really. It also looks as if they're saying that using AngularVelocity to drive the updates. I guess this is because it's more likely to change between updates than the other factors. One slight caveat of nVidia's notes there is that it requires the latency of your packets (easy enough to get in Unreal mind, UT already does that in it's PlayerController) - and you need to maintain your FPS while those corrections are taking place too which is probably the biggest hit. This does mean locking your frame rate most likely unless you can get the system to work with variable FPS. Mind you, if FPS is varying by that much at the time anyway I doubt the slight movement or jitter of your craft or pawn is going to be the most noticeable artefact.

    In my somewhat unique case, I HAVE to send the input from the keyboard anyway because it's used to drive other effects like Particles (engine thrusters etc) and audio for the engine for other players. That's fine though since that can be made an unreliable RPC, and I'm hoping that I can massively quantize that data and pack it into a tiny cost for replication. That overhead is probably minimal if anything.

    This is certainly an interesting problem...
    I'm not sure where you got the "One slight caveat of nVidia's notes there is that it requires the latency of your packets (easy enough to get in Unreal mind, UT already does that in it's PlayerController) - and you need to maintain your FPS while those corrections are taking place" parts, though. Why is it that your FPS needs to be constant?

    And you should be sending your input anyway - that's how you drive the physics on the server from the client! You send the inputs whilst simulating them yourself, and the server simulates them once it receives them.

    Leave a comment:


  • replied
    Yeah I just read over the code and unfortunately it does mean that it's client-authoritative - which is fine for most people but for anything AAA or with a focus on Multiplayer it's probably going to invite cheating. Also means it's probably not suitable for me...

    Thanks for that link from nVidia, that's actually pretty useful! Never really considered that SetActorPosition() would cause it to be re-inserted into the broadphase collision check but that makes perfect sense really. It also looks as if they're saying that using AngularVelocity to drive the updates. I guess this is because it's more likely to change between updates than the other factors. One slight caveat of nVidia's notes there is that it requires the latency of your packets (easy enough to get in Unreal mind, UT already does that in it's PlayerController) - and you need to maintain your FPS while those corrections are taking place too which is probably the biggest hit. This does mean locking your frame rate most likely unless you can get the system to work with variable FPS. Mind you, if FPS is varying by that much at the time anyway I doubt the slight movement or jitter of your craft or pawn is going to be the most noticeable artefact.

    In my somewhat unique case, I HAVE to send the input from the keyboard anyway because it's used to drive other effects like Particles (engine thrusters etc) and audio for the engine for other players. That's fine though since that can be made an unreliable RPC, and I'm hoping that I can massively quantize that data and pack it into a tiny cost for replication. That overhead is probably minimal if anything.

    This is certainly an interesting problem...

    Leave a comment:


  • replied
    Originally posted by TheJamsh View Post
    You've probably seen this already btw, but Rama has put up a post on how he handled replicated Physics movement. I'm going to snoop over his code tonight and see how it all works.

    https://forums.unrealengine.com/show...lating-Physics!
    Yeah I've seen that before. Isn't it client-authoritative? If it's just talking about the physics of others in the world around you... that ain't so hard Even good ol' buffer interpolation will get you there. The trick is server-authoritative physics without having the local client feel like ****.

    EDIT: He's not wrong with the applying impulses part, though. Jump down to 'Network Physics' and you'll see that Nvidia recommends that approach over setting a position. Just gotta weigh the cost of that against a simple interpolation.
    Last edited by HateDread; 07-21-2015, 06:50 AM.

    Leave a comment:


  • replied
    You've probably seen this already btw, but Rama has put up a post on how he handled replicated Physics movement. I'm going to snoop over his code tonight and see how it all works.

    https://forums.unrealengine.com/show...lating-Physics!

    Leave a comment:

Working...
X