Networking is hard and time consuming. That’s been true for a long time, and still is. But, due to tools like Photon, it is easier than it has ever been to make a networked game. Having said that – Photon has pretty terrible documentation, so implementing networking can feel tough, even when it’s not. That’s where we come in. On our mission to democratize all game dev information, I thought it might be fun to give a rundown of how to do synchronization in PUN, and what options are available, to help demonstrate (with code samples) that it’s not that hard.
Lets dive in. First off – what is synchronization? To put it bluntly – synchronization is making sure different players see the same thing. While some updates will happen on different player’s devices, you want to make sure that everybody sees (more or less) the same thing, giving a seamless networked experience.
So – lets talk about how that happens, first theoretically, and then tangibly, in Photon Unity Networking (Pun for short). In the theoretical, how could we ensure everything is synchronised? The first option is to ensure all of our important information is sent, constantly, across the network. Then, all players will constantly know what’s going on. But, of course, the downside to that is you’re sending a potentially large amount of information, constantly.
Ok – so what if instead we just send information when it changes? When something important happens, we notify all the players that it has happened, and keep them up to date? Well – that makes sense sometimes, but has problems for things like networked clients who join late -> they might have missed some of these messages! In truth, a mix of these options (and other options that are… sort of hybrids) is how good networking works. So – lets see what these look like in PUN, and when we should use them
PUN Observable Components
In PUN, we have the idea of observable components. By default, you get a lot of these out of the box, such as things like the Photon Transform View, or the Photon Animation View. These are bundled components that automatically sync data over the network. But, you can also write your own!
By implementing the IPunObservable interface, you gain access to the OnPhotonSerializeView function. This lets you send data over the network as much as you want. Bear in mind, it comes with the same downsides as the theoretical constant sync we mentioned before. You don’t want to send too much data using this method – only the things that should be kept constantly up to date on all clients.
In the above image, you can see what we’re sending over the network. Most of it is fairly simple information, like the amount of time remaining in the game, or the amount of gold that the players collectively have. It’s small bits and bobs, nothing too major. But – what if we need to send something only once, or need to send something large?
Remote Procedure Calls
Remote Procedure Calls (or RPCs) is how PUN does its event or notification based networking. By setting the RPC attribute on a function, you become able to call it over the network very easily.
With the above example, we are spawning random customers at regular intervals. When they are spawned, we want to give them a random head and body. These are randomly allocated on the Master client (responsible for all the game logic processing), and then sent via RPC to each other client. That way, they all get the notification of what head and body each customer should have.
RPCs are a great way to send information that only needs to be sent once. The head and body of a customer isn’t likely to change, so we don’t need to be constantly sending it. Instead, we just send it once, and then don’t worry about it.
Or do we!? As mentioned before, this runs into problems when we have things like players who join a game late, or players who need to reconnect. How do we get around this?
Buffered RPCs
PUN has thought of this (sort of), and gives you the Buffered RPC. We’re actually using one of these in our above example. Because this is buffered, it is stored in a list, and each new player who joins is able to execute the RPC when they connect, thereby ensuring that new players are kept up to date with our RPC.
Of course, buffering an RPC takes up memory, which means you only want to do it for things that are important to remember. You definitely don’t want to buffer all your RPCs. It also has another issue – it’s not perfect for disconnection and reconnection.
Since PUN doesn’t run on a server, there is no central location to store all the buffered requests. This means that each player stores the requests that they have buffered. The downside to this is that if a player disconnects, all the RPC calls that they have buffered are lost. If our master client disconnects, then reconnects, they will no longer have a buffer of important information.
The above image is how we get around this. It’s not perfect, and there may even be a better option that I’m not familiar with, but this one works. When our master client (who controls all of our RPCs) disconnects, those RPCs are lost. So – our new master client (the player who becomes the new master), simply re-sends that information. As people disconnect and reconnect, as long as there is at least one player still in the game, there will always be somebody maintaining the “buffer” list.
So – there are some of the ways you can easily get synchronised using Photon Unity Networking. Hope it’s been helpful! We may dive into some other networking concepts in the future, so make sure you leave us a comment letting us know what you’d like to hear more about!
We hope you enjoyed reading this! Have a question or want to chat more about game development? Reach out to us!
Other places you can find us:
- Our other game development resources
- Join our Discord server
Pingback: Networking: 3 Photon PUN Top Tips | Dev Blog