Unity Multiplayer Tutorial

For using Networking in Unity. Author: Andrius Kuznecovas Last Revision: 24-MAR-2010


1. Multiplayer Tutorial
1. Introduction 2. Getting Started 3. Creating Your First Client / Server Application 3.1 Preparing Scene 3.2 Creating Scripts & Adding Components 3.2.1 Server & Client 3.2.2 Scene & Objects Instantiation On The Network 3.3 Quick Overview 4. Implementing Multiplayer in Startrooper 4.1 Changing Scene 4.2 Integration 4.2.1 Integrating Objects & Scene 4.2.2 Server & Client 4.3.3 Final Polishing 4.3 Quick Overview 3 3 4 4 5 5 8 10 11 11 11 11 21 37 38

Multiplayer Tutorial
Welcome, Unity User. Here you will learn advanced usage of Unity Networking. Have a great time and we hope that this tutorial will be helpful.

1. Introduction
The aim of this tutorial is to show the essence of Unity Networking. We will show how to create basic and advanced network applications using Master Server/Client, UDP Server/Client and Direct Connect. In this tutorial we will use Unity iPhone 1.6, iPhone 3GS and the StarTrooper demo from the Unity web site.

2. Getting Started
There are a few things that you should know before you start. • You will learn how to: Use basic and advanced components of networking. Create a server and client. Use the Master Server. Use Direct Connect. Use UDP Broadcast Server. Create a simple scene on the network. Convert the StarTrooper game to multiplayer.

we will cover the basics needed to create a simple multiplayer application. Knowledge about networks and how they work.6. Create a new Cube GameObject: GameObject -> Create other -> Cube. Advanced C# and JavaScript skills. Your first steps: Create a new Project. 3. Basic Unity skills. You can connect between different types of Unity targets. • Important notes: Unity supports . Unity Networking supports wifi. Create a new Prefab and name it Player: Assets -> Create -> Prefab. iPhone or iPod. 3. 3G and GSM connections. Creating Your First Client / Server Application In this chapter. We will use Direct Connect for connection between client and server. 4 . from Unity Web Player to Unity iPhone.1 Preparing Scene • Now let’s start with a simple scene. etc. You can disable or enable Networking in: Edit -> Project Settings -> Player -> Enable Unity Networking. We will create our first example featuring moving objects on the network and basic handshaking between the client and server. For example you can connect from Unity on the desktop to Unity iPhone.1. You can find more information here. • You should have: Unity iPhone 1.Use other Unity components and more.NET 1. Drag your Cube from the Hierarchy to your Player Prefab in the Project and then delete your Cube from the scene.1 and 2. The example uses all basic multiplayer components such as: Network and NetworkView.

0). yourPort = "".1".5). Rotation (25. 3. listenPort = 25000.0. Light parameters: Position (0.Create a new Plane and name it Ground: GameObject -> Create other -> Plane. then open the file and create some variables: var remoteIP = " Create a Directional Light: GameObject -> Create other -> Directional Light. Scale (5. instantiate the scene and objects on the network. var var var var var remotePort = 25000. Your Plane parameters should be: Position (0. save your scene and name it MainGame: File -> Save Scene.0.2 Creating Scripts & Adding Components • Next you will learn how to create the server and client.1 Server & Client • We will start with the most important — the creation of Server and Client: Create a new JavaScript file and name it ConnectionGUI: Assets -> Create -> JavaScript. 5 . Everything should look like this image: 3. Rotation (0.2. move objects. useNAT = false.0). Add this file (by dragging) to the Main Camera object in the Hierarchy. yourIP = "". Scale (1.0.0).0).0. and connect everything together. Finally.1) Shadows -> Type -> Soft Shadows.0.

