Friday, October 30, 2015

Unity3D At Home Project - Day 6 - Multiplayer

So as you've probably come to realize the "Day" in the title really isn't significant. I could be "Issue", or "Section", or "Chapter" if you like. It's really just a way for me to designate each subgoal within the project.  And that's really important - subgoals. When you're making a game, the entirety of the game itself can be incredibly overwhelming. Menus, scores, multiplayer support, weapons, animations, the list goes on and on and on. And a lot of times you're going to be thinking - what next? How do I get it all done?

Your ability to compartmentalize your game into sections - and more importantly - systems, is going to be one of the most valuable skills for you to develop. Pick out a specific section of the game, decide what the goals are for that section, and implement it. But all of the pieces of the game are intrinsically interconnected. And most pieces build upon some other piece. Which is why it's not only important to decide which section you're working on, but what order are you going to build the sections of your game. For instance, I wouldn't spend a huge amount of time focusing on building a level in my game until I had a better grasp on the gameplay - because those things are going to shape level design.

The next section piece of our game puzzle I wanted to implement was basic multiplayer. Now - that word, all by itself, is still a whopping tall order, so I needed to constrain it even further. Specifically, I wanted to be able to have two clients in the same game together, and be able to run around, and animated correctly.  And as you'll see, in the end, Unity made that frighteningly easy. In fact, it will probably take me longer to write this blog than it did for me to get it up and working. But first things first. Let's talk design a bit.

Multiplayer Architecture
Multiplayer architectures come in a variety of flavors. One is peer-to-peer. In this model, all actions are synced across all computers connected to the network simultaneously. It's extremely important that everything be replicated in exactly the same way. The Age of Empires games were written this way - there was no dedicated server in a multiplayer game. The downside to this model is that it is extremely easy for one machine to become out of sync in its game state with the other machines, and to not realize it. I can't tell you the hours we spent at Ensemble tracking down exactly those kinds of bugs.

These days, most everything though uses a client-server architecture. One machine is authoritative for the game state at all times. Clients make requests to the server, and are informed of other client's actions on the server, and update their game view appropriately. However, even within the client-server architecture, there's a couple of different ways to go.

One method is to use dedicated server. This machine runs and maintains the game state for all of the connected clients, but doesn't run a client itself. The other method uses a host client. The host client serves as both server and client on the same machine. Other clients connect to the host client, and have the game state replicated back to them. This architecture, of using a host client, has some advantages and disadvantages.

The biggest advantage is that games can be played anywhere there's a network. You don't have to have a dedicated server running 24/7 to serve games. If two people have your game, they can connect to each other, and immediately begin playing. Furthermore, anyone that knows the IP address of a host client machine can connect to that machine, and join in. It allows for the greatest flexibility and ease off play for a casual game.

However, there are quite a few disadvantages as well, when there is not a dedicated server. First of all, as you can imagine, in a host-client multiplayer game, the player that is running the host usually has a bit of an advantage, just in sheer terms of latency and response. His machine actually is running the simulation - his client is going to get updates the quickest. In this day and time though, with bandwidths being what they are, this is not typically much of an advantage. And if your game is turn-based, or completely non-latency dependent, it's not an advantage at all.  But the biggest disadvantage is that you, the developer, don't control the server. So this means no account systems, no persistence, no way of saving game state sessions from game to game. It also makes it pretty much impossible to prevent hacks and cheats from entering the game, if you're not hosting the servers.

For those reasons, if I were doing this game "for real" - that is, to make a business off of it, I would almost certainly insist on a model with dedicated servers. And pretty much most games these days that have any multiplayer component do exactly that. Hell, some games that even aren't multiplayer still require you to connect to a dedicated server, just for authentication and account purposes.

But this game is an experiment. And is as much an exercise in exploring technology as it is about making something fun. And one of the pieces that I want to explore, besides Unity's basic multiplayer capabilities, is their matchmaking system. And at the moment, their matchmaker currently only supports host-client multiplayer architecture games.  Now let me be perfectly clear. I'm not saying Unity's multiplayer architecture doesn't support a dedicated client-server model. It absolutely does. But their matchmaking service, currently, only supports client-host games. Furthermore, if you did want to build a dedicated server, though they have announced plans to eventually do so, currently they do not provide hosting for that server in their cloud services. That means you either have to run the server yourself, or find some other cloud hosting service (like Amazon EC2) to host your server for you.

