Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
August 28, 2014
arrowPress Releases
August 28, 2014
PR Newswire
View All
View All     Submit Event





If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 
Server Discovery in Unity 4.0.1
by Darrel Cusey on 03/07/13 02:01:00 pm   Expert Blogs   Featured Blogs

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

This post was originally published at Lakehome Games here.

This Unity Package can be downloaded here (updated):

server_discovery_sample_2013_03_07.unitypackage

For most developers, Unity truly goes out of its way to make things as easy as possible for game development.  For example, switching your camera type from perspective to orthographic takes just a click of a button.  Switching lights from spot, to directional, to area; again, just a click of a button.  However, there are a few parts of Unity that, surprisingly, have no options whatsoever.  One of these parts is Unity's networking design.  Unity is stubbornly Client-Server in its networking design.  There's no drop-down box where you can switch it over to peer-to-peer networking - this is left as an exercise for the programmer.

A peer-to-peer system, though, can be developed on top of Unity's existing client server networking framework by adding additional components that will allow the server to migrate among all networked clients.  Minimally, we'll need to build 3 components: Shared State, Server Discovery and Server Migration.  In my previous article, I described a simple FPS Networking Sample that could maintain state across all clients using a single NetworkView on each client.  An additional advantage to this system is that each client maintained the same information as the "server."  Because of this, the Shared State requirement has already been met.  The clients are not sharing a lot of information (just the list of players and their locations and targets)... but for now, that's sufficient.

In this article, I'll tackle Server Discovery and finally demonstrate Server Migration in a follow-up article.  I'm going to review the most important parts of this solution, so if you haven't already grabbed the Unity package above, you should do so now so you can follow along.  Let's start with a process flow diagram...

server_discovery_overview

One of my design goals for this solution was to make it so that a client could easily switch over to become a server at any time.  Until we put in the last Server Migration piece, this is done manually by following these steps:

  1. Start a single-player game
  2. Hit Escape
  3. Select "Open to LAN"

Now your game will be available to others, but most importantly you are able to start a single player game without connecting to a server first.  If you look at a lot of other Unity Networking tutorials, this isn't even possible.  A lot of other tutorials will use Network.Instantiate(), or else make it so that the client only instantiates a player upon the specific direction of the server.  So, how do we both instantiate at the direction of the server while not actually being connected to a sever?

Easy.  We fake it.  In the diagram above (highlighted in blue), FakeServerJoin() is always executed in any single player game.  This function will start a local server (which won't actually be used at this point) and then use that local Unity server instance to allocate its own network view.  This is okay to do because (if we become a server later) any clients that connect to us will be drawing network views from the same view ID pool -- so there's no duplication of view IDs.

Also, notice how we create a new NetworkPlayer instance, just by calling its default constructor.  Normally, your NetworkPlayer instance is something that is created and populated "behind the scenes" for OnPlayerConnected().  Because we're not actually connecting to a server here, OnPlayerConnected() will never get called.  But that's okay because NetworkPlayer's default constructor will create "good enough" default values that won't interfere with any real network players that may connect to us later.  We just allocate a newtwork view, create a default NetworkPlayer instance, send it all along to the JoinPlayer() function, and pretend these aren't the droids we're looking for... move along... move along.

At any point now, we can open the game to the LAN and people can connect to us normally.

So, now let's see what happens when we open the game to the LAN -- this is where the real Server Discovery functions come into play.  You may have noticed that Unity doesn't have a "Network.Broadcast()" function -- and why would it?  It strictly adheres to a client-server networking model, after all.  It assumes that the client MUST know the IP address of the server.  So, how do we communicate with a server when we don't know its IP address (or even if there is a server out there to talk to)?

To accomplish that, the ListenServer() function (highlighted in red) has to do some lower-level networking work.  It has to create its own IPEndPoint, create a UdpClient, and create a UdpState.  These can all be done with just a single statement each -- so, it's not exactly re-inventing the wheel.

However, there are a couple things we need to be careful with.  First and foremost, we must use a random port every time this function is called.  In a given application session, you cannot create 2 UdPClients with the same IP address and same port.  Unity will complain about this and generate a runtime error.  You would see this error on the client if the client connected, dropped, and then tried to rejoin without restarting the application.  The client shouldn't have to restart the application to re-join, so we have to solve for this limitation.

Update:  I found out that you could actually use 0 for the port number as long as you preset the minPort and maxPort.  This might be a slight improvement as (supposedly) the same port will not be chosen if port 0 is used repeatedly.  If you are feeling adventurous, you may want to try that.

To get around this, we randomize the port on which we're going to listen for server broadcast responses.  Because the server doesn't know what port we're listening on, we have to tell it what that port number is -- we do this by sending our listening port number on the server's known port (which it previously opened for listening for client broadcasts).

Let's summarize before going further:

  • Server open client listener on Known Port (15000)
  • Client opens a listener on a Random Port (15001 to 15999)
  • Client sends on Known Port to the server its chosen Random Port
  • Server responds to client on the client's chosen Random Port

Also notice that we are using a lot of asynchronous callbacks.  This is done both in the ListenServer() function and in the ListenForClients() function (highlighted in yellow).  This will (effectively) create a separate thread on which we can react when the conditions for the callback are met.  This is great for a client because it allows the client to still play locally while hosting a game and while the listener thread is independently listening for client join requests :)