250.Button (new Rect(10.remoteIP).Disconnect(200).20. port = Network."IP Adress: "+ipaddress+":"+port).50).InitializeServer(32. } else { // Getting your ip address and port ipaddress = Network.30).10. } } // Fields to insert ip address and port remoteIP = GUI.40). GUI.100."Start Server")) { Network. remotePort = parseInt(GUI.Button (new Rect(10."Disconnect")) { // Disconnect from the server Network.10.useNat = useNAT.Label(new Rect(140. // Notify our objects that the level and the network is ready for (var go : GameObject in FindObjectsOfType(GameObject)) { go. // Creating server Network. // Connecting to the server Network.ToString(). if (GUI.Connect(remoteIP. SendMessageOptions.10.Now we will create an interface using Unity GUI for creating the server and connecting to it: function OnGUI () { // Checking if you are connected to the server or not if (Network.SendMessage("OnNetworkLoadedLevel".peerType == NetworkPeerType.player.TextField(new Rect(120.40.player.DontRequireReceiver).port.30).20).100.ToString())). remotePort)."Connect")) { Network.remotePort.Button (new Rect(10.TextField(new Rect(230.50.20).Disconnected) { // If not connected if (GUI.ipAddress.100.10.100. } } } 6 . } if (GUI. listenPort).useNat = useNAT.

Build your project to the iPhone and create a server in the Editor.DontRequireReceiver). } • In Play mode your screen should look like these images: • Now you can test your server and client. Note that both applications must be on the same network for everything to work.• Pay attention to the function below. 7 . we notify all objects that the scene and the network are ready. This function is called every time someone successfully connects. function OnConnectedToServer () { // Notify our objects that the level and the network are ready for (var go : GameObject in FindObjectsOfType(GameObject)) go. When this happens. SendMessageOptions. Edit your Player Settings (Edit -> Project Settings -> Player) to set up your iPhone Bundle Identifier and switch the Default Screen Orientation to Landscape Right. Try to connect to the server using the IP address you can find on the server screen — if everything goes OK you will see “Disconnect” button and your IP address on both screens.SendMessage("OnNetworkLoadedLevel".

Network.1. transform. function OnNetworkLoadedLevel () { // Instantiating SpaceCraft when Network is loaded Network. When the component appears on your object change the State Synchronization parameter to Reliable Delta Compressed. Scale (1.rotation.0.2 Scene & Objects Instantiation On The Network • Now we need to add the network components to your Player (Cube) and write code to instantiate it: Select Player Prefab and add a NetworkView: Components -> Miscellaneous -> NetworkView. } 8 . 0). Create a new JavaScript file and name it Instantiate.RemoveRPCs(player. Add a Rigidbody to your Player Prefab: Select Prefab -> Component -> Physics -> Rigidbody. Object parameters: Position (0.DestroyPlayerObjects(player).Instantiate(SpaceCraft.2. Open the file and write following code: var SpaceCraft : Transform. This is needed to show your synchronized transformation to all users.position.0). 0).0). Your new components should look like this image: • Now we will instantiate your Player and objects on the network: Create a new empty GameObject and name it Spawn. transform.5.1). } function OnPlayerDisconnected (player : NetworkPlayer) { Network.3. Rotation (0.

Your editor screen should look like this image: 9 .50).50.Find("Player(Clone)"). Build your project. Select your Spawn object and change the Player parameter to “Player (Transform)” by selecting your prefab from the list.Button(new Rect(20.Add this file to your Spawn object: Select Spawn Object -> Component -> Scripts -> Instantiate. Add this code to the file: function OnGUI() { if(GUI. } } Add this file to the Spawn object in the Hierarchy.position = new Vector3(0.transform. create a server and connect to it."up")) { GameObject. To be sure. • If you test your example you will see that the server and each connected user will have their own Player (Cube).100.5.js. we will create a simple test: Create a new JavaScript file and name it Control. Now you can see that everyone can move their own Player (Cube).0).

Connect everything together. Download the whole NetworkExample project.3 Quick Overview Congratulations! You have covered all of the basics needed to create a multiplayer application. Create a client. • You have learned how to: Prepare a basic scene for a multiplayer game. we will expand on the ideas that we have already covered and create an advanced networking setup. 10 . Instantiate a scene and objects on the network. In the next chapter. Use Direct Connect. Use the basic network components. Create a server.3.

0). Implementing Multiplayer in Startrooper In this chapter you will learn how to convert the StarTrooper game from single player to multiplayer. We will make more changes here later. Select the StarTrooper scene. create two folders: "NetworkFIles" for new scripts and "Plugins"for C# files that should be pre-compiled. Scale (1. In the Trail Render set Materials -> Element 0 -> missleTracer. Rotation (0. 4. Set Transform parameters: Position (0. • Download StarTrooper from the Unity Web Site and make yourself familiar with it. At the end of this chapter you will be able to fly around and kill other users in multiplayer mode. 4. we will create a server and client.2 Integration Now we will make our main changes and integrate networking into the project. We will use complex components and three different connection types: Direct Connect (that you have seen in chapter 4).1 Changing Scene • As the scene we're going to modify was designed for single player game. Remove the Missile Launcher script from the object.30.1 Integrating Objects & Scene • Now we need to create a script that will translate our Rigidbody on the Network: First.2. Add a NetworkView Component: Component -> Miscellaneous -> NetworkView.cs file to the Plugins folder.4. MasterServer Connection and UDP Broadcast connection.1). but we'll start with some basics: Open the downloaded StarTrooper project. When we are done. Add a Trail Render: Component -> Particles -> Trail Render. 11 . Remove the Player Controls script from the object. Let's begin by preparing the scene and SpaceCraft for the network. 4.1. Create a new GameObject and name it Spawn: GameObject -> Create Empty. Select SpaceCraftFBX in Hierarchy. Create a C# file and name it NetworkRigidbody. we need to change it a little bit.0.11). Move the NetworkRigidbody.

1. Vector3 angularVelocity = rigidbody. You can use this script on any project you want.Serialize(ref pos). Quaternion rot = rigidbody.Serialize(ref rot).Serialize(ref velocity). Quaternion rot = Quaternion.angularVelocity. } // Read data from remote client else { Vector3 pos = Vector3. Vector3 velocity = Vector3. public class NetworkRigidbody : MonoBehaviour { public double m_InterpolationBackTime = 0.Do not worry much about this file. because it's suitable for all Rigidbody objects.position.isWriting) { Vector3 pos = rigidbody. stream.cs file and use this code: using UnityEngine.zero. Vector3 pos.zero. Vector3 velocity = rigidbody. NetworkMessageInfo info) { // Send data to server if (stream.Collections.Serialize(ref angularVelocity). 12 . Open your NetworkRigidbody. double timestamp.5. stream. void OnSerializeNetworkView(BitStream stream. stream. // Keep track of what slots are used int m_TimestampCount. stream. internal Vector3 angularVelocity. public double m_ExtrapolationLimit = 0. using System. Quaternion rot. Vector3 velocity. internal struct State { internal internal internal internal } // We store twenty states with "playback" information State[] m_BufferedState = new State[20].velocity. just use it where it is required.identity.rotation.

