diff --git a/Assembly-CSharp-firstpass-vs.csproj b/Assembly-CSharp-firstpass-vs.csproj
new file mode 100644
index 0000000..4d1b446
--- /dev/null
+++ b/Assembly-CSharp-firstpass-vs.csproj
@@ -0,0 +1,68 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.20506
+ 2.0
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}
+ Library
+ Properties
+
+ Assembly-CSharp-firstpass
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_5_5;UNITY_3_5;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_WEBCAM;ENABLE_MICROPHONE;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW;ENABLE_SUBSTANCE
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Assembly-CSharp-firstpass.csproj b/Assembly-CSharp-firstpass.csproj
new file mode 100644
index 0000000..4d1b446
--- /dev/null
+++ b/Assembly-CSharp-firstpass.csproj
@@ -0,0 +1,68 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.20506
+ 2.0
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}
+ Library
+ Properties
+
+ Assembly-CSharp-firstpass
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_5_5;UNITY_3_5;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_WEBCAM;ENABLE_MICROPHONE;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW;ENABLE_SUBSTANCE
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Assembly-CSharp-firstpass.pidb b/Assembly-CSharp-firstpass.pidb
new file mode 100644
index 0000000..0c7b9d0
Binary files /dev/null and b/Assembly-CSharp-firstpass.pidb differ
diff --git a/Assembly-CSharp-vs.csproj b/Assembly-CSharp-vs.csproj
new file mode 100644
index 0000000..e58cdf8
--- /dev/null
+++ b/Assembly-CSharp-vs.csproj
@@ -0,0 +1,70 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.20506
+ 2.0
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}
+ Library
+ Properties
+
+ Assembly-CSharp
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_5_5;UNITY_3_5;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_WEBCAM;ENABLE_MICROPHONE;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW;ENABLE_SUBSTANCE
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF} Assembly-CSharp-firstpass-vs
+
+
+
+
+
diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj
new file mode 100644
index 0000000..56462f5
--- /dev/null
+++ b/Assembly-CSharp.csproj
@@ -0,0 +1,70 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.20506
+ 2.0
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}
+ Library
+ Properties
+
+ Assembly-CSharp
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_5_5;UNITY_3_5;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_WEBCAM;ENABLE_MICROPHONE;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW;ENABLE_SUBSTANCE
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF} Assembly-CSharp-firstpass
+
+
+
+
+
diff --git a/Assembly-CSharp.pidb b/Assembly-CSharp.pidb
new file mode 100644
index 0000000..98f63f9
Binary files /dev/null and b/Assembly-CSharp.pidb differ
diff --git a/Assembly-UnityScript-firstpass-vs.unityproj b/Assembly-UnityScript-firstpass-vs.unityproj
new file mode 100644
index 0000000..8ee3c96
--- /dev/null
+++ b/Assembly-UnityScript-firstpass-vs.unityproj
@@ -0,0 +1,59 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.21022
+ 2.0
+ {C996ED3F-7676-775A-01B8-71BA430EB67B}
+ Library
+ Properties
+
+ Assembly-UnityScript-firstpass
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_4_2;UNITY_3_4;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Assembly-UnityScript-firstpass.pidb b/Assembly-UnityScript-firstpass.pidb
new file mode 100644
index 0000000..d0841d0
Binary files /dev/null and b/Assembly-UnityScript-firstpass.pidb differ
diff --git a/Assembly-UnityScript-firstpass.unityproj b/Assembly-UnityScript-firstpass.unityproj
new file mode 100644
index 0000000..8ee3c96
--- /dev/null
+++ b/Assembly-UnityScript-firstpass.unityproj
@@ -0,0 +1,59 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.21022
+ 2.0
+ {C996ED3F-7676-775A-01B8-71BA430EB67B}
+ Library
+ Properties
+
+ Assembly-UnityScript-firstpass
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_4_2;UNITY_3_4;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Assembly-UnityScript-vs.unityproj b/Assembly-UnityScript-vs.unityproj
new file mode 100644
index 0000000..aaaf420
--- /dev/null
+++ b/Assembly-UnityScript-vs.unityproj
@@ -0,0 +1,62 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.20506
+ 2.0
+ {B871E12C-38CB-763F-3AD7-72A506D3A1DA}
+ Library
+ Properties
+
+ Assembly-UnityScript
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_5_0;UNITY_3_5;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_WEBCAM;ENABLE_MICROPHONE;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW;ENABLE_SUBSTANCE
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+ {08AEB838-7F73-4A1F-D970-1DA6C584F352} Assembly-CSharp-firstpass-vs
+
+
+
+
+
diff --git a/Assembly-UnityScript.pidb b/Assembly-UnityScript.pidb
new file mode 100644
index 0000000..74bd82a
Binary files /dev/null and b/Assembly-UnityScript.pidb differ
diff --git a/Assembly-UnityScript.unityproj b/Assembly-UnityScript.unityproj
new file mode 100644
index 0000000..099f5e7
--- /dev/null
+++ b/Assembly-UnityScript.unityproj
@@ -0,0 +1,62 @@
+
+
+
+ Debug
+ AnyCPU
+ 10.0.20506
+ 2.0
+ {B871E12C-38CB-763F-3AD7-72A506D3A1DA}
+ Library
+ Properties
+
+ Assembly-UnityScript
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ Temp\bin\Debug\
+ DEBUG;TRACE;UNITY_3_5_0;UNITY_3_5;UNITY_EDITOR;ENABLE_PROFILER;UNITY_STANDALONE_WIN;ENABLE_GENERICS;ENABLE_DUCK_TYPING;ENABLE_TERRAIN;ENABLE_MOVIES;ENABLE_WEBCAM;ENABLE_MICROPHONE;ENABLE_NETWORK;ENABLE_CLOTH;ENABLE_WWW;ENABLE_SUBSTANCE
+ prompt
+ 4
+ 0169
+
+
+ pdbonly
+ true
+ Temp\bin\Release\
+ TRACE
+ prompt
+ 4
+ 0169
+
+
+
+
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEngine.dll
+
+
+ C:/Program Files (x86)/Unity/Editor/Data/Managed/UnityEditor.dll
+
+
+
+
+
+
+
+ {08AEB838-7F73-4A1F-D970-1DA6C584F352} Assembly-CSharp-firstpass
+
+
+
+
+
diff --git a/Assets/Resources/Prefabs/PlayerShip.prefab b/Assets/Resources/Prefabs/PlayerShip.prefab
new file mode 100644
index 0000000..d3ca2ce
Binary files /dev/null and b/Assets/Resources/Prefabs/PlayerShip.prefab differ
diff --git a/Assets/Resources/Prefabs/PlayerShipBullet.prefab b/Assets/Resources/Prefabs/PlayerShipBullet.prefab
new file mode 100644
index 0000000..1c852c3
Binary files /dev/null and b/Assets/Resources/Prefabs/PlayerShipBullet.prefab differ
diff --git a/Assets/Scenes/ConnectionFailureScene.unity b/Assets/Scenes/ConnectionFailureScene.unity
new file mode 100644
index 0000000..c29b097
Binary files /dev/null and b/Assets/Scenes/ConnectionFailureScene.unity differ
diff --git a/Assets/Scenes/Game.unity b/Assets/Scenes/Game.unity
new file mode 100644
index 0000000..549b9a1
Binary files /dev/null and b/Assets/Scenes/Game.unity differ
diff --git a/Assets/Scenes/Startup.unity b/Assets/Scenes/Startup.unity
new file mode 100644
index 0000000..d2f0335
Binary files /dev/null and b/Assets/Scenes/Startup.unity differ
diff --git a/Assets/Scripts/DebuggingAid.cs b/Assets/Scripts/DebuggingAid.cs
new file mode 100644
index 0000000..e51624d
--- /dev/null
+++ b/Assets/Scripts/DebuggingAid.cs
@@ -0,0 +1,83 @@
+// This is a temporary script used to get players to join automatically in another
+// hosted game if it's on the LAN.
+
+using UnityEngine;
+using System.Collections;
+
+public class DebuggingAid : MonoBehaviour
+{
+ MasterServerDirector masterServerDirector;
+ bool isSearchingForLANGames;
+ string serverIP = "";
+ string playerName = "";
+
+ // Use this for initialization
+ void Start ()
+ {
+ // Get the player name
+ playerName = ConfigurationDirector.GetPlayerName();
+
+ // Create a persistent player object. This, and the InputDirector object that is
+ // created in the process, will exist for the rest of the application's lifetime.
+ // We don't actually need it here, so just discard the return value.
+ Player.Create();
+
+ // Grab the master server director so we can listen for LAN games
+ masterServerDirector = InputDirector.Get().gameObject.GetComponent();
+ }
+
+ // Update is called once per frame
+ void Update ()
+ {
+ // Connect to the first LAN game we find automatically
+ if (isSearchingForLANGames && masterServerDirector.LANGameCount > 0)
+ {
+ string strIP = masterServerDirector.GetLANGameByIndex(0).ipAddress;
+ Debug.Log("We found a server at " + strIP);
+ InputDirector inputDirector = InputDirector.Get();
+ isSearchingForLANGames = false;
+ masterServerDirector.EnableLANGameSearch(false);
+ ConfigurationDirector.SetPlayerName(playerName);
+ inputDirector.ConnectToServer(strIP, ConfigurationDirector.GetServerPort(), "");
+ }
+ }
+
+ void OnGUI()
+ {
+ if (GUI.Button(new Rect(10,10,300,50), "Host a game"))
+ {
+ // Host a game
+ ConfigurationDirector.SetPlayerName(playerName);
+ isSearchingForLANGames = false;
+ masterServerDirector.EnableLANGameSearch(false);
+ InputDirector inputDirector = InputDirector.Get();
+ inputDirector.HostServer(
+ ConfigurationDirector.GetMaxPlayerCount(),
+ ConfigurationDirector.GetServerPort(),
+ false, // Not a dedicated server
+ "", // No password
+ InputDirector.HostServerType.LAN, // LAN game (Don't submit to Unity's game listings)
+ VersionDirector.GetGameTypeName(),
+ "My Game",
+ "Woot"
+ );
+ }
+
+ serverIP = GUI.TextField(new Rect(350, 300, 300, 50), serverIP);
+ if (GUI.Button(new Rect(10,300,300,50), "Connect to IP"))
+ {
+ // Connect to a game
+ ConfigurationDirector.SetPlayerName(playerName);
+ isSearchingForLANGames = false;
+ masterServerDirector.EnableLANGameSearch(false);
+ InputDirector inputDirector = InputDirector.Get();
+ inputDirector.ConnectToServer(serverIP, ConfigurationDirector.GetServerPort(), "");
+ }
+
+ GUI.Label(new Rect(600,10,100,50), "Name");
+ playerName = GUI.TextField(new Rect(700, 10, 300, 50), playerName);
+
+ isSearchingForLANGames = GUI.Toggle(new Rect(10,150,200,50), isSearchingForLANGames, "Search for LAN games");
+ masterServerDirector.EnableLANGameSearch(isSearchingForLANGames);
+ }
+}
diff --git a/Assets/Scripts/Directors/GameDirector.cs b/Assets/Scripts/Directors/GameDirector.cs
new file mode 100644
index 0000000..9f26a59
--- /dev/null
+++ b/Assets/Scripts/Directors/GameDirector.cs
@@ -0,0 +1,146 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class is responsible for dealing with getting a player set up to begin playing a round of the game.
+///
+public class GameDirector : MonoBehaviour
+{
+ ///
+ /// The one and only input director where all game commands go through
+ ///
+ private InputDirector inputDirector;
+
+ ///
+ /// The one and only GameRules script that applies to the current game
+ ///
+ private GameRules gameRules;
+
+ ///
+ /// Gets the game rules
+ ///
+ ///
+ /// The game rules component
+ ///
+ public GameRules Rules
+ {
+ get { return gameRules; }
+ }
+
+ ///
+ /// Get the current instance of the game director. It does not persist through the application's lifetime.
+ ///
+ static public GameDirector Get()
+ {
+ GameDirector gameDirector = (GameDirector)GameObject.FindObjectOfType(typeof(GameDirector));
+ return gameDirector;
+ }
+
+ #region Unity Events
+
+ // Use this for initialization
+ void Start ()
+ {
+ // If we're a dedicated server, don't render anything
+ if (inputDirector.IsDedicatedServer()) {
+ Camera.main.enabled = false;
+ }
+
+ // Now start the game if we're playing alone
+ if (!inputDirector.IsNetworking())
+ {
+ BeginGame(ConfigurationDirector.GetGameRules());
+ }
+ }
+
+ #endregion
+
+ #region Client and Input Director events
+
+ ///
+ /// This message is sent by the Client component in the InputDirector object after the level has been
+ /// loaded and all communications with the network game channels have been restored. This is code that
+ /// should NOT be occurring in Start() because network communications would still be down at that time.
+ ///
+ void OnNetworkLoadedLevel()
+ {
+ // First off, get our input director. This is in a separate object.
+ inputDirector = InputDirector.Get();
+
+ // In a network game, the server set up its instance of the rules, and starts its game here.
+ // The client will wait from a buffered message from the server with the game rules enumeration.
+ // Then it will start its game.
+ if (inputDirector.IsHosting())
+ {
+ Debug.Log("Game is hot.");
+ string gameRules = ConfigurationDirector.GetGameRules();
+
+ // Inform the server and the clients of the game rules. It's important to do it now before
+ // any other buffered messages (like spawning AI cycles) happens, or else unpredictable things
+ // can happen.
+ inputDirector.BroadcastBufferedCommand("OnDefineGameRules", gameRules);
+ }
+ }
+
+ ///
+ /// This message is sent from the Player component on clients. The originating message is a buffered
+ /// RPC message from the server to all clients right after OnNetworkLoadedlevel for the purpose of
+ /// informing clients what kind of game is being played. Hosts do not get this message.
+ ///
+ ///
+ /// Game rules.
+ ///
+ void OnGameRulesDefined(string gameRules)
+ {
+ // Now begin the game on our instance
+ BeginGame(gameRules);
+ }
+
+ #endregion
+
+ ///
+ /// This is called directly from a solo game or one where you are hosting; it is also called on
+ /// clients after the server tells them what the game rules are. This function will add a component
+ /// to the game director which controls how the game is run. Without calling this, the playfield
+ /// would be empty and nothing would ever happen.
+ ///
+ ///
+ /// The name of the game rules set.
+ ///
+ private void BeginGame(string rulesName)
+ {
+ Debug.Log("in BeginGame with rules: " + gameRules);
+
+ if ("FreeForAll" == rulesName)
+ {
+ gameRules = (GameRules)gameObject.AddComponent();
+ }
+ else
+ {
+ // This should never happen
+ Debug.LogError("Unhandled game rule type: " + gameRules + "!");
+ return;
+ }
+
+ // Send a message to the rules component to begin the game. This is where the scores are
+ // reset, players are spawned, etc. This is the part where this player, whether they be the
+ // host or a client who just joined, creates themselves on the playfield.
+ SendMessage("OnBeginGame");
+ }
+
+ ///
+ /// This message is sent by the UI of this component to terminate a game in progress.
+ ///
+ void OnShutdown()
+ {
+ if (inputDirector.IsHosting()) {
+ inputDirector.UnhostServer();
+ } else {
+ inputDirector.DisconnectFromServer();
+ }
+ // Return to the main menu
+ Application.LoadLevel("Startup");
+ ConsoleDirector.Log("Game session terminated");
+ }
+
+}
diff --git a/Assets/Scripts/GameRules/GameRules.cs b/Assets/Scripts/GameRules/GameRules.cs
new file mode 100644
index 0000000..8717b4b
--- /dev/null
+++ b/Assets/Scripts/GameRules/GameRules.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// The Game Rules class describes the rules for a game; such as: how do players
+/// get points, how are winners chosen, and anything else that is specific to a
+/// type of game.
+///
+public abstract class GameRules : MonoBehaviour
+{
+}
diff --git a/Assets/Scripts/GameRules/GameRules_FFA.cs b/Assets/Scripts/GameRules/GameRules_FFA.cs
new file mode 100644
index 0000000..e1bac76
--- /dev/null
+++ b/Assets/Scripts/GameRules/GameRules_FFA.cs
@@ -0,0 +1,56 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+
+///
+/// This class is responsible for setting up a free-for-all game
+///
+public class GameRules_FFA : GameRules
+{
+ ///
+ /// The one and only input director where all game commands go through
+ ///
+ private InputDirector inputDirector;
+
+ ///
+ /// Our player in the game
+ ///
+ private Player selfPlayer;
+
+ #region Game Director Events
+
+ ///
+ /// This message is sent by the GameDirector component to begin a game. This is where
+ /// the players need to spawn, dynamic obstacles get set up, etc.
+ ///
+ void OnBeginGame()
+ {
+ // Standard initialization for all game rules scripts.
+ inputDirector = InputDirector.Get();
+ selfPlayer = Player.Get();
+
+ // If we're playing offline, then we need to do single-player setup stuff here.
+ if (!inputDirector.IsNetworking())
+ {
+ selfPlayer.gameObject.SendMessage("OnSpawnSpaceship", new Vector3(Random.value * 250 - 500, 0, Random.value * 250 - 500));
+ }
+ // If we're hosting a network game, then we need to decide now where all
+ // the players are going to spawn and spawn them.
+ else if (inputDirector.IsHosting())
+ {
+ // Spawn our own spaceship
+ if (!inputDirector.IsDedicatedServer())
+ {
+ selfPlayer.gameObject.SendMessage("OnSpawnSpaceship", new Vector3(Random.value * 250 - 500, 0, Random.value * 250 - 500));
+ }
+ }
+ else
+ {
+ // If we get here, we're a client. Spawn our first spaceship in this scene.
+ selfPlayer.gameObject.SendMessage("OnSpawnSpaceship", new Vector3(Random.value * 250 - 500, 0, Random.value * 250 - 500));
+ }
+ }
+
+ #endregion
+
+}
diff --git a/Assets/Scripts/GameView.cs b/Assets/Scripts/GameView.cs
new file mode 100644
index 0000000..7623f50
--- /dev/null
+++ b/Assets/Scripts/GameView.cs
@@ -0,0 +1,68 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class is responsible for rendering the game's overlay. We should minimize the number
+/// of OnGUI calls per scene.
+///
+public class GameView : MonoBehaviour
+{
+ public Texture2D plainTex;
+
+ private Server server;
+
+ #region Unity Events
+
+ void Start()
+ {
+ server = Server.Get();
+ }
+
+ void OnGUI()
+ {
+ // TODO: Maintain a spaceship list that only changes in response to messages
+ Spaceship[] spaceships = (Spaceship[])Object.FindObjectsOfType(typeof(Spaceship));
+ foreach (Spaceship s in spaceships)
+ {
+ if (s.Initialized)
+ {
+ PlayerAttributes player = server.GetPlayer(s.playerID);
+ Vector3 screenPos = Camera.main.WorldToScreenPoint(s.transform.position);
+ screenPos.y = Screen.height - screenPos.y;
+
+ // Draw the HP box frame
+ int width = 50;
+ int height = 8;
+ Rect rBox = new Rect(screenPos.x - width/2, screenPos.y - 20 - height/2, width, height);
+ GUI.color = Color.white;
+ GUI.DrawTexture(rBox, plainTex);
+
+ // Draw the name
+ GUI.Label(new Rect(rBox.xMin, rBox.yMin-22, 300,20), player.PlayerName);
+
+ GUI.color = Color.black;
+ rBox.xMin++; rBox.yMin++;
+ rBox.xMax--; rBox.yMax--;
+ GUI.DrawTexture(rBox, plainTex);
+
+ // Now draw the HP
+ GUI.color = Color.green;
+ rBox.xMin++; rBox.yMin++;
+ rBox.xMax--; rBox.yMax--;
+ float w = (float)rBox.width * (float)s.CurrentEnergy / (float)s.maxEnergy;
+ rBox.xMax = rBox.xMin + w;
+ GUI.DrawTexture(rBox, plainTex);
+ }
+ }
+
+ int buttonWidth = 200;
+ int buttonHeight = 50;
+ GUI.color = Color.white;
+ if (GUI.Button(new Rect(20, Screen.height - buttonHeight - 20, buttonWidth, buttonHeight), "Quit"))
+ {
+ SendMessage("OnShutdown");
+ }
+ }
+
+ #endregion
+}
diff --git a/Assets/Scripts/Player.cs b/Assets/Scripts/Player.cs
new file mode 100644
index 0000000..667fcbb
--- /dev/null
+++ b/Assets/Scripts/Player.cs
@@ -0,0 +1,184 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class represents the user directly interacting with this application. It has all the attributes
+/// of the Client class, but also has game-specific traits. Player game elements are created from this
+/// class as well.
+///
+public class Player : Client
+{
+ ///
+ /// The one and only player (this is you). This will exist throughout the application's lifetime.
+ ///
+ static private Player _player;
+
+ ///
+ /// Create the one and only instance of you in this game.
+ ///
+ static public Player Create()
+ {
+ if (null == _player)
+ {
+ InputDirector inputDirector = InputDirector.Create();
+ _player = inputDirector.gameObject.AddComponent();
+ _player.inputDirector = inputDirector;
+ }
+ return _player;
+ }
+
+ ///
+ /// Get the only and only instance of you as a player in the game.
+ ///
+ static public Player Get()
+ {
+ return _player;
+ }
+
+ ///
+ /// The input director cached for convenience
+ ///
+ InputDirector inputDirector;
+ ///
+ /// The self spaceship.
+ ///
+ Spaceship selfSpaceship;
+ ///
+ /// The reported game rules
+ ///
+ string serverGameRules;
+
+ ///
+ /// Gets a value indicating whether this instance is registered with server.
+ ///
+ ///
+ /// true if this instance is registered with server; otherwise, false.
+ ///
+ bool IsRegisteredWithServer { get { return (null != selfID); } }
+
+ #region Unity Events
+
+ // Update is called once per frame
+ void Update ()
+ {
+ if (null != selfSpaceship)
+ {
+ Camera.main.transform.localEulerAngles = new Vector3(90,-selfSpaceship.transform.localEulerAngles.y,0);
+
+ if (Input.GetKeyDown(KeyCode.W)) {
+ selfSpaceship.acceleration = selfSpaceship.MaxAcceleration;
+ }
+ if (Input.GetKeyUp(KeyCode.W)) {
+ selfSpaceship.acceleration = 0;
+ }
+ if (Input.GetKeyDown(KeyCode.S)) {
+ selfSpaceship.acceleration = -selfSpaceship.MaxAcceleration;
+ }
+ if (Input.GetKeyUp(KeyCode.S)) {
+ selfSpaceship.acceleration = 0;
+ }
+ if (Input.GetKeyDown(KeyCode.A)) {
+ selfSpaceship.torque = -selfSpaceship.MaxTorque;
+ }
+ if (Input.GetKeyUp(KeyCode.A)) {
+ selfSpaceship.torque = 0;
+ }
+ if (Input.GetKeyDown(KeyCode.D)) {
+ selfSpaceship.torque = selfSpaceship.MaxTorque;
+ }
+ if (Input.GetKeyUp(KeyCode.D)) {
+ selfSpaceship.torque = 0;
+ }
+ if (Input.GetKeyDown(KeyCode.Space)) {
+ selfSpaceship.Fire();
+ }
+ }
+ }
+
+ #endregion
+
+ #region Input Director and Application Events
+
+ ///
+ /// This is called to spawn a spaceship in the game at the specified position
+ ///
+ void OnSpawnSpaceship(Vector3 pos)
+ {
+ Debug.Log("in OnSpawnSpaceship at " + pos.ToString() + " - selfID = " + selfID);
+ // Create the cycle and assign its name and color
+ GameObject spaceshipPrefab = (GameObject)Resources.Load("Prefabs/PlayerShip");
+ GameObject selfSpaceshipObject = inputDirector.InstantiateObject(spaceshipPrefab, pos, spaceshipPrefab.transform.rotation, System.Convert.ToInt32(selfID));
+ selfSpaceship = selfSpaceshipObject.GetComponent();
+ selfSpaceshipObject.networkView.RPC("OnSetSpaceshipAttributes", RPCMode.AllBuffered,
+ selfID, ConfigurationDirector.GetPlayerName() + "'s ship");
+
+ // Now attach our camera to it.
+ Camera.main.transform.parent = selfSpaceshipObject.transform;
+ Camera.main.transform.localPosition = new Vector3(0,150,0);
+ }
+
+ ///
+ /// Called on a client when the client is disconnected form the server
+ ///
+ ///
+ /// Disconnection mode.
+ ///
+ void OnDisconnectionFromServer(NetworkDisconnection disconnectionMode)
+ {
+ // Load the scene that shows the player why they can't connect
+ ConnectionFailureSceneDirector.Initialize("Lost connection to server", "Startup");
+ }
+
+ #endregion
+
+ #region RPCs
+
+ ///
+ /// This message is sent from a server to either itself or a client after they've registered
+ /// with the server. This is not a buffered call, and it's a one-time call per game instance.
+ ///
+ ///
+ /// The new player ID
+ ///
+ [RPC]
+ public override void OnRegisteredWithServer(string newID)
+ {
+ Debug.Log("OnRegisteredWithServer called. New player ID is " + newID + ". Rules are " + serverGameRules);
+
+ // Assign our new ID
+ selfID = newID;
+
+ // Now notify all players that we have successfully registered so that they know we exist.
+ inputDirector.BroadcastBufferedCommand("OnPlayerRegistered", newID, ConfigurationDirector.GetPlayerName());
+
+ // If this is the host, then we must enter the game now.
+ if (inputDirector.IsHosting())
+ {
+ inputDirector.LoadScene("Game");
+ }
+ else
+ {
+ // Don't tell the game director until we're registered
+ if (IsRegisteredWithServer && null != serverGameRules)
+ {
+ GameDirector.Get().SendMessage("OnGameRulesDefined", serverGameRules);
+ }
+ }
+ }
+
+ // This is a buffered message sent from the server to all clients to tell them what
+ // kind of game is being played. This must always be the first buffered RPC to be sent
+ // after a level has been loaded so that the players know the "rules" of the game.
+ [RPC]
+ void OnDefineGameRules(string gameRules)
+ {
+ serverGameRules = gameRules;
+ // Don't tell the game director until we're registered
+ if (IsRegisteredWithServer && null != serverGameRules)
+ {
+ GameDirector.Get().SendMessage("OnGameRulesDefined", serverGameRules);
+ }
+ }
+
+ #endregion
+}
diff --git a/Assets/Scripts/Projectile.cs b/Assets/Scripts/Projectile.cs
new file mode 100644
index 0000000..b47cc3f
--- /dev/null
+++ b/Assets/Scripts/Projectile.cs
@@ -0,0 +1,59 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class represents a projectile fired from a ship.
+///
+public class Projectile : MonoBehaviour {
+
+ public float lifeTime = 2.0f;
+ public float energyCost = 10.0f;
+ public float damage = 10.0f;
+
+ InputDirector inputDirector;
+
+ ///
+ /// The time this projectile was fired
+ ///
+ float startTime;
+
+ #region Unity Events
+
+ // Use this for initialization
+ void Start ()
+ {
+ // Cache the input director
+ inputDirector = InputDirector.Get();
+ // Set the start time
+ startTime = Time.time;
+ }
+
+ // Update is called once per frame
+ void Update ()
+ {
+ // Only hosts can destroy projectiles
+ if (inputDirector.IsHosting())
+ {
+ if (Time.time > startTime + lifeTime) {
+ inputDirector.DestroyObject(gameObject);
+ }
+ }
+ }
+
+ void OnTriggerEnter(Collider other)
+ {
+ if (inputDirector.IsHosting() // We're hosting the game
+ && other.tag == "Spaceship" // The other object is a spaceship
+ && networkView.group != other.networkView.group // The other object did not come fromm us
+ )
+ {
+ // Tell the spaceship what happened
+ Spaceship s = other.GetComponent();
+ s.OnHitByProjectile(this);
+ // We always destroy ourselves on contact
+ inputDirector.DestroyObject(gameObject);
+ }
+ }
+
+ #endregion
+}
diff --git a/Assets/Scripts/Spaceship.cs b/Assets/Scripts/Spaceship.cs
new file mode 100644
index 0000000..f2eca64
--- /dev/null
+++ b/Assets/Scripts/Spaceship.cs
@@ -0,0 +1,166 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+
+///
+/// This class represents a spaceship on the playfield. Although there is only one per player,
+/// this is not to be confused with the Player component itself.
+///
+public class Spaceship : MonoBehaviour
+{
+ [HideInInspector]
+ public float acceleration = 0;
+ [HideInInspector]
+ public float torque = 0;
+
+ InputDirector inputDirector;
+ Transform t;
+ Rigidbody rb;
+
+ ///
+ /// This is assigned by OnSetSpaceshipAttributes. We only use it for debugging
+ /// in Unity; by looking at this value in the inspector, we can be sure of the
+ /// owning player for this ship.
+ ///
+ public string playerID;
+
+ public float maxAcceleration = 4000;
+ public float maxTorque = 1500;
+
+ public float maxVelocity = 100;
+ public float sqrMaxVelocity = 100 * 100;
+
+ public float maxEnergy = 1000;
+ public float energyRestoreRate = 50;
+
+ public GameObject bulletPrefab;
+
+ public Transform shipTransform;
+
+ public bool Initialized { get { return (null != playerID); } }
+
+ public float MaxAcceleration { get { return maxAcceleration; } }
+ public float MaxTorque { get { return maxTorque; } }
+
+ public float currentEnergy = 1000; // Public for debugging only
+ public float CurrentEnergy { get { return currentEnergy; } }
+
+
+ #region Unity Events
+
+ ///
+ /// Ensure that currentEnergy stays in sync with the other players. This is one
+ /// abstraction that cannot be hidden with an underlying script.
+ ///
+ ///
+ /// Stream.
+ ///
+ ///
+ /// Info.
+ ///
+ void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
+ {
+ if (stream.isWriting) {
+ float energy = currentEnergy;
+ stream.Serialize(ref energy);
+ } else {
+ float energy = 0;
+ stream.Serialize(ref energy);
+ currentEnergy = energy;
+ }
+ }
+
+ // Use this for initialization
+ void Start ()
+ {
+ inputDirector = InputDirector.Get();
+ t = gameObject.transform; // Cache the transform
+ rb = gameObject.rigidbody; // Cache the rigidbody
+ rb.angularDrag = 5.0f;
+ }
+
+ void Update ()
+ {
+ float newEnergy = currentEnergy + energyRestoreRate * Time.deltaTime;
+ if (newEnergy > maxEnergy) {
+ newEnergy = maxEnergy;
+ }
+ if (currentEnergy < newEnergy)
+ {
+ currentEnergy = newEnergy;
+ }
+ }
+
+ void FixedUpdate ()
+ {
+ // Linear movement
+ if (acceleration != 0) {
+ rb.AddForce(shipTransform.forward * rigidbody.mass * acceleration * Time.fixedDeltaTime);
+ }
+
+ // No drag...but in case we ever change our minds...
+ //float idealDrag = maxAcceleration / terminalVelocity;
+ //rigidbody.drag = idealDrag / ( idealDrag * Time.fixedDeltaTime + 1 );
+
+ // Angular movement
+ if (torque != 0) {
+ rigidbody.AddTorque(Vector3.up * rigidbody.mass * torque * Time.fixedDeltaTime);
+ }
+
+ // Cap velocity
+ Vector3 vel = rb.velocity;
+ if (vel.sqrMagnitude > sqrMaxVelocity) {
+ rb.velocity = vel.normalized * maxVelocity;
+ }
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// This message is sent by a Projectile object when it collides with this object
+ ///
+ ///
+ /// The projectile
+ ///
+ public void OnHitByProjectile(Projectile p)
+ {
+ // Reduce our energy by the projectile damage amount
+ currentEnergy = currentEnergy - p.damage;
+ if (currentEnergy < 0) {
+ Debug.Log("Boom!");
+ // TODO: Forward this event to the rules component for further game-rules-level processing
+ }
+ // TODO: Forward this event to the rules component for further game-rules-level processing
+ }
+
+ #endregion
+
+ #region RPCs
+
+ [RPC]
+ void OnSetSpaceshipAttributes(string owningPlayerID, string name)
+ {
+ Debug.Log("in OnSetSpaceshipAttributes: " + owningPlayerID + " " + name);
+ playerID = owningPlayerID;
+ gameObject.name = name;
+ }
+
+ #endregion
+
+ ///
+ /// This function is called by the Player class to fire a projectile.
+ ///
+ public void Fire()
+ {
+ Projectile p = bulletPrefab.GetComponent();
+ if (currentEnergy > p.energyCost)
+ {
+ GameObject bullet = (GameObject)inputDirector.InstantiateObject(bulletPrefab, t.position + shipTransform.forward * 3.0f, bulletPrefab.transform.rotation, networkView.group);
+ bullet.rigidbody.AddForce(shipTransform.forward * bullet.rigidbody.mass * 4000.0f);
+ currentEnergy -= p.energyCost;
+ }
+ }
+
+}
diff --git a/Assets/Standard Assets/Scripts/Client.cs b/Assets/Standard Assets/Scripts/Client.cs
new file mode 100644
index 0000000..72a193c
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/Client.cs
@@ -0,0 +1,175 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+
+///
+/// This class represents the user's existence in the application. All non-application-specific player management
+/// happens here. This component must be a subclass of the actual application-specific user component.
+///
+public abstract class Client : MonoBehaviour
+{
+ ///
+ /// The input director that we use to communicate with the world
+ ///
+ protected InputDirector inputDirector;
+
+ ///
+ /// The server. Even if we are hosting, this object maintains the player list and
+ /// is responsible for server "registration" management; that is, players formally
+ /// requesting to join a game once connected.
+ ///
+ protected Server server;
+
+ ///
+ /// Our own unique player ID
+ ///
+ protected string selfID;
+
+ #region Properties
+
+ ///
+ /// Returns your unique ID
+ ///
+ public string ID { get { return selfID; } }
+
+ #endregion
+
+ #region Abstracts
+
+ ///
+ /// This message is sent from a server to either itself or a client after they've registered
+ /// with the server. This is not a buffered call, and it's a one-time call per game instance.
+ ///
+ ///
+ /// The new player ID
+ ///
+ [RPC]
+ public abstract void OnRegisteredWithServer(string newID);
+
+ #endregion
+
+ #region Unity Events
+
+ // Use this for initialization
+ void Awake ()
+ {
+ inputDirector = InputDirector.Get();
+ server = Server.Get();
+ }
+
+ void OnLevelWasLoaded(int level)
+ {
+ // Allow receiving data again
+ Network.isMessageQueueRunning = true;
+ // Now the level has been loaded and we can start sending out data to clients
+ Network.SetSendingEnabled(0, true);
+ // Notify all objects that the level was loaded
+ foreach (GameObject o in FindObjectsOfType(typeof(GameObject)))
+ o.SendMessage("OnNetworkLoadedLevel", SendMessageOptions.DontRequireReceiver);
+ }
+
+ #endregion
+
+ #region Input Director and Application Events
+
+ ///
+ /// This message is sent by the Input Director component if this application
+ /// has just put up a network host.
+ ///
+ ///
+ /// Error.
+ ///
+ void OnHostServerComplete(NetworkConnectionError error)
+ {
+ // Now handle player stuff
+ if (NetworkConnectionError.NoError == error)
+ {
+ // Reset our own ID
+ selfID = null;
+ // Register with ourselves
+ server.Register();
+ // We know we're good to go, so lets just call this ourselves.
+ OnRegisteredWithServer(networkView.owner.ToString());
+ }
+ }
+
+ ///
+ /// This message is sent by the Input Director component if this application
+ /// has just connected to a server
+ ///
+ ///
+ /// Error.
+ ///
+ void OnConnectToServerComplete(NetworkConnectionError error)
+ {
+ // Now handle player stuff
+ if (NetworkConnectionError.NoError == error)
+ {
+ // Reset our own ID
+ selfID = null;
+ // Register with the server. This will send out an RPC call.
+ server.Register();
+ }
+ }
+
+ ///
+ /// Called on a client when the client is disconnected form the server
+ ///
+ ///
+ /// Disconnection mode.
+ ///
+ void OnDisconnectionFromServer(NetworkDisconnection disconnectionMode)
+ {
+ selfID = null;
+ }
+
+ #endregion
+
+ #region RPCs
+
+ ///
+ /// This message is sent from a server to a client if they were rejected by the server
+ ///
+ ///
+ /// Reason.
+ ///
+ [RPC]
+ void OnServerRegistrationFailed(string reason)
+ {
+ // Disconnect
+ inputDirector.DisconnectFromServer();
+ // Now load the scene that shows the player why they can't connect
+ ConnectionFailureSceneDirector.Initialize("Registration failed: " + reason, "Startup");
+ }
+
+ ///
+ /// This message is sent from a server to a client to load a level
+ ///
+ ///
+ /// Level.
+ ///
+ ///
+ /// Level prefix.
+ ///
+ [RPC]
+ void OnLoadNetworkLevel(string level, int levelPrefix)
+ {
+ // http://unity3d.com/support/documentation/Components/net-NetworkLevelLoad.html
+ Debug.Log("In OnLoadNetworkLevel");
+
+ // There is no reason to send any more data over the network on the default channel,
+ // because we are about to load the level, thus all those objects will get deleted anyway
+ Network.SetSendingEnabled(0, false);
+
+ // We need to stop receiving because first the level must be loaded first.
+ // Once the level is loaded, rpc's and other state update attached to objects in the level are allowed to fire
+ Network.isMessageQueueRunning = false;
+
+ // All network views loaded from a level will get a prefix into their NetworkViewID.
+ // This will prevent old updates from clients leaking into a newly created scene.
+ Network.SetLevelPrefix(levelPrefix);
+ Application.LoadLevel(level);
+ }
+
+ #endregion
+}
diff --git a/Assets/Standard Assets/Scripts/ConfigurationDirector.cs b/Assets/Standard Assets/Scripts/ConfigurationDirector.cs
new file mode 100644
index 0000000..2a6db3f
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/ConfigurationDirector.cs
@@ -0,0 +1,82 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class manages all configurable settings for a game. Everything from
+/// the player profile to game hosting settings are handled here. All the current
+/// values are hard coded for demonstration purposes; you would replace these with
+/// PlayerPrefs function calls in the long term.
+///
+static public class ConfigurationDirector
+{
+ ///
+ /// Gets the max player count.
+ ///
+ ///
+ /// The player count.
+ ///
+ static public int GetMaxPlayerCount()
+ {
+ return 32;
+ }
+
+ ///
+ /// Gets the server port.
+ ///
+ ///
+ /// The server port.
+ ///
+ static public int GetServerPort()
+ {
+ // Host on this port
+ return 21182;
+ }
+
+ ///
+ /// Gets the name of the player.
+ ///
+ ///
+ /// The player name.
+ ///
+ static public string GetPlayerName()
+ {
+ return PlayerPrefs.GetString("PlayerName", "Player");
+ }
+
+ ///
+ /// Sets the name of the player.
+ ///
+ ///
+ /// Value.
+ ///
+ static public void SetPlayerName(string value)
+ {
+ PlayerPrefs.SetString("PlayerName", value);
+ }
+
+ ///
+ /// Gets the game rules. This is expressed as a simple named string, though you could
+ /// opt to make it an enumeration.
+ ///
+ ///
+ /// The game rules.
+ ///
+ static public string GetGameRules()
+ {
+ // The name "FreeForAll" has no special significance to this engine; it's simply
+ // the name chosen for this specific video game.
+ return "FreeForAll";
+ }
+
+ ///
+ /// Gets the multicast port for LAN game searching.
+ ///
+ ///
+ /// The multicast port.
+ ///
+ static public int GetMulticastPort()
+ {
+ // Search for games on this port using UDP protocol
+ return 22043;
+ }
+}
diff --git a/Assets/Standard Assets/Scripts/ConnectionFailureSceneDirector.cs b/Assets/Standard Assets/Scripts/ConnectionFailureSceneDirector.cs
new file mode 100644
index 0000000..b6b6d2c
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/ConnectionFailureSceneDirector.cs
@@ -0,0 +1,52 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class manages what happens in the "ConnectionFailureScene" scene.
+///
+public class ConnectionFailureSceneDirector : MonoBehaviour
+{
+ ///
+ /// The reason for the connection failure
+ ///
+ private string reason;
+
+ ///
+ /// This is called from anywhere in the program when a connection to the host
+ /// has failed.
+ ///
+ ///
+ /// The reason for the failure.
+ ///
+ ///
+ /// The scene to go to after the user clicks the Back button.
+ ///
+ static public void Initialize(string reason, string subsequentScene)
+ {
+ PlayerPrefs.SetString("ConnectionFailureSceneReason", reason);
+ PlayerPrefs.SetString("ConnectionFailureSubsequentScene", subsequentScene);
+ Application.LoadLevel("ConnectionFailureScene");
+ }
+
+ // Use this for initialization
+ void Start ()
+ {
+ // Cache the reason for the disconnection
+ reason = PlayerPrefs.GetString("ConnectionFailureSceneReason");
+ }
+
+ void OnGUI()
+ {
+ // Show the reason for the connection failure
+ GUI.Label(new Rect(0,0,Screen.width,Screen.height / 2), reason);
+
+ // Back button takes the player back to the main menu
+ float sx = Screen.width;
+ float sy = Screen.height;
+ if (GUI.Button(new Rect(sx * 0.425f, sy * 0.55f, sx * 0.15f, sy * 0.1f), "Back"))
+ {
+ // Go go to the next scene
+ Application.LoadLevel(PlayerPrefs.GetString("ConnectionFailureSubsequentScene"));
+ }
+ }
+}
diff --git a/Assets/Standard Assets/Scripts/ConsoleDirector.cs b/Assets/Standard Assets/Scripts/ConsoleDirector.cs
new file mode 100644
index 0000000..e48eb16
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/ConsoleDirector.cs
@@ -0,0 +1,15 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This is the console director. Right now it's only good for logging messages.
+/// Eventually you could use this to log messages in a global console. Consider
+/// this class a very "under construction" class.
+///
+static public class ConsoleDirector
+{
+ static public void Log (string value)
+ {
+ Debug.Log(value);
+ }
+}
diff --git a/Assets/Standard Assets/Scripts/InputDirector.cs b/Assets/Standard Assets/Scripts/InputDirector.cs
new file mode 100644
index 0000000..ded6a11
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/InputDirector.cs
@@ -0,0 +1,807 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// The input director is an object that persists throughout the lifetime of this Unity application.
+/// Its purpose is to hide the abstraction of the application being a local or a network game from
+/// areas of the application which should not care:
+///
+/// Benefits:
+///
+/// - When creating or destroying objects, the application does not need to care about the particulars
+/// of ensuring the action is propogated throughout the network.
+///
+/// - When sending messages to objects, the application does not need to care about the particulars
+/// of ensuring the action is propogated throughout the network.
+///
+/// - MonoBehavior events like players connecting and disconnecting are all managed by this one object,
+/// and can be rebroadcast to other scripts in the same game object that the InputDirector is attached to.
+///
+///
+[RequireComponent (typeof (NetworkView))]
+[RequireComponent (typeof (MasterServerDirector))]
+[RequireComponent (typeof (Server))]
+public class InputDirector : MonoBehaviour
+{
+ ///
+ /// This describes the state of the input director and the game.
+ ///
+ public enum InputTransportMode
+ {
+ ///
+ /// Nothing is going on. This is true when we're in the main menu.
+ ///
+ Idle,
+
+ ///
+ /// A local instance of the game is in progress; no network activity
+ /// will be taking place.
+ ///
+ Local,
+
+ ///
+ /// This instance of the game is acting as the host
+ ///
+ Server,
+
+ ///
+ /// This instance of the game is connecting to a host
+ ///
+ Connecting,
+
+ ///
+ /// This instance of the game is connected to a network host
+ ///
+ Client
+
+ }
+
+ ///
+ /// This describes the type of server being hosted
+ ///
+ public enum HostServerType
+ {
+ ///
+ /// The server is not being advertised to outside players who are looking for games.
+ ///
+ Private,
+
+ ///
+ /// The server is being advertised, but only on the LAN. This should be used for
+ /// LAN parties or when all the players are on the same network.
+ ///
+ LAN,
+
+ ///
+ /// This is a public game open to everyone on the Internet.
+ ///
+ Public
+ };
+
+ ///
+ /// The master server director which is responsible for acquiring online game listings
+ ///
+ private MasterServerDirector masterServerDirector;
+ ///
+ /// The current state of the input director
+ ///
+ private InputTransportMode mode = InputTransportMode.Idle;
+ ///
+ /// The prefix for the next level to load (prevents network messages from one
+ /// level trickling into another)
+ ///
+ private int nextLevelPrefix = 0;
+
+ ///
+ /// True if our input transport mode is Server and we're not actually engaging in the game
+ /// (but we still need to run it)
+ ///
+ private bool isDedicatedServer;
+ ///
+ /// The state of how we interact with the master game-search server
+ ///
+ private HostServerType gameHostType;
+
+ ///
+ /// The type name of the game; usually the title followed by version (e.g. Vengeance2.3)
+ ///
+ private string gameTypeName;
+ ///
+ /// The name of the game as it appears in game searches
+ ///
+ private string gameName;
+ ///
+ /// A description of the game as it appears in game searches
+ ///
+ private string gameComment;
+
+ ///
+ /// The one and only input director. This will exist throughout the application's lifetime.
+ ///
+ static private InputDirector _inputDirector;
+
+ ///
+ /// Returns the input director in the scene. If it does not exist, it is created.
+ /// The input director object persists through all scenes.
+ ///
+ static public InputDirector Create()
+ {
+ if (null == _inputDirector) {
+ GameObject o = new GameObject();
+ o.name = "InputDirector";
+ // Ensure this object is never destroyed
+ DontDestroyOnLoad(o);
+ // Cache the director
+ _inputDirector = o.AddComponent();
+ }
+ return _inputDirector;
+ }
+
+ ///
+ /// Returns the input director in the scene. If it does not exist, it is created.
+ /// The input director object persists through all scenes.
+ ///
+ static public InputDirector Get()
+ {
+ return _inputDirector;
+ }
+
+ #region Properties
+
+ ///
+ /// Determines whether this instance is hosting a game.
+ ///
+ ///
+ /// true if this instance is hosting a game; otherwise, false.
+ ///
+ public bool IsHosting()
+ {
+ bool result = false;
+ // If we are in a local one-player game (not on the internet), or are the server of an
+ // internet game, then we consider ourselves the host.
+ if (InputTransportMode.Local == mode || InputTransportMode.Server == mode)
+ {
+ result = true;
+ }
+ return result;
+ }
+
+ ///
+ /// Determines whether this instance is network-enabled.
+ ///
+ ///
+ /// true if this instance is network-enabled; otherwise, false.
+ ///
+ public bool IsNetworking()
+ {
+ bool result = true;
+ // Simple test. We're always networking unless we're a local one-player game.
+ if (InputTransportMode.Local == mode)
+ {
+ result = false;
+ }
+ return result;
+ }
+
+ ///
+ /// Determines whether the specified object belongs to our instance of the game.
+ ///
+ ///
+ /// true if this game object belongs to us for controlling. This only applies to objects with networkView components being used.
+ ///
+ ///
+ /// If set to true o.
+ ///
+ public bool IsOurs(GameObject o)
+ {
+ if (IsNetworking()) {
+ return o.networkView.isMine;
+ } else {
+ // If we're not networked, we must be hosting a one-player game...so the object o is always ours.
+ return true;
+ }
+ }
+
+ ///
+ /// Determines whether this instance is running a dedicated server.
+ ///
+ ///
+ /// true if this instance is a dedicated server; otherwise, false.
+ ///
+ public bool IsDedicatedServer()
+ {
+ if (InputTransportMode.Server == mode) {
+ return isDedicatedServer;
+ } else {
+ // If we're not a server, we're not dedicated, period.
+ return false;
+ }
+ }
+
+ ///
+ /// Gets the input transport mode. Refer to the enumeration for possible values.
+ ///
+ ///
+ /// The mode.
+ ///
+ public InputTransportMode GetMode()
+ {
+ return mode;
+ }
+
+ // This function changes the mode of the input director
+ public void SetMode(InputTransportMode value)
+ {
+ // Unhost if we're currently hosting but the input mode is changing
+ if (InputTransportMode.Server == mode && value != mode) {
+ UnhostServer();
+ }
+
+ // TODO: Check the existing mode and throw exceptions if not supported.
+ // For example, going from Idle to Client without Connecting first shouldn't
+ // be possible.
+ SetModeInternal(value);
+ }
+
+ ///
+ /// Gets the IP address from a given client ID
+ ///
+ ///
+ /// The IP address.
+ ///
+ ///
+ /// I.
+ ///
+ public string GetIPAddress(string ID)
+ {
+ foreach (NetworkPlayer p in Network.connections) {
+ if (p.ToString() == ID) {
+ return p.ipAddress;
+ }
+ }
+ return "";
+ }
+
+ #endregion
+
+ #region Internal methods
+
+ private void SetModeInternal(InputTransportMode value)
+ {
+ mode = value;
+ Debug.Log("InputDirector mode is now " + value.ToString());
+ }
+
+ #endregion
+
+ #region Unity Events
+
+ public void Start()
+ {
+ // First, add the ability for this object to communicate with a master game server
+ // to find other players online.
+ masterServerDirector = GetComponent();
+ // Now turn off state synchronization because it does not apply to this object.
+ // See http://docs.unity3d.com/Documentation/Components/net-StateSynchronization.html for what
+ // it should apply to.
+ networkView.stateSynchronization = NetworkStateSynchronization.Off;
+ }
+
+ ///
+ /// This is called by Unity when the network server has initialized.
+ ///
+ void OnServerInitialized()
+ {
+ Debug.Log("Server initialized for host type " + gameHostType.ToString() + "." );
+ // Update our mode
+ SetModeInternal(InputTransportMode.Server);
+ // Register the game on Unity's master server
+ if (HostServerType.Private != gameHostType) {
+ masterServerDirector.RegisterHost(gameTypeName, gameName, gameComment, isDedicatedServer, (HostServerType.Public == gameHostType) ? true : false);
+ }
+ // Let all components know the server is initialized
+ gameObject.SendMessage("OnHostServerComplete", NetworkConnectionError.NoError, SendMessageOptions.DontRequireReceiver);
+ }
+
+ ///
+ /// This is called by Unity when a player connects to a server. Only servers get this message.
+ ///
+ ///
+ /// The player who connected.
+ ///
+ void OnPlayerConnected(NetworkPlayer player)
+ {
+ // player.ToString() is the unique ID of the player. When printing NetworkPlayer.ToString()
+ // you will see a number, but we should not assume it will always be a number.
+ Debug.Log("Player " + player.ToString() + " connected from " + player.ipAddress + ":" + player.port + ".");
+ // Let all components know a player connected
+ gameObject.SendMessage("OnPlayerConnectedToServer", player, SendMessageOptions.DontRequireReceiver);
+ // TODO: Anything else?
+ }
+
+ ///
+ /// This is called by Unity when a player disconnects from a server. Only servers get this message.
+ ///
+ ///
+ /// The player who disconnected.
+ ///
+ void OnPlayerDisconnected(NetworkPlayer player)
+ {
+ // player.ToString() is the unique ID of the player. When printing NetworkPlayer.ToString()
+ // you will see a number, but we should not assume it will always be a number.
+ Debug.Log("Player " + player.ToString() + " disconnected.");
+
+ // Let all components know a player disconnected.
+ gameObject.SendMessage("OnPlayerDisconnectedFromServer", player, SendMessageOptions.DontRequireReceiver);
+
+ // Delete all player objects and RPC buffer entries. This will also destroy them for all other players.
+ Network.RemoveRPCs(player);
+ Network.DestroyPlayerObjects(player);
+
+ // TODO: Anything else?
+ }
+
+ ///
+ /// This is called by Unity when we, a client, failed to connect to a remote server.
+ ///
+ ///
+ /// The error.
+ ///
+ void OnFailedToConnect(NetworkConnectionError error)
+ {
+ Debug.Log("Failed to connect to server!");
+ SetModeInternal(InputTransportMode.Idle);
+ // Shut down our connection
+ DisconnectFromServer();
+ // Let all components know what happened
+ SendMessage("OnConnectToServerComplete", error, SendMessageOptions.DontRequireReceiver);
+ }
+
+ ///
+ /// This is called by Unity when we, a client, connected to the remote server. Only clients get this message.
+ ///
+ void OnConnectedToServer()
+ {
+ // Update our mode
+ SetModeInternal(InputTransportMode.Client);
+ // Let all components know what happened. We don't need to do anything here.
+ SendMessage("OnConnectToServerComplete", NetworkConnectionError.NoError, SendMessageOptions.DontRequireReceiver);
+ }
+
+ ///
+ /// This is called by Unity when we, a client, were disconnected from the remote server.
+ /// It can also be called when the remote server is shut down. Only clients get this message.
+ ///
+ ///
+ /// Disconnection mode.
+ ///
+ void OnDisconnectedFromServer(NetworkDisconnection disconnectionMode)
+ {
+ Debug.Log("Disconnected from the server.");
+ SetModeInternal(InputTransportMode.Idle);
+ // TODO: Better handling per
+ // http://unity3d.com/support/documentation/ScriptReference/Network.OnDisconnectedFromServer.html
+ // Let all components know what happened
+ SendMessage("OnDisconnectionFromServer", disconnectionMode, SendMessageOptions.DontRequireReceiver);
+ }
+
+ #endregion
+
+ #region Connection methods
+
+ ///
+ /// This function will host a server. The caller specifies the max number of players
+ /// and the listening port. This will notify the notification object when the game is
+ /// hosted.
+ ///
+ ///
+ /// The maximum number of connections allowed. This is typically the maximum player count.
+ ///
+ ///
+ /// Port we listen for connections on
+ ///
+ ///
+ /// True if this is a dedicated server.
+ ///
+ ///
+ /// Server password.
+ ///
+ ///
+ /// Host scope (see the enumeration for possible values)
+ ///
+ ///
+ /// The name of the type of game (typically the game's title followed by version number)
+ ///
+ ///
+ /// The name of the hosted game (e.g. "Ted's game")
+ ///
+ ///
+ /// A description of the game
+ ///
+ public void HostServer(int connections, int listenPort, bool dedicatedServer,
+ string password, HostServerType hostType, string typeName, string name, string comment)
+ {
+ if (InputTransportMode.Idle == mode)
+ {
+ bool useNat = !Network.HavePublicAddress();
+ isDedicatedServer = dedicatedServer;
+ gameHostType = hostType;
+ gameTypeName = typeName;
+ gameName = name;
+ gameComment = comment;
+ // Although we're hosting, set our status to Connecting because doing anything else
+ // right now would be invalid.
+ SetModeInternal(InputTransportMode.Connecting);
+ Network.InitializeSecurity();
+ Network.incomingPassword = password;
+ NetworkConnectionError result = Network.InitializeServer(connections, listenPort, useNat);
+ if (NetworkConnectionError.NoError == result)
+ {
+ // This might not mean the server is initialized; it could mean it's initializing. We want
+ // to wait for a OnServerInitialized message.
+ Debug.Log("Network.InitializeServer was successful");
+ }
+ else {
+ // Unity doesn't send messages for failing to initialize the server, so we'll send one.
+ SendMessage("OnHostServerComplete", result);
+ // TODO: Throw an exception?
+ }
+ }
+ else {
+ // TODO: Throw an exception
+ }
+ }
+
+ ///
+ /// This function will unhost a server
+ ///
+ public void UnhostServer()
+ {
+ if (InputTransportMode.Server == mode) {
+ // Remove from the Unity master server game list
+ masterServerDirector.UnregisterHost();
+ // Now disconnect
+ Network.Disconnect();
+ // Update our mode
+ SetModeInternal(InputTransportMode.Idle);
+ }
+ else {
+ // TODO: Throw an exception
+ }
+ }
+
+ ///
+ /// This function will have the game instance attempt to connect to a server
+ ///
+ ///
+ /// The server's network IP address
+ ///
+ ///
+ /// The port of the server listening for connections
+ ///
+ ///
+ /// Server password.
+ ///
+ public void ConnectToServer(string IP, int remotePort, string password)
+ {
+ // Only do this if we're idle. If we were connected to a server, we should have disconnected by now.
+ if (InputTransportMode.Idle == mode)
+ {
+ SetModeInternal(InputTransportMode.Connecting);
+ NetworkConnectionError result = Network.Connect(IP, remotePort, password);
+ if (NetworkConnectionError.NoError == result)
+ {
+ // This might not mean the connection is established; it could mean it's initializing. We want
+ // to wait for a OnConnectedToServer message.
+ Debug.Log("Network.Connect was successful.");
+ }
+ else {
+ // TODO: Throw an exception?
+ }
+ }
+ else {
+ // TODO: Throw an exception?
+ }
+ }
+
+ ///
+ /// This function will disconnect us from the server if we're still connected
+ ///
+ public void DisconnectFromServer()
+ {
+ if (InputTransportMode.Connecting == mode || InputTransportMode.Client == mode)
+ {
+ Network.Disconnect();
+ // Update our mode
+ SetModeInternal(InputTransportMode.Idle);
+ }
+ }
+
+ ///
+ /// This function is called by the server to disconnect a client
+ ///
+ ///
+ /// The client's ID.
+ ///
+ public void DisconnectClientFromServer(string ID)
+ {
+ for (var i=0; i < Network.connections.Length; i++) {
+ if (Network.connections[i].ToString() == ID) {
+ Network.CloseConnection(Network.connections[i],true);
+ return;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Server and Client Methods
+
+ ///
+ /// This function will create a new object in the game based on a prefab
+ ///
+ ///
+ /// The created object.
+ ///
+ ///
+ /// The prefab from which to create the object.
+ ///
+ ///
+ /// The position for the new object.
+ ///
+ ///
+ /// The rotation for the new object.
+ ///
+ ///
+ /// The network group to assign the object to. See the Unity documentation for more info. If you're
+ /// not sure what to put here or don't plan on grouping network objects, just leave this value 0.
+ ///
+ public GameObject InstantiateObject(GameObject prefab, Vector3 position, Quaternion rotation, int group)
+ {
+ GameObject result = null;
+ switch (mode) {
+ case InputTransportMode.Local:
+ // Regular instantiation
+ result = (GameObject)GameObject.Instantiate(prefab, position, rotation);
+ break;
+ case InputTransportMode.Server:
+ case InputTransportMode.Client:
+ // Network instantiation. This object will appear in everyone's world.
+ result = (GameObject)Network.Instantiate(prefab, position, rotation, group);
+ break;
+ default:
+ // Not supported
+ // TODO: Throw an exception
+ break;
+ }
+ return result;
+ }
+
+ ///
+ /// This function will destroy a game object, regardless of whether this application owns the object.
+ ///
+ ///
+ /// Doomed object.
+ ///
+ public void DestroyObject(GameObject doomedObject)
+ {
+ switch (mode) {
+ case InputTransportMode.Local:
+ // Regular destruction
+ Destroy(doomedObject);
+ break;
+ case InputTransportMode.Server:
+ case InputTransportMode.Client:
+ // Network destruction
+ // TODO: Figure out why When when destroying client objects do we still get messages
+ // like "View ID AllocatedID: 50 not found during lookup. Strange behaviour may occur"?
+ // Probably because I pulled the rug from right under the client's feet and they're trying
+ // to send state information. I tried sending an RPC first, but Destroy seems to have a higher
+ // priority and completes before my RPC gets out. Need to confirm the right way to destroy
+ // objects that were instantiated by other players.
+ Network.Destroy(doomedObject);
+ break;
+ default:
+ // Not supported
+ // TODO: Throw an exception
+ break;
+ }
+ }
+
+ ///
+ /// This function will send a buffered command to an object. In a local game, a message
+ /// will be sent. In a network game, an RPC call will be made. In a network game,
+ /// the object should have been created with InputDirector.InstantiateObject()
+ ///
+ ///
+ /// The receiving object.
+ ///
+ ///
+ /// The command string.
+ ///
+ ///
+ /// Additional arguments.
+ ///
+ public void SendBufferedCommand(GameObject receiver, string command, params object[] args)
+ {
+ switch (mode) {
+ case InputTransportMode.Local:
+ // Process the command locally. The receiver is required to handle the message.
+ if (args.Length == 1) {
+ receiver.SendMessage(command, args);
+ } else {
+ Debug.LogError("SendBufferedCommand called in a local session with multiple arguments!");
+ }
+ break;
+ case InputTransportMode.Server:
+ case InputTransportMode.Client:
+ // Use an RPC command. This requires that the receiver has a network view.
+ receiver.networkView.RPC(command, RPCMode.AllBuffered, args);
+ break;
+ default:
+ // Not supported
+ // TODO: Throw an exception
+ break;
+ }
+ }
+
+ ///
+ /// This function will send a non-buffered command to an object. In a local game, a message
+ /// will be sent. In a network game, an RPC call will be made. In a network game,
+ /// the object should have been created with InputDirector.InstantiateObject()
+ ///
+ ///
+ /// The receiving object.
+ ///
+ ///
+ /// The command string.
+ ///
+ ///
+ /// Additional arguments.
+ ///
+ public void SendCommand(GameObject receiver, string command, params object[] args)
+ {
+ switch (mode) {
+ case InputTransportMode.Local:
+ if (args.Length == 1) {
+ receiver.SendMessage(command, args[0], SendMessageOptions.DontRequireReceiver);
+ } else {
+ Debug.LogError("SendCommand called in a local session with multiple arguments!");
+ }
+ break;
+ case InputTransportMode.Server:
+ case InputTransportMode.Client:
+ receiver.networkView.RPC(command, RPCMode.All, args);
+ break;
+ default:
+ // Not supported
+ break;
+ }
+ }
+
+ ///
+ /// This function will broadcast a buffered command to all clients and the originator.
+ ///
+ ///
+ /// The command.
+ ///
+ ///
+ /// Command arguments.
+ ///
+ public void BroadcastBufferedCommand(string command, params object[] args)
+ {
+ switch (mode) {
+ case InputTransportMode.Server:
+ case InputTransportMode.Client:
+ networkView.RPC(command, RPCMode.AllBuffered, args);
+ break;
+ default:
+ // Not supported
+ break;
+ }
+ }
+
+ ///
+ /// This function will broadcast a non-buffered command to all clients and the originator.
+ ///
+ ///
+ /// The command.
+ ///
+ ///
+ /// Command arguments.
+ ///
+ public void BroadcastCommand(string command, params object[] args)
+ {
+ switch (mode) {
+ case InputTransportMode.Server:
+ case InputTransportMode.Client:
+ networkView.RPC(command, RPCMode.All, args);
+ break;
+ default:
+ // Not supported
+ break;
+ }
+ }
+
+ #endregion
+
+ #region Server Methods
+
+ ///
+ /// This function is called by us, the server, to change the current level during a network game
+ ///
+ ///
+ /// Level.
+ ///
+ public void LoadScene(string level)
+ {
+ if (InputTransportMode.Local == mode)
+ {
+ Application.LoadLevel(level);
+ }
+ else if (InputTransportMode.Server == mode)
+ {
+ // Send a buffered RPC to load the level so everyone who joins also gets the command
+ networkView.RPC("OnLoadNetworkLevel", RPCMode.AllBuffered, level, nextLevelPrefix++);
+ }
+ else {
+ // TODO: Throw an exception?
+ }
+ }
+
+ ///
+ /// This function will send a non-buffered command to a single player. It is not
+ /// designed to be used by individual clients.
+ ///
+ ///
+ /// Network player ID (should originally have been acquired from NetworkPlayer.ToString())
+ ///
+ ///
+ /// Command.
+ ///
+ ///
+ /// Arguments.
+ ///
+ public void SendCommand(string networkPlayerID, string command, params object[] args)
+ {
+ switch (mode) {
+ case InputTransportMode.Server:
+ foreach (NetworkPlayer p in Network.connections) {
+ if (p.ToString() == networkPlayerID) {
+ networkView.RPC(command, p, args);
+ return;
+ }
+ }
+ // TODO: Throw an exception?
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ #endregion
+
+ #region Client Methods
+
+ ///
+ /// This function is called by us, the client, to send a non-buffered command to the server
+ ///
+ ///
+ /// The command string.
+ ///
+ public void SendCommandToServer(string command)
+ {
+ switch (mode) {
+ case InputTransportMode.Client:
+ networkView.RPC(command, RPCMode.Server);
+ break;
+ default:
+ // TODO: Throw an exception?
+ break;
+ }
+ }
+
+ #endregion
+
+}
diff --git a/Assets/Standard Assets/Scripts/LANBroadcastService.cs b/Assets/Standard Assets/Scripts/LANBroadcastService.cs
new file mode 100644
index 0000000..4f4a492
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/LANBroadcastService.cs
@@ -0,0 +1,281 @@
+// LAN UDP-Broadcast Service Script
+// 12-11-2009
+// Made by Jordin Kee aka Jordos
+// You may use and/or modify this script as you like. Crediting is welcome, but not required.
+// Use this at your own risk, I do not guarantee it is bugfree. In fact I do not guarantee anything :)
+
+// This script can be used as a service to perform UDP Broadcasting over a LAN. This is usefull to search for servers on a LAN, without knowing their IP.
+// Next, this script is designed for a situation where the player is not able to choose to either start a server, or join one.
+// Instead, it is determined by the application (i.e. this script). This, because of the projet I made it for.
+// If you don't like this, but still want to use the script, read on...
+// This script uses the .Net UDPClient class to perform sending and receiving (http://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.aspx)
+
+// How to use this script:
+// This script must be seen as a service, therefore it must be controlled by another object, say "NetworkController". This script serves 2 goals:
+// 1. Search for an existing server and determine out of the results, whether this player should join a server, or start one itself
+// 2. Send out messages saying it has started a server and is ready to receive connections
+// NetworkController calls 'StartSearchBroadcasting'. This function takes 2 delegates (WTF? Delegate? --> http://msdn.microsoft.com/en-us/library/900fyy8e(VS.71).aspx)
+// One is called when the script has found a server. It passes the IP address of the server, so the NetworkController can join it.
+// The other is called when there is no server found or it is determined that this player should start one. The NetworkController can create a server.
+// In this last case service 2 should be used, namely StartAnnounceBroadcasting(). This will broadcast messages that this player has a server ready.
+// When other players start a search, they will receive this message and stop searching immediately.
+// Make sure NetworkController also calls StopBroadcasting(), as not doing this may result in crashes when the game is started again.
+
+// How the script works:
+// When a search is started, the script begins recieving messages. These messages are stored in a list and this list is refreshed, so that old messages are deleted.
+// What the script also does, is sending out messages that this player is willing to start a server, but has none ready at the moment.
+// The search stops as soon as a 'I have server ready' message is received, send out by the StartAnnounceBroadcasting() of another player.
+// After a specified amount of time, the search is ended. The received messages are scanned. If there are none, this player must start a server.
+// If there are messages 'i am willing to start a server' from other players, this means that multiple players are searching, but non has started a server.
+// In this case the script determines which of these players must start the server. This is based on the IP of the players (the highest willl be the server).
+// All players agree on this, as they use the same script. The player that is choosen will call the 'MustStartServer' delegate,
+// the others will continue searching, waiting for the message that a server is ready.
+
+// If you want to use this script, but want players to choose between creating/joining a server for themselves:
+// When the player chooses to create a server, call the StartAnnounceBroadcasting()
+// When the player chooses to join, call StartSearchBroadcasting(), but strip the part of sending out messages (see Update() method)
+
+using UnityEngine;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+
+public class LANBroadcastService : MonoBehaviour
+{
+ public delegate void delJoinServer(string strIP); // Definition of JoinServer Delegate, takes a string as argument that holds the ip of the server
+ public delegate void delStartServer(); // Definition of StartServer Delegate
+ public enum enuState { NotActive, Searching, Announcing }; // Definition of State Enumeration.
+ public struct ReceivedMessage { public float fTime; public string strIP; public bool bIsReady;} // Definition of a Received Message struct. This is the form in which we will store messages
+
+ private string strMessage = ""; // A simple message string, that can be read by other objects (eg. NetworkController), to show what this object is doing.
+ private enuState currentState = enuState.NotActive;
+ private UdpClient objUDPClient; // The UDPClient we will use to send and receive messages
+ private List lstReceivedMessages; // The list we store all received messages in, when searching
+ private delJoinServer delWhenServerFound; // Reference to the delegate that will be called when a server is found, set by StartSearchBroadcasting()
+ private delStartServer delWhenServerMustStarted; // Reference to the delegate that will be called when a server must be created, set by StartSearchBroadcasting()
+ private string strServerNotReady = "wanttobeaserver"; // The actual content of the 'i am willing to start a server' message
+ private string strServerReady = "iamaserver"; // The actual content of the 'i have a server ready' message
+ private float fTimeLastMessageSent;
+ private float fIntervalMessageSending = 1f; // The interval in seconds between the sending of messages
+ private float fTimeMessagesLive = 3; // The time a message 'lives' in our list, before it gets deleted
+ private float fTimeToSearch = 5; // The time the script will search, before deciding what to do
+ private float fTimeSearchStarted;
+ private string myIPAddress;
+ private float lastUpdateTime;
+
+ public string Message { get { return strMessage; } } // Property to read the strMessage
+ public enuState CurrentState { get { return currentState; } }
+ public List ReceivedMessages { get { return lstReceivedMessages; } }
+
+ void Awake()
+ {
+ // Create our list
+ lstReceivedMessages = new List();
+ // Cache our IP address
+ IPHostEntry host;
+ myIPAddress = "0.0.0.0";
+ host = Dns.GetHostEntry(Dns.GetHostName());
+ foreach (IPAddress ip in host.AddressList)
+ {
+ if (ip.AddressFamily.ToString() == "InterNetwork")
+ {
+ myIPAddress = ip.ToString();
+ }
+ }
+ Debug.Log("Local IP Address: " + myIPAddress);
+ }
+
+ void Update()
+ {
+ // Check if we need to send messages and the interval has espired
+ if ((currentState == enuState.Searching || currentState == enuState.Announcing)
+ && Time.time > fTimeLastMessageSent + fIntervalMessageSending)
+ {
+ // Determine out of our current state what the content of the message will be
+ byte[] objByteMessageToSend = System.Text.Encoding.ASCII.GetBytes(currentState == enuState.Announcing ? strServerReady : strServerNotReady);
+ // Send out the message
+ objUDPClient.Send(objByteMessageToSend, objByteMessageToSend.Length, new IPEndPoint(IPAddress.Broadcast, ConfigurationDirector.GetMulticastPort()));
+ // Restart the timer
+ fTimeLastMessageSent = Time.time;
+
+ // Refresh the list of received messages (remove old messages)
+ if (currentState == enuState.Searching)
+ {
+ // This rather complex piece of code is needed to be able to loop through a list while deleting members of that same list
+ for (int i=0; i < lstReceivedMessages.Count; i++)
+ {
+ if (Time.time > lstReceivedMessages[i].fTime + fTimeMessagesLive)
+ {
+ // If this message is too old, delete it and restart the foreach loop
+ lstReceivedMessages.RemoveAt(i--);
+ }
+ }
+ }
+ }
+
+ if (currentState == enuState.Searching)
+ {
+ // Check the list of messages to see if there is any 'i have a server ready' message present
+ foreach (ReceivedMessage objMessage in lstReceivedMessages)
+ {
+ // If we have a server that is ready, call the right delegate and stop searching
+ if (objMessage.bIsReady)
+ {
+ StopSearching();
+ strMessage = "We will join";
+ if (null != delWhenServerFound) {
+ delWhenServerFound(objMessage.strIP);
+ }
+ break;
+ }
+ }
+ // Check if we're ready searching.
+ if (currentState == enuState.Searching && Time.time > fTimeSearchStarted + fTimeToSearch)
+ {
+ // We are. Now determine who's gonna be the server.
+
+ // This string holds the ip of the new server. We will start off pointing ourselves as the new server
+ string strIPOfServer = myIPAddress;
+ // Next, we loop through the other messages, to see if there are other players that have more right to be the server (based on IP)
+ foreach (ReceivedMessage objMessage in lstReceivedMessages)
+ {
+ if (ScoreOfIP(objMessage.strIP) > ScoreOfIP(strIPOfServer))
+ {
+ // The score of this received message is higher, so this will be our new server
+ strIPOfServer = objMessage.strIP;
+ }
+ }
+ // If after the loop the highest IP is still our own, call delegate to start a server and stop searching
+ if (strIPOfServer == myIPAddress)
+ {
+ StopSearching();
+ strMessage = "We will start server.";
+ if (null != delWhenServerMustStarted) {
+ delWhenServerMustStarted();
+ }
+ }
+ // If it's not, someone else must start the server. We will simply have to wait as the server is clearly not ready yet
+ else
+ {
+ strMessage = "Found server. Waiting for server to get ready...";
+ // Clear the list and do the search again.
+ lstReceivedMessages.Clear();
+ fTimeSearchStarted = Time.time;
+ }
+ }
+ }
+ lastUpdateTime = Time.time;
+ }
+
+ // Method to start an Asynchronous receive procedure. The UDPClient is told to start receiving.
+ // When it received something, the UDPClient is told to call the EndAsyncReceive() method.
+ private void BeginAsyncReceive()
+ {
+ objUDPClient.BeginReceive(new AsyncCallback(EndAsyncReceive), null);
+ }
+ // Callback method from the UDPClient.
+ // This is called when the asynchronous receive procedure received a message
+ private void EndAsyncReceive(IAsyncResult objResult)
+ {
+ // Create an empty EndPoint, that will be filled by the UDPClient, holding information about the sender
+ IPEndPoint objSendersIPEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ // Read the message
+ byte[] objByteMessage = objUDPClient.EndReceive(objResult, ref objSendersIPEndPoint);
+ // If the received message has content and it was not sent by ourselves...
+ if (objByteMessage.Length > 0 &&
+ !objSendersIPEndPoint.Address.ToString().Equals(myIPAddress))
+ {
+ // Translate message to string
+ string strReceivedMessage = System.Text.Encoding.ASCII.GetString(objByteMessage);
+ // Create a ReceivedMessage struct to store this message in the list
+ ReceivedMessage objReceivedMessage = new ReceivedMessage();
+ objReceivedMessage.fTime = lastUpdateTime;
+ objReceivedMessage.strIP = objSendersIPEndPoint.Address.ToString();
+ objReceivedMessage.bIsReady = strReceivedMessage == strServerReady ? true : false;
+ lstReceivedMessages.Add(objReceivedMessage);
+ }
+ // Check if we're still searching and if so, restart the receive procedure
+ if (currentState == enuState.Searching) BeginAsyncReceive();
+ }
+ // Method to start this object announcing this is a server, used by the script itself
+ private void StartAnnouncing()
+ {
+ currentState = enuState.Announcing;
+ strMessage = "Announcing we are a server...";
+ }
+ // Method to stop this object announcing this is a server, used by the script itself
+ private void StopAnnouncing()
+ {
+ currentState = enuState.NotActive;
+ strMessage = "Announcements stopped.";
+ }
+ // Method to start this object searching for LAN Broadcast messages sent by players, used by the script itself
+ private void StartSearching()
+ {
+ lstReceivedMessages.Clear();
+ fTimeSearchStarted = Time.time;
+ BeginAsyncReceive();
+ currentState = enuState.Searching;
+ strMessage = "Searching for other players...";
+ }
+ // Method to stop this object searching for LAN Broadcast messages sent by players, used by the script itself
+ private void StopSearching()
+ {
+ currentState = enuState.NotActive;
+ strMessage = "Search stopped.";
+ }
+
+ // Method to be called by some other object (eg. a NetworkController) to start a broadcast search
+ // It takes two delegates; the first for when this object finds a server that can be connected to,
+ // the second for when this player is determined to start a server itself.
+ public void StartSearchBroadCasting(delJoinServer connectToServer, delStartServer startServer)
+ {
+ // Set the delegate references, so other functions within this class can call it
+ delWhenServerFound = connectToServer;
+ delWhenServerMustStarted = startServer;
+ // Start a broadcasting session (this basically prepares the UDPClient)
+ StartBroadcastingSession();
+ // Start a search
+ StartSearching();
+ }
+ // Method to be called by some other object (eg. a NetworkController) to start a broadcast announcement. Announcement means; tell everyone you have a server.
+ public void StartAnnounceBroadCasting()
+ {
+ // Start a broadcasting session (this basically prepares the UDPClient)
+ StartBroadcastingSession();
+ // Start an announcement
+ StartAnnouncing();
+ }
+ // Method to start a general broadcast session. It prepares the object to do broadcasting work. Used by the script itself.
+ private void StartBroadcastingSession()
+ {
+ // If the previous broadcast session was for some reason not closed, close it now
+ if (currentState != enuState.NotActive) StopBroadCasting();
+ // Create the client
+ objUDPClient = new UdpClient(ConfigurationDirector.GetMulticastPort());
+ objUDPClient.EnableBroadcast = true;
+ // Reset sending timer
+ fTimeLastMessageSent = Time.time;
+ }
+ // Method to be called by some other object (eg. a NetworkController) to stop this object doing any broadcast work and free resources.
+ // Must be called before the game quits!
+ public void StopBroadCasting()
+ {
+ if (currentState == enuState.Searching) StopSearching();
+ else if (currentState == enuState.Announcing) StopAnnouncing();
+ if (objUDPClient != null)
+ {
+ objUDPClient.Close();
+ objUDPClient = null;
+ }
+ }
+ // Method that calculates a 'score' out of an IP adress. This is used to determine which of multiple clients will be the server. Used by the script itself.
+ private long ScoreOfIP(string strIP)
+ {
+ long lReturn = 0;
+ string strCleanIP = strIP.Replace(".", "");
+ lReturn = long.Parse(strCleanIP);
+ return lReturn;
+ }
+}
diff --git a/Assets/Standard Assets/Scripts/MasterServerDirector.cs b/Assets/Standard Assets/Scripts/MasterServerDirector.cs
new file mode 100644
index 0000000..217effb
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/MasterServerDirector.cs
@@ -0,0 +1,266 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+
+///
+/// The "master server" is the ivory tower where all servers of this game report to let
+/// everyone know that this game is available and looking for players. This class manages
+/// this application's communication with the master server; including functionality for
+/// both searching and hosting.
+///
+[RequireComponent (typeof (LANBroadcastService))]
+public class MasterServerDirector : MonoBehaviour
+{
+ ///
+ /// Describes a game that was found when a search was done.
+ ///
+ public class FoundGame
+ {
+ ///
+ /// The game's listening IP address.
+ ///
+ public string ipAddress;
+ ///
+ /// True if the game is a dedicated server
+ ///
+ public bool isDedicated;
+ ///
+ /// True if the game is on the LAN
+ ///
+ public bool isOnLAN;
+ ///
+ /// The number of players on the server
+ ///
+ public int playerCount;
+ ///
+ /// The max number of players allowed on the server
+ ///
+ public int maxPlayerCount;
+ ///
+ /// The ping object that measures latency
+ ///
+ public Ping ping;
+ }
+
+ ///
+ /// This component enables LAN-based game searching
+ ///
+ LANBroadcastService lanBroadcastService;
+ ///
+ /// True if we are searching for LAN games
+ ///
+ bool isLANGameSearchEnabled;
+
+ ///
+ /// The list of all found WAN games (we use Unity's framework to populate this)
+ ///
+ List foundWANGames;
+ ///
+ /// The list of all found LAN games (we use the LANBroadcastService component to populate this)
+ ///
+ List foundLANGames;
+
+ public int WANGameCount { get { return foundWANGames.Count; } }
+ public int LANGameCount { get { return foundLANGames.Count; } }
+ public FoundGame GetWANGameByIndex(int index)
+ {
+ return foundWANGames[index];
+ }
+ public FoundGame GetLANGameByIndex(int index)
+ {
+ return foundLANGames[index];
+ }
+
+ ///
+ /// When hosting a game, we need to include special data in the comment to find things
+ /// like whether the game is a dedicated server or not. This function, given the user
+ /// input comment, will return the data formatted comment to give to the master server
+ /// director.
+ ///
+ ///
+ /// The formatted game comment (e.g. 'HTed's 24/7 server!')
+ ///
+ ///
+ /// The original game comment (e.g. 'Ted's 24/7 server!')
+ ///
+ ///
+ /// Dedicated server.
+ ///
+ static string FormatGameComment(string comment, bool dedicatedServer)
+ {
+ return (dedicatedServer ? "D" : "H") + comment;
+ }
+
+ ///
+ /// When hosting a game, we need to include special data in the comment to find things
+ /// like whether the game is a dedicated server or not. This function, given a comment
+ /// from a game search result from the master server director, will give us the comment
+ /// to display to the user.
+ ///
+ ///
+ /// The original game comment (e.g. 'Ted's 24/7 server!')
+ ///
+ ///
+ /// The formatted game comment (e.g. 'HTed's 24/7 server!')
+ ///
+ static string UnformatGameComment(string comment)
+ {
+ return comment.Substring(1,comment.Length-1);
+ }
+
+ ///
+ /// Returns true if the game being hosted is a dedicated server
+ ///
+ ///
+ /// true if this instance is dedicated server; otherwise, false.
+ ///
+ ///
+ /// The game's formatted game comment.
+ ///
+ static bool IsDedicatedServerByComment(string comment)
+ {
+ return (comment.Length > 0 && comment[0] == 'D') ? true : false;
+ }
+
+ static string IPFromStringArray(string[] value)
+ {
+ string result = "";
+ foreach (string s in value) {
+ result = s + " ";
+ }
+ return result;
+ }
+
+ #region Unity Events
+
+ void Awake()
+ {
+ foundLANGames = new List();
+ lanBroadcastService = gameObject.GetComponent();
+ // Start looking for hosts on the LAN
+ Debug.Log("Polling for LAN games on port " + ConfigurationDirector.GetMulticastPort());
+ }
+
+ void Update()
+ {
+ // Check for any new listings from the Unity master server
+ HostData[] hostData = MasterServer.PollHostList();
+ if (hostData.Length > 0)
+ {
+ foreach (HostData host in hostData)
+ {
+ FoundGame foundGame = new FoundGame();
+ foundGame.ipAddress = IPFromStringArray(host.ip);
+ if (IsDedicatedServerByComment(host.comment)) {
+ foundGame.isDedicated = true;
+ foundGame.playerCount = host.connectedPlayers - 1;
+ foundGame.maxPlayerCount = host.playerLimit - 1;
+ } else {
+ foundGame.isDedicated = false;
+ foundGame.playerCount = host.connectedPlayers;
+ foundGame.maxPlayerCount = host.playerLimit;
+ }
+ foundGame.ping = new Ping(foundGame.ipAddress);
+ foundGame.isOnLAN = false;
+ foundWANGames.Add(foundGame);
+ }
+ MasterServer.ClearHostList();
+ }
+
+ // Update our LAN listings from the LAN broadcast service. This list has a list of all received
+ // UDP packets in the last five seconds.
+ foundLANGames.Clear();
+ foreach (LANBroadcastService.ReceivedMessage m in lanBroadcastService.ReceivedMessages)
+ {
+ if (m.bIsReady) {
+ // This is a host announcing its presence
+ FoundGame foundGame = new FoundGame();
+ foundGame.ipAddress = m.strIP;
+ foundGame.ping = new Ping(foundGame.ipAddress);
+ foundGame.isOnLAN = true;
+ foundLANGames.Add(foundGame);
+ }
+ }
+ }
+
+ void OnFailedToConnectToMasterServer(NetworkConnectionError error)
+ {
+ // TODO: We should probably do stuff here someday
+ Debug.Log("Failed to connect to the master server! " + error.ToString());
+ }
+
+ void OnMasterServerEvent(MasterServerEvent msEvent)
+ {
+ // TODO: We should probably do stuff here someday
+ Debug.Log("In OnMasterServerEvent: " + msEvent.ToString());
+ }
+
+ #endregion
+
+ ///
+ /// Enables the LAN game search.
+ ///
+ ///
+ /// Enable.
+ ///
+ public void EnableLANGameSearch(bool enable)
+ {
+ if (isLANGameSearchEnabled != enable)
+ {
+ isLANGameSearchEnabled = enable;
+ if (enable) {
+ lanBroadcastService.StartSearchBroadCasting(null,null);
+ } else {
+ lanBroadcastService.StopBroadCasting();
+ }
+ }
+ }
+
+ ///
+ /// Registers our game with Unity's master game server list and the LAN
+ ///
+ ///
+ /// Game type name.
+ ///
+ ///
+ /// Game name.
+ ///
+ ///
+ /// Comment.
+ ///
+ ///
+ /// Dedicated server.
+ ///
+ ///
+ /// Internet server.
+ ///
+ public void RegisterHost(string gameTypeName, string gameName, string comment, bool dedicatedServer, bool internetServer)
+ {
+ // TODO: Pass in server information (pass in the game type name, name, comment, and dedicated server flags)
+ lanBroadcastService.StopBroadCasting();
+ lanBroadcastService.StartAnnounceBroadCasting();
+ if (internetServer) {
+ MasterServer.RegisterHost(gameTypeName, gameName, FormatGameComment(comment, dedicatedServer));
+ }
+ }
+
+ ///
+ /// Removes our game from Unity's master game server list and the LAN
+ ///
+ public void UnregisterHost()
+ {
+ lanBroadcastService.StopBroadCasting();
+ MasterServer.UnregisterHost(); // This is safe even if we aren't an internet host
+ }
+
+ ///
+ /// Requests the master game server listing from Unity
+ ///
+ public void RequestHostList()
+ {
+ MasterServer.ClearHostList();
+ foundWANGames = new List(); // Clear our known list
+ MasterServer.RequestHostList(VersionDirector.GetGameTypeName());
+ }
+
+}
diff --git a/Assets/Standard Assets/Scripts/PlayerAttributes.cs b/Assets/Standard Assets/Scripts/PlayerAttributes.cs
new file mode 100644
index 0000000..f9171ff
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/PlayerAttributes.cs
@@ -0,0 +1,23 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class describes fundamental player attributes
+///
+public class PlayerAttributes
+{
+ ///
+ /// This is the player's network view ID from the server's perspective
+ ///
+ public string ID;
+
+ ///
+ /// The player's name
+ ///
+ public string PlayerName;
+
+ ///
+ /// An object containing extended application-specific attributes
+ ///
+ public object Extended;
+}
diff --git a/Assets/Standard Assets/Scripts/Server.cs b/Assets/Standard Assets/Scripts/Server.cs
new file mode 100644
index 0000000..2c699b3
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/Server.cs
@@ -0,0 +1,202 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+
+///
+/// This component manages application-specific server work. When you host
+/// a game, the input director doesn't care about who is already in the game
+/// or banned IP addresses; but this component does.
+///
+public class Server : MonoBehaviour
+{
+ #region Member Variables
+
+ ///
+ /// The input director that we use to communicate with the world
+ ///
+ protected InputDirector inputDirector;
+
+ ///
+ /// The list of players in the game
+ ///
+ public Dictionary playerList; // Public for debugging purposes
+
+ ///
+ /// The list of banned IP addresses
+ ///
+ protected Dictionary bannedIPAddresses;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Returns the number of players
+ ///
+ public int PlayerCount { get { return playerList.Count; } }
+
+ ///
+ /// Gets the player.
+ ///
+ ///
+ /// The player.
+ ///
+ ///
+ /// Player ID.
+ ///
+ public PlayerAttributes GetPlayer(string playerID) { return playerList[playerID]; }
+
+ ///
+ /// Determines whether a specified ID address is spanned.
+ ///
+ ///
+ /// true if the IP address is banned; otherwise, false.
+ ///
+ ///
+ /// The IP address.
+ ///
+ public bool IsBanned(string IPAddress)
+ {
+ return bannedIPAddresses.ContainsKey(IPAddress);
+ }
+
+ #endregion
+
+ ///
+ /// The one and only server. This will exist throughout the application's lifetime.
+ ///
+ static private Server _server;
+
+ // Returns the one and only server component. This component is used by clients to register
+ // with game servers, and used by game servers to track player lists. This component is
+ // always attached to the input director.
+ static public Server Get()
+ {
+ if (null == _server)
+ {
+ InputDirector inputDirector = InputDirector.Get();
+ if (null != inputDirector)
+ {
+ _server = InputDirector.Get().gameObject.GetComponent();
+ }
+ }
+ return _server;
+ }
+
+ // Use this for initialization
+ void Start()
+ {
+ playerList = new Dictionary();
+ bannedIPAddresses = new Dictionary();
+ inputDirector = GetComponent();
+ }
+
+ // This is called by clients and servers alike to register with the master player
+ // list, and to get an updated list from the main server.
+ public void Register()
+ {
+ if (inputDirector.GetMode() == InputDirector.InputTransportMode.Server)
+ {
+ // If this is called by the server, then it must mean the game is just starting
+ // and they are the only player in it. So, we need to clear the player list. The
+ // calling client component is responsible for acting like registration was successful.
+ playerList.Clear();
+ }
+ else
+ {
+ // If we're a client, send a message to the server that we want to register. We will get our
+ // network view ID, as it is on the server, back in a response message.
+ networkView.RPC("OnServerRegister", RPCMode.Server, VersionDirector.GetVersion());
+ }
+ }
+
+ // This is called by the server when a player is disconnected and we want to
+ // unregister the player from the server list.
+ public void Unregister(NetworkPlayer p)
+ {
+ // Tell everyone, including ourselves, that the client left. We don't need to
+ // buffer this because when the client disconnected, we removed all its RPC
+ // buffer entries. New players that come in later will never know that this
+ // player existed.
+ networkView.RPC("OnPlayerUnregistered", RPCMode.All, p.ToString());
+ }
+
+ // This is called by the server to kick a player
+ public void Kick(string ID, bool ban)
+ {
+ if (inputDirector.IsHosting() && inputDirector.IsNetworking())
+ {
+ string IPAddress = inputDirector.GetIPAddress(ID);
+ inputDirector.DisconnectClientFromServer(ID);
+ if (ban) {
+ bannedIPAddresses.Add(IPAddress, true);
+ ConsoleDirector.Log("Banned player " + ID + " with address " + IPAddress);
+ } else {
+ ConsoleDirector.Log("Kicked player " + ID);
+ }
+ }
+ }
+
+ // Called on a server when a player disconnects from the server
+ void OnPlayerDisconnectedFromServer(NetworkPlayer player)
+ {
+ // Unregister the player with the server
+ Unregister(player);
+ }
+
+ // This message is sent from a client to a server. This includes the version of the game
+ [RPC]
+ void OnServerRegister(string requestedVersion, NetworkMessageInfo info)
+ {
+ Debug.Log("OnServerRegister called from player " + info.sender.ToString() + " with version " + requestedVersion);
+ // This is where games can do per-game authentication with players before
+ // letting them actually play. Here, we:
+ //
+ // - Ensure the versions are consistent
+ // - Ensure the player is not banned
+ //
+ // If all is well, reply to the player with their new ID; make them responsible
+ // for broadcasting their presence to everyone so that it's their network
+ // view ID that gets put into the RPC buffer.
+ if (VersionDirector.GetVersion() != requestedVersion) {
+ networkView.RPC("OnServerRegistrationFailed", info.sender, "Version mismatch");
+ } else if (IsBanned(info.sender.ipAddress)) {
+ networkView.RPC("OnServerRegistrationFailed", info.sender, "You are banned");
+ } else {
+ // Inform the sender that the registration was successful
+ networkView.RPC("OnRegisteredWithServer", info.sender, info.sender.ToString());
+ }
+ }
+
+ // This message is sent from a client or server to everyone to make their presence known to
+ // all in the game. This is a buffered call so that incoming players can seamlessly get the
+ // player list.
+ [RPC]
+ void OnPlayerRegistered(string ID, string playerName)
+ {
+ Debug.Log("OnPlayerRegistered called. Adding " + playerName + " (" + ID + ") to the list");
+
+ // Add the player to our list
+ PlayerAttributes a = new PlayerAttributes();
+ a.ID = ID;
+ a.PlayerName = playerName;
+ playerList.Add(ID, a);
+
+ // Log the connection
+ ConsoleDirector.Log(playerName + " has joined the game.");
+ }
+
+ // This message is sent from the server to everyone to make it known that a player has left
+ // the game.
+ [RPC]
+ void OnPlayerUnregistered(string ID)
+ {
+ // Remove the player from the list
+ PlayerAttributes e;
+ if (playerList.TryGetValue(ID, out e))
+ {
+ ConsoleDirector.Log(e.PlayerName + " has left the game.");
+ playerList.Remove(ID);
+ }
+ }
+}
diff --git a/Assets/Standard Assets/Scripts/VersionDirector.cs b/Assets/Standard Assets/Scripts/VersionDirector.cs
new file mode 100644
index 0000000..0cae4a6
--- /dev/null
+++ b/Assets/Standard Assets/Scripts/VersionDirector.cs
@@ -0,0 +1,31 @@
+using UnityEngine;
+using System.Collections;
+
+///
+/// This class is responsible for maintaining the application version
+/// and name for use with the master server.
+///
+public class VersionDirector
+{
+ ///
+ /// Gets the version of the application.
+ ///
+ ///
+ /// The version.
+ ///
+ static public string GetVersion()
+ {
+ return "0.1";
+ }
+
+ ///
+ /// Gets the name of the game type.
+ ///
+ ///
+ /// The game type name.
+ ///
+ static public string GetGameTypeName()
+ {
+ return "Vengance " + GetVersion();
+ }
+}
diff --git a/Assets/Textures/Materials/spaceship.mat b/Assets/Textures/Materials/spaceship.mat
new file mode 100644
index 0000000..227c165
Binary files /dev/null and b/Assets/Textures/Materials/spaceship.mat differ
diff --git a/Assets/Textures/Materials/spaceshipbullet.mat b/Assets/Textures/Materials/spaceshipbullet.mat
new file mode 100644
index 0000000..ea3a6da
Binary files /dev/null and b/Assets/Textures/Materials/spaceshipbullet.mat differ
diff --git a/Assets/Textures/Materials/starfield.mat b/Assets/Textures/Materials/starfield.mat
new file mode 100644
index 0000000..ba7a9a1
Binary files /dev/null and b/Assets/Textures/Materials/starfield.mat differ
diff --git a/Assets/Textures/Spaceship.bmp b/Assets/Textures/Spaceship.bmp
new file mode 100644
index 0000000..4422c5a
Binary files /dev/null and b/Assets/Textures/Spaceship.bmp differ
diff --git a/Assets/Textures/plain.bmp b/Assets/Textures/plain.bmp
new file mode 100644
index 0000000..bf0bb26
Binary files /dev/null and b/Assets/Textures/plain.bmp differ
diff --git a/Assets/instructions.txt b/Assets/instructions.txt
new file mode 100644
index 0000000..c2976d4
--- /dev/null
+++ b/Assets/instructions.txt
@@ -0,0 +1,167 @@
+====================================================================================
+Gamieon Unity network wrapper
+By Christopher Haag
+Gamieon, Inc.
+http://www.gamieon.com
+====================================================================================
+
+This is a simple project that has a number of scripts designed to be re-used in other Unity applications.
+It is designed to offer a way to prevent all of your scripts from looking like:
+
+ ...
+ if (networked) { do this; }
+ else { do that; }
+ ...
+
+By centralizing GameObject instantiation, destruction, message passing and player management into
+a single persistent GameObject that has several scripts with functions you can use.
+
+For example, if you want to create a bullet, you wouldn't do:
+
+ if (networked) { Network.Instantiate(...); }
+ else { GameObject.Instantiate(...); }
+
+You could instead just do:
+
+ inputDirector.InstantiateObject(...);
+
+and it will get the job done.
+
+If your game is not single player or you just connect to yourself to play locally, then that is of little
+value. However, these scripts also manage searching for instances of your game online (even on LAN's),
+notifying players when other players join and leave in your game; and even support ban lists (in memory only).
+
+This project also provides a very simple example of how Unity networking mechanics work. A number of Debug.Log
+messages are scattered throughout the code so you can observe them and better understand things.
+
+
+===========================================================================================================
+Relevant files you shouldn't have to change
+===========================================================================================================
+Client.cs - Represents the user's existence in the application. All non-application-specific player management
+happens here. This component must be a subclass of the actual application-specific user component.
+
+InputDirector.cs - The main hub responsible for dispatching player events, hosting games and joining games.
+There is only ever once instance of this component, and it persists throughout the application's lifetime.
+
+LANBroadcastService.cs - Lower level code used to find LAN players using UDP multicasting
+
+MasterServerDirector.cs - Used for players to find each other over both LANs and WANs
+
+PlayerAttributes.cs - Retains player ID's, names, and eventually custom fields like achievements and more.
+
+Server.cs - Whether you are a client or hosting the game, this acts as an interface to the list of players
+and other authoritative operations.
+
+
+===========================================================================================================
+Game-specific files used in this sample project
+===========================================================================================================
+
+ConfigurationDirector.cs - Controls server rules and player configuration
+
+ConnectionFailureSceneDirector.cs - Manages the scene you see when you fail to connect to a server
+
+ConsoleDirector.cs - Right now it simply forwards logging messages to the Unity console, but it is intended
+to be the game's console controller where you can see an event history and also handle commands.
+
+GameDirector.cs - Responsible for dealing with getting a player set up to begin playing a round of the game.
+
+GameRules_FFA.cs - Defines how players set themselves up to play a free for all game, and the rules to win.
+
+Player.cs - Represents the user's existence in the application. There is only ever once instance of this component,
+and it persists throughout the application's lifetime. It is inherited from the Client component.
+
+VersionDirector.cs - Simple application versioning properties
+
+
+
+===========================================================================================================
+The chain of events when hosting a game
+===========================================================================================================
+
+1. When the application is launched, Player.Create() is called. This creates a new GameObject in the initial scene
+that encapsulates these components: InputDirector, LANBroadcastService, MasterServerDirector, Player (inherited
+from Client). This GameObject will persist throughout the lifetime of the application.
+
+2. InputDirector.HostServer is called to launch the host.
+
+3. InputDirector sends OnHostServerComplete message to its game object.
+
+4. Client component catches OnHostServerComplete message and "registers" itself with the server by calling
+Server.Register. Registering with the server is a level of authentication above Unity's engine; clients do
+it as an RPC call to give the servers their name, version, any unique ID's, and IP address to check against
+a ban list.
+
+5. OnRegisteredWithServer is called on the Player object, and it goes into the game by calling
+InputDirector.LoadScene. A buffered RPC call is made to load the level; this way, new players know to go into that same level.
+
+6. Unity calls Client.OnLevelWasLoaded, and it notifies all game objects in the scene with a call to
+OnNetworkLoadedLevel.
+
+7. The GameDirector object, which exists in the new scene, gets the OnNetworkLoadedLevel message. It checks
+the application's configuration for what kind of game is playing (in this case, a free-for-all). It then
+sends another buffered RPC call called "OnDefineGameRules" that informs all incoming clients of what kind
+of game is being played.
+
+8. The Player object, which is in the same game object as the InputDirector component, will get the OnDefineGameRules
+message and handle it by sending a OnGameRulesDefined message to the GameDirector object.
+
+9. The GameDirector object gets the OnGameRulesDefined message, and creates a GameRules-inherited component for its game object
+based on what. The GameRules component dictates things like how to win a game, scoring rules, and so forth. The GameDirector
+object then sends an OnHostBeginGame message to its game object.
+
+10. The GameRules_FFA component gets the OnHostBeginGame message, and sends a message back to the Player component's game object
+of OnSpawnSpaceship to have a player spawn its spaceship. It's important to understand that the Player component and the Spaceship
+components are two completely different things.
+
+11. The player instructs the input director to instantiate the spaceship. It will then keep a handle to its spaceship and
+invoke a buffered RPC call to set the spaceship attributes.
+
+12. The created spaceship gets the OnSetSpaceshipAttributes message and has its attributes assigned to it.
+
+
+===========================================================================================================
+The chain of events when connecting to a game
+===========================================================================================================
+
+1. When the application is launched, Player.Create() is called. This creates a new GameObject in the initial scene
+that encapsulates these components: InputDirector, LANBroadcastService, MasterServerDirector, Player (inherited
+from Client). This GameObject will persist throughout the lifetime of the application.
+
+2. InputDirector.ConnectToServer is calle to connect to the remote host.
+
+3. After Unity sends the OnConnectedToServer message to the InputDirector, the InputDirector will send a OnConnectToServerComplete
+message that the Client component will get.
+
+4. The Client component will call Server.Register to "register" itself with the server. Registering with the server
+is a level of authentication above Unity's engine; clients do it as an RPC call to give the servers their name, version,
+any unique ID's, and IP address to check against a ban list.
+
+5. At this point, the server is sending buffered RPC's to you. The first buffered RPC message, OnLoadNetworkLevel, will
+have you load the game scene. The next messages are OnPlayerRegistered messages that other players sent; server included,
+when they joined the game. This is how you get a list of everyone in the game. You also get a OnDefineGameRules message
+which contains information about the game you're playing. In this case, the rules are a simple "Free For All" string, which
+tells the application that everyone spawns at will and it's just going to be a shootout. The Player component stores those
+rules for later so we know how to spawn our spaceship after we've registered.
+
+6. The server approves your request to register, and you get a OnRegisteredWithServer message back in the Player component
+that contains your new player ID. After getting this, you send a buffered OnPlayerRegistered message to everyone to "introduce"
+yourself to everyone in the game; server included.
+
+7. Everyone, yourself included, gets the OnPlayerRegistered message and you show up in all the player lists. Now that you've
+registered, and you know the game rules, you're ready to start playing. The Player component sends a OnGameRulesDefined message
+to the GameDirector component over in another GameObject.
+
+8. The GameDirector component gets OnGameRulesDefined, creates a GameRules_FFA component, and sends an OnBeginGame message
+to that component.
+
+9. The GameRules_FFA component sends a message to your InputDirector GameObject called OnSpawnSpaceship; which the Player
+component picks up and uses InputDirector.instantiate to create your spaceship so that everyone in the game can see it.
+
+10. The player instructs the input director to instantiate the spaceship. It will then keep a handle to its spaceship and
+invoke a buffered RPC call to set the spaceship attributes.
+
+11. The created spaceship gets the OnSetSpaceshipAttributes message and has its attributes assigned to it.
+
+
diff --git a/Assets/starfield.jpg b/Assets/starfield.jpg
new file mode 100644
index 0000000..e9ff18a
Binary files /dev/null and b/Assets/starfield.jpg differ
diff --git a/Library.zip b/Library.zip
new file mode 100644
index 0000000..c36a875
Binary files /dev/null and b/Library.zip differ
diff --git a/ProjectSettings/AudioManager.asset b/ProjectSettings/AudioManager.asset
new file mode 100644
index 0000000..9595c59
Binary files /dev/null and b/ProjectSettings/AudioManager.asset differ
diff --git a/ProjectSettings/DynamicsManager.asset b/ProjectSettings/DynamicsManager.asset
new file mode 100644
index 0000000..b5f3165
Binary files /dev/null and b/ProjectSettings/DynamicsManager.asset differ
diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset
new file mode 100644
index 0000000..042b8fe
Binary files /dev/null and b/ProjectSettings/EditorBuildSettings.asset differ
diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset
new file mode 100644
index 0000000..5cec1a9
Binary files /dev/null and b/ProjectSettings/EditorSettings.asset differ
diff --git a/ProjectSettings/InputManager.asset b/ProjectSettings/InputManager.asset
new file mode 100644
index 0000000..9679976
Binary files /dev/null and b/ProjectSettings/InputManager.asset differ
diff --git a/ProjectSettings/NavMeshLayers.asset b/ProjectSettings/NavMeshLayers.asset
new file mode 100644
index 0000000..27d9e93
Binary files /dev/null and b/ProjectSettings/NavMeshLayers.asset differ
diff --git a/ProjectSettings/NetworkManager.asset b/ProjectSettings/NetworkManager.asset
new file mode 100644
index 0000000..fbb4791
Binary files /dev/null and b/ProjectSettings/NetworkManager.asset differ
diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset
new file mode 100644
index 0000000..80b5754
Binary files /dev/null and b/ProjectSettings/ProjectSettings.asset differ
diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset
new file mode 100644
index 0000000..29e262b
Binary files /dev/null and b/ProjectSettings/QualitySettings.asset differ
diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset
new file mode 100644
index 0000000..364caed
Binary files /dev/null and b/ProjectSettings/TagManager.asset differ
diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset
new file mode 100644
index 0000000..ab817b9
Binary files /dev/null and b/ProjectSettings/TimeManager.asset differ
diff --git a/UniNetShooter-csharp.sln b/UniNetShooter-csharp.sln
new file mode 100644
index 0000000..88cf69d
--- /dev/null
+++ b/UniNetShooter-csharp.sln
@@ -0,0 +1,45 @@
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2008
+
+Project("{F2EAEDF2-2AEA-3F1C-5EEC-152ADA60C0D6}") = "UniNetShooter", "Assembly-CSharp-firstpass-vs.csproj", "{2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}"
+EndProject
+Project("{F2EAEDF2-2AEA-3F1C-5EEC-152ADA60C0D6}") = "UniNetShooter", "Assembly-CSharp-vs.csproj", "{05EC98B6-FB67-CA9B-8A26-88E22667BD72}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ StartupItem = Assembly-CSharp.csproj
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.inheritsSet = null
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ $0.TextStylePolicy = $3
+ $3.FileWidth = 120
+ $3.TabWidth = 4
+ $3.EolMarker = Unix
+ $3.inheritsSet = Mono
+ $3.inheritsScope = text/plain
+ $3.scope = text/plain
+ EndGlobalSection
+
+EndGlobal
diff --git a/UniNetShooter.sln b/UniNetShooter.sln
new file mode 100644
index 0000000..f6730d1
--- /dev/null
+++ b/UniNetShooter.sln
@@ -0,0 +1,45 @@
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2008
+
+Project("{F2EAEDF2-2AEA-3F1C-5EEC-152ADA60C0D6}") = "UniNetShooter", "Assembly-CSharp-firstpass.csproj", "{2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}"
+EndProject
+Project("{F2EAEDF2-2AEA-3F1C-5EEC-152ADA60C0D6}") = "UniNetShooter", "Assembly-CSharp.csproj", "{05EC98B6-FB67-CA9B-8A26-88E22667BD72}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2CA8DFC8-CE1D-F164-BA55-923E8768E9FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {05EC98B6-FB67-CA9B-8A26-88E22667BD72}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ StartupItem = Assembly-CSharp.csproj
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.inheritsSet = null
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ $0.TextStylePolicy = $3
+ $3.FileWidth = 120
+ $3.TabWidth = 4
+ $3.EolMarker = Unix
+ $3.inheritsSet = Mono
+ $3.inheritsScope = text/plain
+ $3.scope = text/plain
+ EndGlobalSection
+
+EndGlobal
diff --git a/UniNetShooter.userprefs b/UniNetShooter.userprefs
new file mode 100644
index 0000000..c1bf329
--- /dev/null
+++ b/UniNetShooter.userprefs
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file