Unity Networking for Fun and Profit

Getting Connected

Unity networking utilizes the Client-Server networking paradigm. This means that there needs to be a single server, which clients connect to. The server handles the routing of network messages to clients. A server can be a remote machine that only handles the messaging, but for a game like Duet, which is going to have a very small number of connected clients, it’s much more likely that one of the player’s devices will act as the server. Creating a server is a relatively simple affair, with the server instance calling Network.InitializeServer. Clients then just use Network.Connect to connect to that server. There is a bit of complexity regarding Network Address Translation (NAT), and accessing remote machines, but dealing with those issues is a bit beyond the scope of this document.

Connecting the server and client(s) initially does very little. In theory, connected servers and clients could be on completely different scenes, with totally different game states. This may seem strange initially, but allows for flexibility and performance that would be impossible if everything was kept in perfect sync across all peers (clients and server). For example, Project Gorgon (an in-development Unity-based MMORPG) uses several “headless” (without a graphical component) Unity applications to handle NPC interactions. Even though the client and the NPC driver are two completely different Unity applications, they are able to share information with each other. If we want to provide a completely synchronized experience with multiple identical clients, then we need to provide a structure that handles that. Fortunately, that isn’t terribly difficult.

Network Views

When peers are connected, only game objects that have Network Views attached to them can transmit information across the network. Network Views are built-in Unity components, and have a few different operation modes that allow for situation-specific performance tuning. There are three pieces of information that are crucial to utilizing Network Views: the owner GUID, the State Synchronization Mode, and the Observed Component.

The owner GUID is the least visible of the three properties discussed above, primarily because it is initialized by Unity, and cannot easily be changed. Whichever peer creates the Network View is set as the owner. This is important because only the owner can write changes to the state synchronization stream. Other peers can perform local changes, but Unity won’t send those changes to other connected peers.

For example, Client A owns their player object, and can send position changes to the other peers. Client B owns their player object which punches Client A, sending them backwards. The change in position seen on Client B’s instance won’t be seen by any of the other peers. For the force to actually take effect, Client B needs to communicate to Client A that its player was punched, and have Client A react to that change. This doesn’t sound ideal, but it actually allows a technique called Dead Reckoning, where Client B show’s Client A’s reaction to B’s action before A has actually sent a reaction message. For more information, the Valve wiki has an excellent article on interpolation, prediction, and compensation.

State Synchronization

Each Network View (there can be multiple Network Views attached to a single GameObject) has an Observed Component and a Synchronization Mode. The Observed Component is the component whose data will be automatically sent through the network by Unity’s networking. Transforms are commonly used, but any type component can be observed. Since only one component can be observed per Network View, if you want to observe multiple components of an object, you’ll need to utilize multiple Network Views. It may be possible to create a single Component that is an aggregate of all of the components you’d like to sync, but I haven’t investigated that possibility.

The Network View’s Synchronization Mode can be either “Off”, “ReliableDeltaCompressed”, or “Unreliable”. Turning the Synchronization Mode Off means that the Observed Component will not be sent over the network, and can be left null. This can seem like a wasted option, but it is the way of using the game object solely as an endpoint for Remote Procedure Calls (RPCs) without sending useless information over the network. Surprisingly, both ReliableDeltaCompressed and Unreliable Synchronization Modes utilize the UDP protocol, which is inherently unreliable. Unity Networking has it’s own network layer that utilizes ACKS and NACKS to make sure that anything marked as ReliableDeltaCompressed is, in fact, reliably streamed. ReliableDeltaCompressed is much slower than the Unreliable Synchronization, but it is guaranteed to have all updates seen by all peers, and that all of the updates are seen in the correct order.

The Unreliable setting, unsurprisingly, makes none of these promises. When using Unreliable Synchronization, messages can be lost or arrive out of order. This isn’t as useless as it sounds. When the component being synchronized is something that is constantly changing, such as a player’s position, there are going to be lots of updates being pushed through the network. If a small percentage of them are lost in the cold void of the internet, it isn’t a very big problem. Any lost messages are likely to be rapidly made outdated by newer ones that do make it through to the peers. Unreliable Synchronization is much faster than ReliableDeltaCompressed, which makes it ideal for data that will be constantly changing.

It’s worth noting again that with all types of Synchronization, only the changes made by the owner peer will be reflected across the network. Even if the server wishes to make changes, if it doesn’t own the object, it cannot. Therefore, if you want the server to be the authority on game state (which is a fairly standard requirement), the server needs to be the owner of all important objects, and have the players that “control” them send requests for state changes.

Observing a Script