rot = rot. m_BufferedState[0] = state.timestamp < m_BufferedState[i+1].i>=1.Vector3 angularVelocity = Vector3. however never exceed the buffer size // Slots aren't actually freed so this just makes sure the buffer is // filled up and that uninitalized slots aren't used.Serialize(ref velocity). stream.timestamp > interpolationTime) { 13 . stream. } // Record current state in slot 0 State state. state.Serialize(ref rot).time . // Update used slot count. stream. // Shift the buffer sideways.angularVelocity = angularVelocity.timestamp = info.Serialize(ref angularVelocity). state. state.i++) { if (m_BufferedState[i]. // Use interpolation if the target playback time is present in the buffer if (m_BufferedState[0].pos = pos.Length-1. m_TimestampCount = Mathf.Serialize(ref pos).timestamp. Nothing is done here for (int i=0.velocity = velocity. if it is inconsistent you could reshuffel or // drop the out-of-order state. m_BufferedState.i<m_TimestampCount-1. } } } // We have a window of interpolationBackTime where we basically play // By having interpolationBackTime the average ping.i--) { m_BufferedState[i] = m_BufferedState[i-1]. // Check if states are in order. stream.zero. you will usually use interpolation.Log("State inconsistent"). state. deleting state 20 for (int i=m_BufferedState.Min(m_TimestampCount + 1.Length).timestamp) Debug. state.m_InterpolationBackTime. // And only if no more data arrives we will use extra polation void Update () { // This is the target playback time of the rigid body double interpolationTime = Network.

So it uses 14% of rhs. // Don't extrapolation for more than 500 ms. rigidbody.pos.14.angularVelocity.Lerp(lhs.timestamp .magnitude * Mathf.i<m_TimestampCount. 0)]. float extrapolationLength = (float)(interpolationTime . // The best playback state (closest to 100 ms old (default time)) State lhs = m_BufferedState[i].0F.Rad2Deg. rhs. return.070 t is 9.timestamp <= interpolationTime || i == m_TimestampCount-1) { // The state one slot newer (<100ms) than the best playback state State rhs = m_BufferedState[Mathf.angularVelocity).time is 9. // Use the time between the two slots to determine if interpolation is necessary double length = rhs.rot.pos + latest.i++) { if (m_BufferedState[i].000. rhs. so sampleTime is 9.rot.time is 9.timestamp.900 .lhs. t).lhs.timestamp). // if t=0 => lhs is used directly transform.pos. Quaternion angularRotation = Quaternion.980 length is 0.910 rhs. float t = 0.Slerp(lhs.0001) t = (float)((interpolationTime .900 lhs. // As the time difference gets closer to 100 ms t gets closer to 1 in // which case rhs is only used // // // // lhs if (length > 0. t).latest. transform. Example: Time is 10.AngleAxis(axisLength. you would need to do that carefully if (extrapolationLength < m_ExtrapolationLimit) { float axisLength = extrapolationLength * latest.position = latest.timestamp) / length).localPosition = Vector3.// Go through buffer and find correct state to play back for (int i=0. } } } // Use extrapolation else { State latest = m_BufferedState[0].070 = 0.velocity * extrapolationLength. 86% of 14 . latest.9.Max(i-1.localRotation = Quaternion.910 / 0.

We will enable it later with some conditions.isMine) { var _NetworkRigidbody : NetworkRigidbody = GetComponent("NetworkRigidbody").angularVelocity = latest. Add RigidAssign. Disable the NetworkRigidbody component (just remove a tick from component).velocity. Delete SpaceCraftFBX from the scene. Create the Tag "SpaceCraft" for your SpaceCraft Prefab: Edit -> Project Settings -> Tags. Create a new JavaScript file and name it RigidAssign. rigidbody. _NetworkRigidbody2. } else { name += "Remote".enabled = true.js to your object and edit the file: function OnNetworkInstantiate (msg : NetworkMessageInfo) { if (networkView. name it SpaceCraft and move it to your Prefabs folder. _NetworkRigidbody. Drag your SpaceCraftFBX from the Hierarchy to your new Prefab (SpaceCraft) in Project window. } } } } Select your SpaceCraftFBX object in the Hierarchy (not in the Project files).rot.rotation = angularRotation * latest. Add NetworkRigidbody. var _NetworkRigidbody2 : NetworkRigidbody = GetComponent("NetworkRigidbody").angularVelocity. In the Network View component: change the Observed parameter from SpaceCraftFBX (Transform) to SpaceCraftFBX (NetworkRigidbody). rigidbody.velocity = latest. 15 .enabled = false. } } Create a new Prefab.rigidbody.cs to your selected object: Component -> Scripts -> Network Rigidbody.

