Dev Story: Goggles – World of Vaporia
This guest post was written by Sebastian Erben, co-founder of indie studio Bit2Good.
Bit2Good produces handcrafted games independently since 2011. Their most current project is ‘Goggles – World of Vaporia’, a vehicle combat shooter set in a Steampunk styled world called Vaporia.
Goggles was initially developed as a single player experience and was then transformed into a 4 player co-op online game with the help of Photon Unity Networking.
Sebastian shares some bits of what worked for them and what didn’t during that transformation process.
Get Goggles – World of Vaporia on Steam.
Out of the comfort zone
When we began development of Goggles in 2012 it was neither a 4 player coop game, nor was it called Goggles (you can read more on the general development of Goggles here).
Online multiplayer wasn’t a topic that we had gathered any experience before either so for us there was no natural approach to the multiplayer side of game development. Thus, we made some mistakes and learned some lessons the hard way and I’d like to share some of that knowledge with you, hoping that at least some of them will be helpful ;).
Lesson No. 1: Multiplayer as a feature
Since our schedule and budget was tight, we had to work on multiple things at the same time and that’s the reason Goggles’ multiplayer was treated more like a tacked-on feature than an integral part of the gameplay experience. This gave us many headaches later on, since all the levels were first developed to work in singleplayer. The problem with this is that not every member of the team became acquainted with multiplayer and many parts of the game had to be rewritten at a certain point of development in order to work in multiplayer.
So if you want to create a multiplayer game, make sure you set up a multiplayer centric structure first and then handle the singleplayer as a one player multiplayer match, possibly with special rules. If you already have a singleplayer game and want to add multiplayer to it, be prepared to change huge parts of the codebase. It’s pretty much inevitable.
Lesson No. 2: If everything else fails – RPC
When using Photon Unity Network (or PUN), the first tutorials will do a pretty decent job teaching you the basics of network multiplayer. They are easy to understand because mostly the OnPhotonSerializeView
callback which you can use to keep values synchronized.
However, if your game is not from the ground up build as a multiplayer game, OnPhotonSerializeView
can actually cause some trouble. Imagine your enemies as state machines (which they probably are) that will act differently depending on a bunch of values. It’s now tempting to just keep all values in sync all the time but this will likely yield bad results. The best thing would probably be to conditionally sync via OnPhotonSerializeView
and PhotonNetwork.RaiseEvent
.
But if your development process is similar to ours, that might lead to a complete rewrite of the enemy logic which, if on a tight budget, is problematic. What we do instead is to just sync position values every once in a while and apart from that use RPCs to only sync what needs to be synced. RPCs are reliable which can be drawback if used excessively but in our case enemies only change their behaviour once in a while. The great thing about RPCs is the amount of control about what to synchronize and what to do with those values.
Lesson No. 3: Know your RPCs & cull
When starting out with RPCs, you’ll first get to know them as some sort of “networked functions” and you’ll probably start out by using PhotonTargets.All
. Don’t stop there!
RPCs can be made with either a PhotonTargets
value or a PhotonPlayer
value and both are super useful because they allow us to implement some very easy network culling (if you want to know more about network culling, take a look at this tutorial)!
PhotonTargets
is best used when you want to leverage PhotonNetwork’s buffer or make sure everyone (sender as well as receivers) gets a RPC from the Photon server.
PhotonPlayer
is used best when you only want certain clients to receive RPCs. A real world example from our Goggles is the synchronization of enemies. As I wrote before, many elements of Goggles came into being as a singleplayer function and some of them weren’t rewritten because of the time it would have taken to do so. The administration of enemies is one of those elements so the master client always knows every enemy and is responsible for spawning and despawning them. Enemies synchronize themselves with all clients and it is here where we were able to gain some drastic improvements in network performance by using PhotonPlayer
:
// We iterate through our players which are represented in a custom class that holds
// references to transform and photon player.
// attacking is a private boolean variable telling us if the enemy is currently attacking.
// myself is a private variable referencing the enemies own transform.
// SyncThreshold is a public float variable.
if (Vector3.Distance(_player.Trans.position, myself.position) < SyncThreshold){
if (!_player.PhotonPlayer.isLocal){
myPhoton.RPC("UpdateAttackState", _player.PhotonPlayer, attacking);
}
}
This piece of code essentially looks which client is near enough and then synchronizes the enemy’s attack state via UpdateAttackState
which is a custom RPC. It doesn’t seem like much, but when 4 players are spreading out (which is the worst case since each player will trigger enemies per portal that they’re approaching) this bit of code saves us up to 66% of bandwith compared to a simple PhotonTargets.All
approach.
Lesson No. 4: Know your callbacks
PUN gives you a whole bunch of useful callbacks that are rarely explored in tutorials because it’s hard to tailor a meaningful example for each one of them. I suggest you take a look at the Photon.PunBehaviour
class reference and tinker with the callbacks that seem useful to you.
This is actually an advice that you can expand to Unity itself: being aware of the existance of a specific callback can you save hours, maybe even days of work. It could also lead to ideas for new features that you otherwise would never have considered to implement.
Takeaway
Most of this will be an old hat if you’re a developer that’s seasoned in the art of network. But if you’re just starting out or in the middle of development of your first network game I have a feeling this could provide some help for you; I certainly would have liked some of this advice ;).
If you have any questions or feedback or you just want to get in touch you can find me on Twitter @thebossti or send me an email at erben.sebastian@bit2good.com.