It also completely sidesteps the whole "Connect is Blocking" problem (which we're not even going to talk about here -- that would be a whole separate article).  The BeginReceive(new AsyncCallback(ListenServerCallback), us1) function will call the function "ListenServerCallback" when a response is received back on our client's chosen  Randomly Port number and operate completely on a separate thread.  For the server, the same thing happens with the BeginReceive(new AsyncCallback(ListenForClientsCallback), us1) function, but this time it calls the ListenForClients function when a client sends a message on the Known Port.

Note as well that just opening a listening port won't (of course) cause a server to respond -- so, we have to actually send a message to the server.  This is done with the FindServer() function.  We could send a message to the server something like, "Looking for Server Discovery server," but that would be a waste.  The server needs to know the chosen Random Port on which we're listening for responses, so we'll send that instead.  Notice too that we specifically have to turn on the capability for the UdpClient to broadcast with "uc2.EnableBroadcast = true;" -- it's off by default.

The last point I wanted to discuss here is the need for a Thread.Sleep() function call.  This is needed so that those separate threads that have been started can get a chance to get some processing time.  This must be done any time an asynchronous function is used, so it's just easiest to put this in the networkController's Update() function.  The integer it accepts is the number of milliseconds to block the current thread -- thereby allowing the listeners some time to check their buffers and execute the callbacks if any messages have been received.

So, that's pretty much all there is to it.  Adding Server Discovery to Unity.  We're now two-thirds of the way done to having a full peer-to-peer Unity client.

If you liked this article, please follow me on Twitter @Tulrath to know first when the final part of this series is published.

As always, thanks for reading!


Related Jobs

Cloud Imperium Games
Cloud Imperium Games — Austin, Texas, United States
[08.27.14]

Lead Network Engineer
Cloud Imperium Games
Cloud Imperium Games — Santa Monica, California, United States
[08.27.14]

Animation Programmer
Cloud Imperium Games
Cloud Imperium Games — Austin, Texas, United States
[08.27.14]

Lead Software Engineer
Cloud Imperium Games
Cloud Imperium Games — Austin, Texas, United States
[08.27.14]

Server/Backend Programmer






Comments


Brian Schaeflein
profile image
I know you're doing peer-to-peer here, but I was wondering if you've any experience with third-party networking solutions like the UnityPark Suite at muchdifferent.com?

Darrel Cusey
profile image
Brian,

I have not heard of that one, but I don't usually use too many third-party solutions -- I'm too much of a control freak :)

Jack Nilssen
profile image
Great post. Thumbs up. Would read again.

Darrel Cusey
profile image
LOL thanks DarkAcre man :D

Dominique Da Costa
profile image
Hi, great post.

I'm currently looking for different solutions to implement multiplayer in my game made with Unity free. I was thinking to use Photon Cloud but for Android and iOS, there is a need to have the pro version of Unity.
So I'm interested by your post and want to know I you have a blog with other thought on multiplayer and Unity ;-)
Also, I've seen a lot of critics on the Unity networking feature, especially the fact that it was not efficient in "real cases". What's your point of view ?

Regards.

Darrel Cusey
profile image
Dominique,

I post all kinds of things to [http://www.lakehomegames.com], but I'll be cross-posting to GamaSutra right here in the Expert Blogs Section for the foreseeable future :) You can also follow me @Tulrath or subscribe to my Lakehome Games blog at the address above to receive e-mail notifications.

I was going to write my opinions on Unity networking here -- but then realized it was getting to be the size and shape of a Blog post... so you can find my answers to your questions here instead:

[http://lakehomegames.com/2013/05/10/unity-networking-what-i-reall
y-think/]

M Nauman Ghumman
profile image
Great post Darrel Cusey
I built a “Get away” card game on android. This game will be played by connecting all the clients to a server using a network and network may be hotspot or wifi.Cards will be distributed by server. Server may also play the game.
your "server_discovery_sample_2013_03_07.unitypackage" working fine server name discover on all client and all clients connected to it.
But i am adding a chatting module on my project.i am a beginner developer in unity that is way i can not add a chatting module on your .unitypakage so please help me....i am very thankful to you.........


none
 
Comment: