Best Practices for a 2d non-physics platformer

Options
Hello,

I have been throwing together a 2d platformer somewhat in the style of Super Crate Box (http://www.supercratebox.com/) - a non-physics-based platformer with two players in a single-screen room. I have been having some issue synchronizing players such that they are not jittery. I have tried a few techniques, all with some ups and downs, but none that I am currently sold on. What are the current best practices for a fast-paced 2d game using Photon Unity Networking? Are there any good examples to learn from?

The first of the two best methods that I have used are to read the updated position, read the current Network time, and then MoveTowards the true position, using the accumulated time since last updated divided by the current sync delay as the timestep.

The second method is to read the updated position and the updated velocity, where velocity was the delta position between the last two frames on the other players side. Using this I extrapolate the next position by multiplying the velocity times the delay and adding that to the read "final" position.

The first method above provides probably the most "accurate" movement - when I spin up two clients, there is very little delay between a player's movement and what happens on the other screen. The character also appears to move very closely to how they originally moved. The downside to this is that there is very prominant jittering.

The second method is the smoothest - there is still a little jittering, but not nearly as much as the above method. The biggest downside to this is ofcourse extrapolating next position in a fast-paced 2d game where players can stop abruptly or immediately jump causes some quirkiness with turning around, jumping and landing. The movement is also not super accurate - a player will often seem to leap through corners of platforms or skid midway through a floor for moments. They also rubber-band quite a bit when jumping into/onto a platform.

I am not sure that I can really apply much prediction to this game - players can jump at any moment or turn around. Also while not currently implemented - I want jump height to be a function of the time spent holding the jump button. So it seems like the only thing I could probably predict is the y velocity on the descent of a jump, unless I am being completely oblivious.

I have toyed around with sendRate and sendRateOnSerialize a bit, but without being too happy with any results. Increasing both of these transfers between smoothness and accurracy, but I don't think just adjusting these will give me the results I am looking for. I should also note that the PhotonViews on the players are only observing the component dealing with moving the entity and recording velocity. I am also defaulting to Unreliable sends as that seems to be mostly what I want, and changing that does not improve results.

Any help is greatly appreciated! Let me know if I can provide any information or code or anything!

Comments

  • Tobias
    Options
    Sorry for taking so long to respond at all. There was the holiday season and this is a really tricky question.
    I'll try to summarize a bit of the background, to enable you to experiment some more and I will give you my input on your case. I can't do much coding-wise at the moment though.

    Like you said: Syncing movement for a game where you can jump and instantly switch between directions is really hard.
    This "arcade" style movement suffers very obviously from lag. Especially when you put both clients next to each other.

    In best case, you would want to transfer input at the moment it happens.
    The default logic of PUN is not well suited for this case, as it has a fixed update frequency. With only 10 updates/sec some input might be delayed locally by 100ms. This is called local lag and it's even worse if the local lag is 0..100ms (randomly) and you don't know how much of that happened for each individual update.
    Tweaking the send-rate helps only so much to avoid this local lag and might cause sending a lot more than you want to.

    The synced time all clients in a room have could help but you need to keep in mind it's also not perfect. Each client tries to approximate the server's timestamp as best as it can but the result itself is +/-20ms inaccurate.


    I never did this myself but I would try one of those options:
    1) Do 20 updates/sec and send the movement state (moving left, jumping, etc) with the position. Interpolate and if the remote char is too far from the actual position, teleport or move faster. I assume the character's speed is fixed, so you don't need to send that. Send unreliable. You could send an update "serial" to detect if something got dropped, so you can even handle a skipped update in a special way.
    2) You could try to modify PUN into sending updates immediately when a (movement) key was pressed. You can use PhotonNetwork.RaiseEvent() to carry your movement info and PhotonNetwork.SendOutgoingCommands() right after calling that to send this event. Alternatively you can use a PhotonView.RPC(), which is more or less the same but related to a PhotonView.

    Keep in mind, you can cheat as much as needed to make your life easier. Players won't always sit next to each other and small differences in positions are not a problem as long as the rules for the game are consistent. The logic of each client just has to look correct to each player: As long as hitting the other inflicts correct damage, it does not matter so much if the players were at the correct pixel position.

    Hope that helps or gives you some new ideas or insight.
    All the best and let us know what you do in the end...