Along with observing basic unity Components like Transforms and RigidBodies, a NetworkView can be given a custom MonoBehavior as its Observed Component. When a message comes in (or a request to send is made), the MonoBehavior’s OnSerializeNetworkView method is called. The BitStream parameter can have data pushed to or received by it using the stream’s Serialize function. Determining whether or not the Component is an owner is a simple matter of reading the stream’s isWriting property. When setting up the functions that read and write serialized data, it is important that the order data is serialized and deserialized is identical, otherwise the right data will end up in the wrong place. Only basic types (int, float, etc) can be serialized, along with Quaternions, Vector3s, and two types relating to NetworkPlayers. Custom objects, even if they are serializable, can’t be send through this function, nor can variable-size objects like arrays. In theory, this limitation could be circumvented by sending the length of the array, and then the contents.
Like the built-in synchronization for unity Components, the order and reliability of OnSerializeNetworkView calls depends on the Synchronization Mode of the Network View. In Unreliable mode, the data is sent/received approximately 15 times per second, though that can be changed by changing the Network.sendRate parameter. Rather than send a certain number of times per second, when Synchronization is in ReliableDataCompressed mode, changes are only sent/received when unity detects that a the component has changed in a way that warrants a data update. Since there is more overhead in this mode, it makes sense that the updates should strive to contain more data, but keep the number of transmissions lower.

Since the custom Component is still being Synchronized via the NetworkView, it holds to the restriction that only the owner can transmit data, and all non-owner peers can merely read the stream. Fortunately, there is a way for peers to send messages to each other

RPC Calls