rotation. transform. function OnNetworkLoadedLevel () { // Instantiating SpaceCraft when Network is loaded Network.js Add the file to the Spawn object in the Hierarchy and write this code to it: var SpaceCraft : Transform.Select your SpaceCraft Prefab again and assign the Tag to it: click on "Untagged" and select "SpaceCraft". } function OnPlayerDisconnected (player : NetworkPlayer) { // Removing player if Network is disconnected 16 .position. 0). transform.Instantiate(SpaceCraft. • Your SpaceCraft Prefab should look like this image: The SpaceCraft is now prepared for the Network! Create a new JavaScript file and name it Instantiate.

height : float = 5.0.position = target.LerpAngle (currentRotationAngle. 0). var currentRotationAngle : float = transform.Debug.position.Euler (0. Network. Select the Main Camera object in the Hierarchy. function LateUpdate () { if(GameObject. // Convert the angle into a rotation var currentRotation : Quaternion = Quaternion.transform.position. } Select the Spawn object in the Hierarchy and set the Space Craft parameter to SpaceCraft (Transform) (select your SpaceCraft Prefab from list).0.y + height. heightDamping : float = 2. 0). Open the Smooth Follow script and change everything to: var var var var target : Transform.FindWithTag("SpaceCraft"). // Set the position of the camera on the x-z plane to: // distance meters behind the target //transform. wantedRotationAngle.y. currentRotationAngle. distance : float = 10.DestroyPlayerObjects(player).RemoveRPCs(player. wantedHeight.0.0.deltaTime.forward * distance. // Damp the height currentHeight = Mathf. heightDamping * dt).Lerp (currentHeight. var currentHeight : float = transform. rotationDamping * dt). // Damp the rotation around the y-axis var dt : float = Time.eulerAngles. // Set the height of the camera 17 .position. var pos : Vector3 = target. // Calculate the current rotation angles var wantedRotationAngle : float = target.y. pos. Network.eulerAngles.Log("Server destroying player").currentRotation * Vector3. var rotationDamping : float = 3. var wantedHeight : float = target.FindWithTag("SpaceCraft")) { if (!target) target = GameObject. currentRotationAngle = Mathf.position .y = currentHeight.y.

Change everything to this code: var turnSpeed : float = 3.FindWithTag("SpaceCraft"). } } Add the Player Controls script to the Main Camera and open it to edit.0.Find("speed").2.FindWithTag("SpaceCraft")) { GameObject. var maxTurnLean : float = 70.zero.transform.0. var craft : GameObject.Portrait. normalizedSpeed. guiSpeedElement.LookAt (target). } else { iPhoneSettings.5. function Awake () { if (horizontalOrientation) { iPhoneSettings. var sensitivity : float = 0. private var normalizedSpeed : float = 0.screenOrientation = iPhoneScreenOrientation.AddRelativeForce(0.LandscapeLeft. 0. normalizedSpeed * (forwardForce*3)).0.rigidbody.position = new Vector3 (0. 0). var accelerator : Vector3 = iPhoneInput. private var euler : Vector3 = Vector3.acceleration. 18 .position = pos. // Always look at the target transform. var maxTilt : float = 50. var forwardForce : float = 5.transform. var guiSpeedElement : Transform.0. } guiSpeedElement = GameObject. var horizontalOrientation : boolean = true.screenOrientation = iPhoneScreenOrientation. } function FixedUpdate () { if(GameObject.

// Since we set absolute lean position.2).position = new Vector3 (0.Lerp(euler.z.y * maxTilt.transform. } // Rotate turn based on acceleration euler.x * maxTurnLean. // Since we set absolute lean position. Open the Missile Launcher script to edit.y = t. do some extra smoothing on it euler.Lerp(euler.touches) { if (evt. var missile : GameObject. sensitivity).x = -accelerator. Set the Missile parameter to missilePrefab.rotation = Quaternion.x * turnSpeed. do some extra smoothing on it euler.position.Euler(euler).2). accelerator. GameObject.Moved) { normalizedSpeed = evt.rotation. 0.x. guiSpeedElement. } } function Update () { for (var evt : iPhoneTouch in iPhoneInput. normalizedSpeed. -accelerator.x = Mathf.phase == iPhoneTouchPhase.FindWithTag("SpaceCraft"). We will add a timer for shooting and some small changes as well. var timer : int = 0. } } } Add a Missile Launcher script to the Main Camera. function FixedUpdate() { timer++.y / Screen. accelerator.Lerp (transform. 0. // Apply rotation and apply some smoothing var rot : Quaternion = Quaternion. } 19 .if (horizontalOrientation) { var t : float = accelerator.y. rot.x.y += accelerator.height. accelerator. 0).z = Mathf.

IgnoreCollision(thisMissile.transform.FindWithTag("SpaceCraft")) { var position : Vector3 = new Vector3(0.0) as GameObject. camera.FindWithTag("SpaceCraft").tag == "SpaceCraft") { Instantiate (explosion. 1) * 10.contacts[0]. if (collision.transform.collider).position.transform. position = GameObject.0) .GetMouseButtonDown (0))&&(timer>10)) { // if SpaceCraft exists if(GameObject. Physics.rotation. } } } Create the new tag "Missile" and assign it to missilePrefab. position.Find("Spawn").. We will make the ability to kill other SpaceCraft: var explosion : GameObject. Instantiate (explosion.gameObject. Select the missilePrefab and open the MissileTrajectory script to edit.gameObject.gameObject.FindWithTag("SpaceCraft")) { if(((collision.tag == "Untagged")||(collision.0) .identity). } 20 . Quaternion.TransformPoint (position).normal * 5.collider.transform.position = GameObject.main. } Destroy (gameObject).normal * 5.transform.Instantiate (missile.gameObject.0.tag == "SpaceCraft"))&&(collision. // instantiating missile var thisMissile : GameObject = Network.gameObject. function OnCollisionEnter(collision : Collision) { if(GameObject.point + (contact.function Update () { if ((Input. GameObject. -0.rotation).FindWithTag("SpaceCraft").FindWithTag("SpaceCraft"). timer = 0.tag != "Missile")) { var contact : ContactPoint = collision. GameObject. contact.point + (contact.2. collision. contact.

This scene will be used for UDP Broadcast connection.1.TransformDirection (Vector3. We will be creating three different types of servers.0).} } function FixedUpdate () { if(GameObject. Create a new scene and save it as ServerChoose: File -> New Scene.AddForce (transform. for example: • PS: you can create a folder and put all scenes together in one place. Create a new scene again. Create a new scene and save it as UDPServer. 21 .0)) * 720.2 Server & Client Now it's time to create the server for this game. Create a new scene and save it as MasterServer.FindWithTag("Missile")) { rigidbody. 4. We will use this scene when player has disconnected to clear up leftovers before new connection.forward + Vector3(0. Add all your scenes to the build settings: File -> Build Settings -> Add Open Scene.2. } } You have prepared the Scene and Objects for the Network. This scene will be used for Master Server. Your scene "ServerChoose" should be first in the list. save as EmptyScene. The scene will be used for choosing server type.0. File -> Save Scene.

width-220). Add this script to the Main Camera and add the following code: function OnGUI() { GUI. Create a new Empty GameObject and name it ConnectionGUI.LoadLevel("MasterServer").30).200.50)."SELECT CONNECTION TYPE").width/2)-100.height/2)+20.Button(new Rect((Screen. if(GUI.Button(new Rect((Screen. 22 .(Screen. function Awake () { // Network level loading is done in a seperate channel.height/ 2)-100."STAR-TROOPER MULTIPLAYER DEMO").50). GUI."UDP Connection")) { Application.Open the ServerChoose scene.LoadLevel("StarTrooper"). } if(GUI.height/2)-130. Create a new JavaScript file and name it NetworkLevelLoad. Create a new JavaScript file and name it Menu."Master Server Connection")) { Application.(Screen.200.height-30). networkView.width/2)-100.Label(new Rect((Screen. We will use this scene and this script for choosing a type of server and connection."Direct Connection")) { Application.200. We will use this script for loading the StarTrooper scene and objects to the network.(Screen.LoadLevel("UDPServer"). Add the NetworkLevelLoad script to the ConnectionGUI object and open to edit: // Keep track of the last level prefix (increment each time a new level loads) private var lastLevelPrefix = 0.220.50).width/2)-80.width/2)-100.50).200. } } Open the MasterServer scene.(Screen. } if(GUI.Label(new Rect((Screen.group = 1.height/2)-40.Button(new Rect((Screen. DontDestroyOnLoad(this).(Screen.

// Now the level has been loaded and we can start sending out data Network.Application. yield.AllBuffered. // Load level with incremented level prefix (for view IDs) networkView. // Once the level is loaded.SetLevelPrefix(levelPrefix). // because we are about to load the level.isMessageQueueRunning = true.peerType != NetworkPeerType."StarTrooper")) { // Make sure no old RPC calls are buffered and then send load level command Network. yield. because all those objects will get deleted anyway Network. // Notify our objects that the level and the network is ready 23 . RPCMode. RPC's and other state update attached to objects in the level are allowed to fire Network.Disconnected) { if (GUI.10.SetSendingEnabled(0.LoadLevel("EmptyScene").RPC( "LoadLevel". Network. lastLevelPrefix + 1).Button(new Rect(350. // Allow receiving data again Network. true). lastLevelPrefix = levelPrefix.100. } function OnGUI () { // When network is running (server or client) then display the level "StarTrooper" if (Network.isMessageQueueRunning = false. // There is no reason to send any more data over the network on the default channel. Application.RemoveRPCsInGroup(0). // We need to stop receiving because first the level must be loaded. false). } } } @RPC function LoadLevel (level : String.LoadLevel(level).SetSendingEnabled(0.30). // All network views loaded from a level will get a prefix into their NetworkViewID. levelPrefix : int) { Debug. "StarTrooper". Network.Log("Loading level " + level + " with prefix " + levelPrefix).RemoveRPCsInGroup(1). // This will prevent old updates from clients leaking into a newly created scene.

function OnFailedToConnectToMasterServer(info: NetworkConnectionError) { Debug.i<go_len.dedicatedServer = true.300. private var windowRect = Rect (Screen.length. With this script we will create a Master Server/Client and some GUI to use it. otherwise add NetworkLevelLoad from the menu (Component->Scripts->NetworkLevelLoad). private var hostListRefreshTimeout = 10.LoadLevel("EmptyScene"). private var doneTesting = false. } 24 . // Enable this if not running a client on the server machine // MasterServer.0.0.Log(info).0. private var natCapable : ConnectionTesterStatus = ConnectionTesterStatus. for (var i=0. private var lastHostListRequest = -1000. private var filterNATHosts = false. private var timeoutHostList = 0.i++) { go[i]. SendMessageOptions. private var timer : float = 0. private var probingPublicIP = false.0. var go_len = go. private var hideTest = false. } } function OnDisconnectedFromServer () { Application.width-300. var gameName = "YourGameName".Undetermined. private var testMessage = "Undetermined NAT capabilities". } @script RequireComponent(NetworkView) • Ensure that this script has added a NetworkView automatically.var go : Transform[] = FindObjectsOfType(Transform). var serverPort = 25002. Add this file to the ConnectionGUI object and open the script to edit: DontDestroyOnLoad(this).0.DontRequireReceiver).SendMessage("OnNetworkLoadedLevel".100). Create a new JavaScript file and name it MasterServerGUI.

" +" local LAN games only. case ConnectionTesterStatus. doneTesting = false.Undetermined: testMessage = "Undetermined NAT capabilities". filterNATHosts = true.PrivateIPNoNATPunchthrough: testMessage = "Cannot do NAT punchthrough.".Log("This machine has a public IP address"). } function Awake () { // Start connection test natCapable = Network.Log("This machine has a private IP address"). report the results in a label and react to the results accordingly natCapable = Network. 25 . doneTesting = true. } function Update() { // If test is undetermined. else Debug. keep running if (!doneTesting) { TestConnection(). break. filtering NAT enabled hosts for client connections.Error: testMessage = "Problem determining NAT capabilities".TestConnection().function OnFailedToConnect(info: NetworkConnectionError) { Debug.HavePublicAddress()) Debug. case ConnectionTesterStatus. // What kind of IP does this machine have? TestConnection also indicates this in the // test results if (Network. } } function TestConnection() { // Start/Poll the connection test. } function OnGUI () { ShowGUI(). break.Log(info). switch (natCapable) { case ConnectionTesterStatus.TestConnection().

time + 10.PublicIPIsConnectable: testMessage = "Directly connectable public IP address. break." +" NAT punchthrough can circumvent the firewall. Network." +" running a server is impossible. 26 . doneTesting = true. // This case is a bit special as we now need to check if we can // use the blocking by using NAT punchthrough case ConnectionTesterStatus.Log("Testing if firewall can be circumnvented"). probingPublicIP = true.TestConnectionNAT().useNat = true. } // NAT punchthrough test was performed but we still get blocked else if (Time.Network.useNat = true.useNat = false.". timer = Time.PrivateIPHasNATPunchThrough: if (probingPublicIP) testMessage = "Non-connectable public IP address (port "+ serverPort +" blocked). natCapable = Network. break. // reset Network.". case ConnectionTesterStatus. else testMessage = "NAT punchthrough capable. doneTesting = true. case ConnectionTesterStatus. // NAT functionality is enabled in case a server is started. // clients should enable this based on if the host requires it Network. break. Network. doneTesting = true.useNat = false.PublicIPPortBlocked: testMessage = "Non-connectble public IP address (port " + serverPort +" blocked). // If no NAT punchthrough test has been performed on this public IP. Enabling NAT punchthrough functionality. force a test if (!probingPublicIP) { Debug. doneTesting = true.".useNat = true.time > timer) { probingPublicIP = false. } break.".

lastHostListRequest = Time.TestConnection(true).PollHostList(). MasterServer. "stuff". got " + natCapable. MasterServer.10. break.ClearHostList(). } } function ShowGUI() { if (GUI.10.InitializeServer(32.Button (new Rect(100. var _cnt : int = 0."Start Server")) { Network.30).case ConnectionTesterStatus." +"it must be started to check server accessibility.RegisterHost(gameName. } // Refresh hosts if (GUI."Retest connection")) { Debug.useNat) ) { 27 . Debug.210.Log("Redoing connection test"). } if (Network. } var data : HostData[] = MasterServer.30)."Refresh available Servers") || Time.RequestHostList (gameName).120.Button(new Rect(10. MasterServer.realtimeSinceStartup.".peerType == NetworkPeerType.Log("Refresh Click").Button(new Rect(10.PublicIPNoServerStarted: testMessage = "Public IP address but server not initialized. probingPublicIP = false.40. default: testMessage = "Error in test routine.realtimeSinceStartup > lastHostListRequest + hostListRefreshTimeout) { MasterServer. serverPort).Disconnected) { // Start a new server if (GUI. Restart connection test when ready.updateRate = 3.30). "profas chat test"). natCapable = Network.90. for (var element in data) { // Do not display NAT enabled games if we cannot do NAT punchthrough if ( !(filterNATHosts && element. doneTesting = false.

port + " ".useNat = element. } hostInfo = hostInfo + "]".gameName + " " + element. else print("Connecting directly to host"). if (Network. } } } • Now you can test your project using the Master Server: Go to Edit -> Project Settings -> Player and set up your iPhone Bundle Identifier and change the Default Screen Orientation to Landscape Left.UnregisterHost(). and connect to the // first valid one."Disconnect")) { Network.ip. if (GUI.Disconnect().Button(new Rect(20. 28 . MasterServer.30). var hostInfo. In the GUI we could just display // the first one in order not confuse the end user. } } } } else { if (GUI. element.ToString())) { // Enable NAT functionality based on what the hosts if configured to do Network.400.ip) { hostInfo = hostInfo + host + ":" + element.10.40). Network.Button (new Rect(10. // Here we display all IP addresses.useNat) print("Using Nat punchthrough to connect").90. there can be multiple in cases where // internal LAN connections are being attempted. but internally Unity will // do a connection check on all IP addresses in the element.playerLimit.port).useNat.connectedPlayers + " / " + element.Connect(element.hostInfo.var name = element.ip list.(_cnt*50)+90. for (var host in element. hostInfo = "[".

Connect to the server from the iPhone. Click on the StarTrooper button in the Editor or on the iPhone. Click on Start Server on Editor. Your screens should look like these images: We have finished our Master Server! 29 . Run the Server scene in the Editor.Build your project to the iPhone. Click on Master Server Connection in the Editor and on the iPhone. The server should appear on iPhone (if not. click Refresh available Servers).

Assign the UDPConnectionGUI file to the UDPServer object.Collections. string ip = "0.0. private UdpClient client.• Now we need to create a second connection type . System. Open UDPConnectionGUI and write the following: using UnityEngine.0. private int clear_list = 0. bool youServer = false. Assign a NetworkView Component to the UDPServer object and change the Observed parameter to UDPServer (Transform). public void Update() { if(clear_list++>200) { server_name = "". (Or assign it after you write the following code). Assign the NetworkLevelLoad Script file to the UDPServer object. private private private private private string port = "6767". int listenPort = 25001.Sockets.Net. System. Move the UDPConnection file to the Plugins folder. clear_list = 0.255. private bool connected = false. System.255". using using using using System.Threading.UDP Broadcast connection: Open the UDPServer scene.Net. string ip_broadcast = "255. Create a new C# file and name it UDPConnectionGUI.255. Create a new empty GameObject and name it UDPServer. } 30 . private IPEndPoint receivePoint. public class UDPConnectionGUI : MonoBehaviour { private UdpClient server. private string server_name = "".0".

Receive(ref receivePoint). startClient. 31 . server_name = encode.Text.Text.GetBytes(Network.player. if(connected) { server_name = "".Log("Start").Convert.Text.ASCIIEncoding(). byte[] sendData = encode.ToString()). } public void start_client() { bool continueLoop =true.ToInt32(port)).System.Start(). receivePoint = new IPEndPoint(IPAddress. System. LoadClient().Text.ipAddress.ASCIIEncoding encode = new System.Convert. } } } catch {} } public void start_server() { try { while(true) { System.Parse(ip).} public void Start() { Debug.ASCIIEncoding().ASCIIEncoding encode = new System. Thread startClient = new Thread(new ThreadStart(start_client)).Close(). try { while(continueLoop) { byte[] recData = client. break.GetString(recData). } public void LoadClient() { client = new UdpClient(System. client.ToInt32(port)).

Parse(ipaddress). } } } else { if(GUI.ip_broadcast.100.ToInt32(port)). Network.Close(). server = new UdpClient(System. } } } } 32 ."Disconnect")) { Network. listenPort).System.Connect(server_name.100.Convert.Start().Close(). Thread startServer = new Thread(new ThreadStart(start_server)). youServer = false. startServer.Disconnect(). Network.Convert. server.Button(new Rect(20. client.Convert.server_name)) { connected = true.10."Start Server")) { youServer = true.ipAddress.Sleep(100).Send(sendData. receivePoint = new IPEndPoint(IPAddress. ip = ipaddress.50). listenPort).InitializeServer(32.30).server.ToInt32(port)).ToString().30).ToInt32(port)).player.200. string ipaddress = Network.Button(new Rect(10.100. Thread.sendData. LoadClient().Button(new Rect(10.Length.System.10. } if(server_name!="") { if(GUI. } } catch {} } void OnGUI() { if(!youServer) { if(GUI.

function Awake() { if (FindObjectOfType(MasterServerGUI)) this.1". var remotePort = 25000. Connect to the server from the iPhone when the IP address button appears.enabled = false. Play the UDPServer scene in the Editor. 33 .0. var listenPort = 25000. var useNAT = false. Your screens should look like these images: We have finished our UDP Broadcast server! • Now we need to create a third connection type . Start the server from the Editor by clicking Start Server. Create a new JavaScript file and name it ConnectionGUI. Assign the ConnectionGUI.js file to the Main Camera and write the following: var remoteIP = "127. var yourIP = "".• Now you can test this connection type: Set your Player Settings (if you haven't changed them yet) as instructed above. Build the project to the iPhone.Direct Connect: Open the StarTrooper scene.0. var yourPort = "".

10.20).30). } function OnGUI () { if (Network.TextField(new Rect(230. listenPort). // Creating server Network. remotePort).ToString()))."IP Adress: "+ipaddress+":"+port). } 34 .100.Button (new Rect(10. } else { // If connected // Getting your ip address and port ipaddress = Network.20).100.50."Connect")) { Network. } if (GUI.remotePort. GUI.100.Disconnected) { // If not connected if (GUI.useNat = useNAT. port = Network. if (GUI.useNat = useNAT. SendMessageOptions.30).port."Start Server")) { Network. // Connecting to the server Network.enabled = false.player.ToString().InitializeServer(32. remotePort = parseInt(GUI.remoteIP).Button (new Rect(10. } } remoteIP = GUI.peerType == NetworkPeerType.50).TextField(new Rect(120."Disconnect")) { // Disconnect from the server Network. // Notify our objects that the level and the network is ready for (var go : GameObject in FindObjectsOfType(GameObject)) { go.if(FindObjectOfType(UDPConnectionGUI)) this.10.40.ipAddress.Disconnect(200).40).20.Button (new Rect(10.player.SendMessage("OnNetworkLoadedLevel".10.Connect(remoteIP.DontRequireReceiver).250.Label(new Rect(140.100.10.

loadedLevel).LoadLevel(Application. else { var _NetworkLevelLoad : NetworkLevelLoad = FindObjectOfType(NetworkLevelLoad). Click Direct Connection in the Editor and on the iPhone. SendMessageOptions. Build the project to the iPhone. Enter the IP address on the iPhone in the IP field.OnDisconnectedFromServer(). Your screens should look like these images: 35 . _NetworkLevelLoad. Click Connect.SendMessage("OnNetworkLoadedLevel". } } • Now you can test this connection type: Set your Player Settings (if you haven't changed them yet) as instructed above.DontRequireReceiver).} } function OnConnectedToServer() { // Notify our objects that the level and the network is ready for (var go : GameObject in FindObjectsOfType(GameObject)) go.enabled != false) Application. Start the server from the Editor by clicking Start Server. Play the StarTrooper scene in the Editor. } function OnDisconnectedFromServer () { if (this. You can get the IP address from the Editor Player screen.

36 .We have finished our Client and Server with three connection types.

8.3 Final Polishing • Our project is almost finished now. Max Tilt: Select the SpaceCraft Prefab and set the Transform Scale parameters to: 1.1. To make our game more playable we need to add some final polish: Open the StarTrooper scene.-0. so feel free to make your own modifications or new features. In the Main Camera open MissileLauncher. Select the missilePrefab Prefab and set the Transform Scale parameters to: 8.0. Sensitivity: 0.js script.8. Select the Main Camera in the Hierarchy and change the public parameters in the Player Controls script: Turn Speed: 3. Your last screen should look something like this image: • PS: These last modifications are based only on my taste. 37 . At line 16.3.5).1.-0.2. change the vector from Vector3(0.8.5. Forward Force: 20.1) to Vector3(0. Max Turn Lean: 70.

Convert a single player game into multiplayer. Use the main Network Components.4.3 Quick Overview Congratulations! You have finished your MultiPlayer StarTrooper project. Create a Server/Client using Direct Connection. Use advanced Unity Components. • You have learned how to: Prepare a scene and object for the network. Create a Server/Client using UDP Broadcast connection. Download the whole MultiPlayer StarTrooper Project 38 . Translate a scene and objects on the network. Create a Master Server/Client.

Sign up to vote on this title
UsefulNot useful