So, for all of those reasons, we'll be using a host-client architecture. Now, this decision is extremely important, because it dramatically shapes the nature of our game design. No dedicated server means no persistence beyond any particular game session. No accounts, no logins, and perhaps most importantly, no real means of monetization through in-app purchases or microtransactions, This pretty severely limits your means of monetizing this game to either doing so outright purchasing (and quite frankly who buys games anymore?) or in-game advertising. That means, if I were really intent on making this game make money, I'd probably be better suited making it a mobile app.  Fortunately, I'm not too worried about monetizing this game at the moment - as I've said, it's an experiment - but these are still really important things to think about when making your game.

Unity Multiplayer
So in truth, I probably spent more time reading over Unity's multiplayer documentation than I did actually setting it up in my game. And because it is so well documented, I won't go into the API in detail here, but I will touch on a few highlights. Unity's multiplayer api is provided at two levels. The first is a low level, network transport layer that provides socket management, messaging, and serialization functions. The second layer is game-ready high level api (HLAPI) that provides a full suite of tools at your disposal to handle all of the most common multiplayer chores, including state synchronization, client commands and RPC calls, lobby services, spawning, and even, as mentioned, matchmaking services.  And it is my  intention to take full advantage of the HLAPI.

Setting it up
So what did we have to do, actually, to get it working?  Well as I said, it was surprisingly simple. Unity 5.2 provides integration of their cloud services right into the editor, so the first thing I did was register my project with the multiplayer matchmaking. Now, I haven't even taken advantage of any of that part of the multiplayer system yet, but I am registered!

Unity also provides a template for converting your single player project into a multiplayer project, which was actually pretty useful. The biggest shift that comes with converting your project to multiplayer though, is going to be your shift in thinking.  Up until now, you just thought about "the game", and everything is happening in "the game".  But now, you have to think in terms of the client and the server, or both. Does this action happen on the client only? On the server only? Both? And more importantly, if this code is controlling some game object, like the player, is this code running on the player that I personally am controlling, or is it running on a player object that someone else is controlling, and how does the code know the difference?  And that is where Unity's API comes in, as it provides a good deal off state information for all of those cases.

So the first thing I did after registering was to make the collection of components that now defines our player (Kyle the Robot!) was a prefab.  I then modified his controller script by deriving it from NetworkBehavior instead of Monobehavior, and attached a NetworkIdentity component to him. That makes him Network aware.  The HLAPI provides a drop-in Network Manager class to oversee the multiplayer system as a whole, and you just drag your player prefab right onto the manager to let it know what the player is.

Now I only want the player control code to run for the local player (my player), as his movement information will be replicated by the network api to the other clients. So I wrapped the gathering of user input and modifying his movement based on that input inside of "IsLocalPlayer".

Remember my shitty camera? Well the camera control is a client-only object, so I didn't extend it from NetworkBehavior. However, it can no longer be directly attached to the player object, as the player object will be spawned by the multiplayer system. So I wrote a little code in the Player Controller's OnLocalPlayerStart callback routine that finds the AvatarCamera in the scene, and attaches it to the local player, so it can follow it around.

And that was pretty much it! With that little bit of code, the player was successfully getting spawned, controlled, and movement being replicated. And the camera followed it around correctly! One thing I noticed was that the animation wasn't getting replicated to clients. But lo and behold, Unity provides a NetworkAnimator component that you can attach to your player prefab, give it a reference to your animator component, and it will handle replicating the animation state for that object to the clients. And it just fricken' worked! Unbelievable!

So, success! It took me less time to get multiplayer up and running in my project than it did to set it up on GitHub - no exaggeration. Sometimes thing surprise you.

Want to try it? Another fun thing I learned about was GitHub releases, where you can attach a set off binaries to your project for people to download and use, so issued a "release" of Unity Terrain. I'll provide a link to the game down below.  I mean, right now, there is literally precious little to do execpt to connect, and run around each other, but it does work. And if you have any questions or comments don't hesitate to leave them below.  Next time - we start over with a new project, and we talk asset collection!

Useful Links

Unity Multiplayer Overview
A nice overview of Unity's multiplayer system, straight from them.

Converting your Single Player Project
A template for converting your single player project into a multiplayer one.

Unity-Terrain Release
Download the binaries off of GitHub and give it a try!

The At-Home Project
 The master blog page for the Unity At Home Project

No comments:

Post a Comment