Remote Procedure Calls, or RPCs, follow a convention that should feel very familiar to a Unity Developer. The if a GameObject has a NetworkView attached, it can call the RPC function, and provide a function name, a broadcast type, and a list of parameters. Then, all synchronized Network Views will receive a function call with that same name and list of parameters. The receiver’s function does need to be marked with an RPC attribute (“@RPC” for Javascript, “[RPC]” for C#), so the unity compiler knows that it is a network-exposed function. RPC function names must be unique throughout the scene’s scripts, so Unity knows which types of GameObjects can receive the RPC. In this way, it is very similar to using Messages, except instead of sending up or down the hierarchy, the messages are sent across the network. Similar to State Synchronization, RPCs can only take basic types, Quaternions, Vector3s, and NetworkedPlayer data. However, RPCs are at a disadvantage, since they can’t make use of the array workaround discussed above. The benefit to RPCs is that these functions are called on all peers that meet the qualifications of the broadcast type specified in the RPC call. Generally, and RPC will only be sent to a single other peer (such as a client messaging the server), or to all connected peers (broadcasting a change). Also, RPCs can be buffered, which means that they are stored at the server level. When a new client connects, all buffered RPCs are sent to that new client, which makes them useful for storing persistent state information.

Unfortunately, the Unity documentation is relatively light when it comes to discussing the performance of RPCs in real-time games. A note is made that the number of parameters of an RPC shouldn’t be overwhelming, which seems to imply that they are expected to be used for fast, frequent updates. However, RPC calls are guaranteed to be received in the same order that they are sent, which can introduce latency if a particular message is misplaced. Since they can, in theory, be called at any time by any component attached to a NetworkView, it would seem that they fill a space between the slower and reliable ReliableDataCompressed and Unreliable modes of State Synchronization. Their ability to affect other peer’s game objects, though, makes them invaluable. This doesn’t expose any additional risk of hijacked peers, though, since the owners of those objects receive the RPC, and can decide if it is, in fact, a message worth listening to.

My Final Argument for Games as Art

So I’ve been having an alarming number of nightmares recently. My nightmares don’t tend to be of the obvious pain-of-death variety; more of the slow, horrifying realization type. Last night’s was particularly crushing, and I would like to share it with you:

A different place, a different time. I lived in a house, with my wife, in a world overrun with vicious predators, who sought nothing more than to rend us limb from limb. For reasons not known to me, our house was not safe at night. So every night was spent running through the wilderness, not staying in one place for very long, leaving our scent with small groups of prey so the predators would consume them and not us. Every day was spent resting in the house; each day one of us would say to the other, “When we rest, we rest together,” and she would make a bottle for the baby. Every night, we’d run.

After a few nights, I realized there were no other humans.

And one day, weeks later, she was making the bottle, quietly sobbing, saying to me, “When we rest, we rest together,” and I realized there was no baby either.

 

Not exactly how I wanted to wake up, and it certainly kept me from sleeping for about the rest of the night. So I lay in bed, thinking about the dream, thinking about how I could communicate to someone just how crushing that realization was. There are obviously many ways that story could be communicated; I could write it, as I just did. I could turn it into a proper short story, with characters; I could illustrate it as a comic; I could do a short film. But all of these are viewing forms — even with a first person perspective, if you came to the realization, it wouldn’t be yours — you would be watching someone else come to the realization. You may feel for them, you may empathize, but it still isn’t your realization.

I realized the only way to fully communicate my experience to someone else would be to turn it into a game.

The reason, of course, is that there is a very specific set of criteria and stimuli you need to experience in order to grasp the full weight of the situation:

  • You need to be you, but as someone else. This strange dissociation isn’t a particularly difficult concept; we do it all the time in dreams, and we do it all the time in games.
  • You need to not know everything about yourself, but know enough to function in your new world. This strange not-amnesia isn’t really that weird; again, we do it all the time in dreams, and we do it all the time in games.
  • You need to be able to take in information at your own pace, information needs to be available to you at only the moments you look for them, and gaps need to be present to allow you draw your own conclusions. Movies try to do this, but they can only show you information; as clever as a director may be, he can’t truly allow you to discover information. But we do it all the time in dreams, and we certainly do it in games.

The realization that there was no baby, and hadn’t been for some time, only came at the moment I saw her crying, because every instance before that, after she made the bottle, we would sleep, and when we ran, she ran behind me. This allowed the assumption that she carried and cared for the baby. But when I had that realization, the rest of the dream came into the focus — I had never seen the baby, I had never heard the baby. The only evidence that the baby had ever existed was in my wife’s actions.

These sort of cognitions, realizations, and epiphanies are only possible in a medium where you are given a certain amount of author-provided information and a certain amount of autonomy of discovery. And so I discovered this truth about games:

Games can cause the participant to have author-guided realizations.

and even more importantly…

Games can cause the participant to have author-guided realizations that the participant owns.

I humbly submit that no other art form could as effectively communicate my dream, as I experienced it. And before you tell me it wouldn’t be a game because there are no goals, the goals are obvious: avoid getting killed by predators, for as many days as possible.

A game can be the strongest medium with which to express a dream. If that doesn’t qualify it as art, then you clearly don’t know how to dream.

Point and Click, Perfected

I play a ton of games. I’m also really fickle. In fact, one of the main reasons why I make games is my frustration with finding games that satiate my specific moods. I’m your worst game design nightmare. I get easily irritated, therefore, I HATE making games for me.

For a few years now, I’ve been hearing about this point-and-click adventure game called Trauma. I believe we’ve even had the immense honor of being nominated for awards next to this title. In my head, I’m not a huge point-and-click fan, despite playing every damn Cyan game from Myst onwards. Somewhere, I think I might be lying to myself. But, the truth is… a genre doesn’t have to define a game. Sometimes, a game can be so completely amazing, it just busts “awesome” at the seams.

I can’t really explain why I’ve put off playing Trauma for so long. At the end of the day, I was just being a jerk. I judged the book by its cover (which honestly looks fantastic). It wasn’t for me. It was for some other artsy, geeky, game-design nerd. Yep. I was wrong.

Trauma is INCREDIBLE. The story draws you in even when you hate story games (I DO, maybe). The aesthetics are phenomenal. A REAL designer was on this project. Almost every game I play these days lacks a good designer; that was not the case here. Someone took the time to really pay attention to the experience they were creating. There are no handholding tutorials. In fact, the basic tutorials are built into every stage of the game and even hidden as a part of the experience itself. The whole damn thing is just bathed in style. The music sets a perfect tone. The photographic imagery is impressive. The animations are smooth without being corny.

One of my favorite parts of the game is its control interface. By nature it is point and click. However, other actions can be performed by drawing lights in the air. Those actions are scattered around this playground-style-world for Gestures through Point and Clickyou to find. The only complaint I have about the game is that the 3 ‘execute’ actions are the same for each ‘dream’. I would have loved to explore a bit more and interact in a more conductive (think music) way.

The play time is rather short. We’re talking 3 hours to completely find everything and all the alternate endings. While some people may find this terrible, I cannot help but LOVE the game for this. The whole experience was fleeting. I have been treated to this amazing experience. I’ll never be in that state of mind again at that moment and pick that game to play. You’ve given me a lasting memory. Thank you Krystian Majewski.

Games like this make me self-reflect. I love being touched by an experience. For me, it’s rare and when it happens I cannot help but be thankful. Thankful that there are people out there making such great experiences and thankful that I’ve just got to get out there and discover them.