So, a few weeks ago I started looking at network gaming using Unity and got something simple working and posted a picture on the Unity Indie Devs FB group.
Now, as you know this is really a XBox One Indie Development blog, how is this relevant? Well, as I am sure you know, if you are part of the
ID@XBox program, then it has been announced that we will get an XB1 license for free for developing on the XB1 :D
So, I have embellished on this work in progress a little , and it now looks like this
As you can see, there are a few things we can go over, from the star field (it’s animated by the way, I know you can’t see that) to the ship trails as well as the main thread of this post, networking.
I am going to focus on the networking as I am going to put all the source up in a zip at the end for you to download, so you can have a look at how I have done it all, I dare say, Ill explain some of it as I go.
Caveat:-
All that you read here is just how I found I could get it working, the only tutorials I have used have been for the basic network connection stuff, the rest is all my own work. So, with that being said, this may not be the best way to go about writing this in Unity 4.x, but it’s the way I have found (so far) I just hope that this gives those of you that have never done it before somewhere to start, and for those of you that this is all old hat for, please let me know how I can improve on it.
NetworkManager
The first thing we are going to do is create a network manager to handle the setting up of the server and the connection’s between it and the client. I have written my network manager so that if there are no games being hosted, it will become the server, if there is 1, then it will attempt to connect. In a real game, I/we would create a lobby and have the option to either be a server or look for a server to join, but from this code I am sure you can figure out how easy that would be to do.
I went about creating my network manager by creating an empty GameObject in the scene, to that I had it host a GUIText GameObject like this:
I then added a C# script to the project and called it NetworkManager and added it to the empty GameObject.
NetworkManager.cs
There is a far bit going on in here, lets have a look…
using UnityEngine;
using System.Collections;
using System;
using System.Linq;
using System.Collections.Generic;
public class NetworkManager : MonoBehaviour
{
// Intrinsic .NET Random number generator class, Unity has one to, but I still use this.
System.Random rnd = new System.Random(DateTime.Now.Millisecond);
// The Prefab that will be used to create the player in the game.
public GameObject PlayerPrefab;
// A List of available games being hosted, if there are none, then
// we will become the server and clients will connect to us
private HostData[] hostList = new HostData[] { };
// The maximum number of players that can connect to the server
public int MaxPlayers = 32;
// Status text used to display network status messages.
GUIText StatusText;
// Use this for initialization
void Start ()
{
// Setup the connection.
// MasterServer.RequestHostList will result in a call to OnMasterServerEvent
MasterServer.RequestHostList("RandomChaosNetworkGameSample");
// Set the current status.
StatusText = GetComponentInChildren<GUIText>();
StatusText.transform.position = Camera.main.ScreenToViewportPoint(new Vector3(8, 4, 0));
SetStatus("Checking Network Status");
}
#region Intrinsic Unity Network methods
void OnMasterServerEvent(MasterServerEvent msEvent)
{
// This function handles lots of events, we just want to know about HostListReceived
if (msEvent == MasterServerEvent.HostListReceived)
{
// Poll the list
hostList = MasterServer.PollHostList();
// Is a game already beeing hoste?
if (hostList.Length == 0)
{
// Nope? Then host one :)
SetStatus("Initializing Server");
// This will result in OnServerInitialized being called
Network.InitializeServer(MaxPlayers, 8080, !Network.HavePublicAddress());
Network.incomingPassword = "oxopots";
MasterServer.RegisterHost("RandomChaosNetworkGameSample", "This Game", "This is a sample");
}
else
{
// Yes, so connect to it.
SetStatus("Connecting to Server");
// This will result in OnConnectedToServer being called
Network.Connect(hostList[0],"oxopots");
}
}
}
// This method gets called once we have set up the server.
void OnServerInitialized()
{
SetStatus("Server Initializied");
SpawnPlayer(Vector3.zero);
}
// Method to change the displayed network status message
void SetStatus(string status)
{
StatusText.text = string.Format("[{0}]", status);
}
// The server has this method called each time a connection is made
void OnPlayerConnected(NetworkPlayer player)
{
SetStatus(string.Format("New Player Connected. {0}/{1} total", Network.connections.Length + 1, Network.maxConnections));
}
// Clients connecting to the server will have this called once the client has connected.
void OnConnectedToServer()
{
SetStatus("Connected to Server.");
SpawnClientPlayer();
}
// Method to spawn the client player, we don't want to have every one
// start in the same spot, so we randomize it a little
public void SpawnClientPlayer(string name = null)
{
// Random position..
float x, y;
x = Mathf.Lerp(-10, 10, (float)rnd.NextDouble());
y = Mathf.Lerp(-10, 10, (float)rnd.NextDouble());
SpawnPlayer(new Vector3(x, y, 0), name);
}
// Method called when disconnecting
void OnDisconnectedFromServer(NetworkDisconnection info)
{
if (Network.isServer) {
SetStatus("Local server connection disconnected");
}
else
{
if (info == NetworkDisconnection.LostConnection)
SetStatus("Lost connection to the server");
else
SetStatus("Successfully diconnected from the server");
}
}
// Method calld if there is a connection issue
void OnFailedToConnect(NetworkConnectionError error)
{
SetStatus(string.Format("Could not connect to server: {0}", error));
}
// Method called on the server when a client disconnects
void OnPlayerDisconnected(NetworkPlayer player)
{
SetStatus("player has disconnected");
PlayerControler thisPlayer = GetNetworkPlayerControler(player);
if (thisPlayer != null)
DestroyPlayer(thisPlayer.networkView.viewID.ToString(), thisPlayer.DisplayName);
Network.RemoveRPCs(player);
Network.DestroyPlayerObjects(player);
}
#endregion
#region My Helper Methods
// Method used to get a player based on NetworkPlayer
PlayerControler GetNetworkPlayerControler(NetworkPlayer player)
{
List<PlayerControler> players = new List<PlayerControler>(FindObjectsOfType<PlayerControler>());
foreach (PlayerControler go in players)
{
if (go.gameObject.networkView.owner == player)
return go;
}
return null;
}
// Method to spawn a player
void SpawnPlayer(Vector3 position, string name = null)
{
GameObject thisPlayer = (GameObject)Network.Instantiate(PlayerPrefab, position, Quaternion.identity, 0);
if (Network.isServer)
thisPlayer.GetComponent<PlayerControler>().DisplayName = "Server Plr";
if (name != null)
thisPlayer.GetComponent<PlayerControler>().DisplayName = name;
thisPlayer.GetComponent<PlayerControler>().UpdateNetworkPlayer();
}
// MEthod to update a given player
public void UpdatePlayer(string id, string dispName, string text)
{
List<GameObject> objs = new List<GameObject>(FindObjectsOfType<GameObject>());
GameObject updOb = objs.SingleOrDefault(e => e.networkView != null && e.networkView.viewID.ToString() == id);
if (updOb != null)
{
if (text.Contains("Score"))
{
int s = text.IndexOf("Score:") + 6;
int l = text.IndexOf("\n", s) - s;
int score = 0;
int kills = 0;
float hits = 1;
int.TryParse(text.Substring(s, l), out score);
s = text.IndexOf("Kills:") + 6;
l = text.IndexOf("\n", s) - s;
int.TryParse(text.Substring(s,l), out kills);
float.TryParse(text.Substring(s + l), out hits);
text = text.Substring(0, s+l);
updOb.GetComponent<PlayerControler>().Score = score;
updOb.GetComponent<PlayerControler>().Kills = kills;
updOb.GetComponent<PlayerControler>().hits = hits;
}
updOb.GetComponent<PlayerControler>().DisplayName = dispName;
updOb.GetComponentInChildren<GUIText>().text = text;
}
}
// Method used to update the network status for a disconnected player.
public void DestroyPlayer(string id, string playerName)
{
SetStatus(string.Format("{0} disconnected", playerName));
}
#endregion
}
Lets start from the top and work our way down the code. There are five fields in this class two are public the rest are private, the two public ones as you can see in the editor screen shot above are the PlayerPrefab and MaxPlayers.
The PlayerPrefab is the prefab that will be used to instance the player(s) in the game, we will come to the creation of that in a little while, the MaxPlayers is where you can set the maximum number of players a server can host.
Lets get into these methods now:-
void Start()
Like most MonoBehavior’s we have a Start method, in here we are going to set up the network.
// Use this for initialization
void Start ()
{
// Setup the connection.
// MasterServer.RequestHostList will result in a call to OnMasterServerEvent
MasterServer.RequestHostList("RandomChaosNetworkGameSample");
// Set the current status.
StatusText = GetComponentInChildren<GUIText>();
StatusText.transform.position = Camera.main.ScreenToViewportPoint(new Vector3(8, 4, 0));
SetStatus("Checking Network Status");
}
So, as I say, we want to know if there are any games already being played, so we use the Unity MasterServer class and request a list of hosts, this method will return in the function OnMasterServerEvent, we will look at that next. Once we have made this asynchronous call, we then set the StatusText up, we know it’s a child component, so we can get it with GetComponentInChildren and bind it to our StatusText variable and we can then use my method of SetStatus to update it on the screen for the user.
void OnMasterServerEvent(MasterServerEvent msEvent)
This method is provided by Unity, and is called as a response to MasterServer.RequestHostList, we can now decide if we need to connect to an existing game or create one of our own.
void OnMasterServerEvent(MasterServerEvent msEvent)
{
// This function handles lots of events, we just want to know about HostListReceived
if (msEvent == MasterServerEvent.HostListReceived)
{
// Poll the list
hostList = MasterServer.PollHostList();
// Is a game already beeing hoste?
if (hostList.Length == 0)
{
// Nope? Then host one :)
SetStatus("Initializing Server");
// This will result in OnServerInitialized being called
Network.InitializeServer(MaxPlayers, 8080, !Network.HavePublicAddress());
Network.incomingPassword = "oxopots";
MasterServer.RegisterHost("RandomChaosNetworkGameSample", "This Game", "This is a sample");
}
else
{
// Yes, so connect to it.
SetStatus("Connecting to Server");
// This will result in OnConnectedToServer being called
Network.Connect(hostList[0],"oxopots");
}
}
}
If no hosts are present then we will call Network.InitializeServer, this will, if all goes well, result in OnServerInitialized being called so we know the server has been set up and is waiting for players to join. If there are games already hosted, then we will connect to the first one in the list with Network.Connect, as I say, in an actual game, we would be able to connect to any we liked or serve our own.
void OnServerInitialized()
So, we have initialized the server, let the user know with SetStatus and spawn him/her a player to play the game with using SpawnPlayer and put them in the center of the game world.
void OnServerInitialized()
{
SetStatus("Server Initializied");
SpawnPlayer(Vector3.zero);
}
void SetStatus(string status)
This is my method to keep the user informed on what’s going on with the network, all it does it update the StatusText GUText text with what we want to show.
void SetStatus(string status)
{
StatusText.text = string.Format("[{0}]", status);
}
void OnPlayerConnected(NetworkPlayer player)
A player has connected to the server, so, again, we inform the server player.
void OnPlayerConnected(NetworkPlayer player)
{
SetStatus(string.Format("New Player Connected. {0}/{1} total", Network.connections.Length + 1, Network.maxConnections));
}
void OnConnectedToServer()
This method is called by the client network after a successful Network.Connect, so in here we are going to tell the client player he has connected and create him/her a player to use.
void OnConnectedToServer()
{
SetStatus("Connected to Server.");
SpawnClientPlayer();
}
public void SpawnClientPlayer(string name = null)
This met5hod spawns a player, it differs as it will place the player at a random location and if it’s a re spawn, assign the new player with the current players name.
public void SpawnClientPlayer(string name = null)
{
// Random position..
float x, y;
x = Mathf.Lerp(-10, 10, (float)rnd.NextDouble());
y = Mathf.Lerp(-10, 10, (float)rnd.NextDouble());
SpawnPlayer(new Vector3(x, y, 0), name);
}
void OnDisconnectedFromServer(NetworkDisconnection info)
This method is called by both client and server when they disconnect, it’s used here just to inform the user.
void OnDisconnectedFromServer(NetworkDisconnection info)
{
if (Network.isServer) {
SetStatus("Local server connection disconnected");
}
else
{
if (info == NetworkDisconnection.LostConnection)
SetStatus("Lost connection to the server");
else
SetStatus("Successfully diconnected from the server");
}
}
void OnFailedToConnect(NetworkConnectionError error)
Again, another informative method should the connection fail.
void OnFailedToConnect(NetworkConnectionError error)
{
SetStatus(string.Format("Could not connect to server: {0}", error));
}
void OnPlayerDisconnected(NetworkPlayer player)
This method is called on the server when a player drops off, we inform the user then clear up the disconnected player. Using the provided NetworkPlayer object we need to find the player GameObject in the scene, then, if found destroy it, in this case we just inform the user with this method. We then clear our the players RPC and PlayerObjects over the network, this will remove it from all clients.
void OnPlayerDisconnected(NetworkPlayer player)
{
SetStatus("player has disconnected");
PlayerControler thisPlayer = GetNetworkPlayerControler(player);
if (thisPlayer != null)
DestroyPlayer(thisPlayer.networkView.viewID.ToString(), thisPlayer.DisplayName);
Network.RemoveRPCs(player);
Network.DestroyPlayerObjects(player);
}
PlayerControler GetNetworkPlayerControler(NetworkPlayer player)
This is a method of my own creation to get a player GameObject based on a given NetworkPlayer object.
PlayerControler GetNetworkPlayerControler(NetworkPlayer player)
{
List<PlayerControler> players = new List<PlayerControler>(FindObjectsOfType<PlayerControler>());
foreach (PlayerControler go in players)
{
if (go.gameObject.networkView.owner == player)
return go;
}
return null;
}
void SpawnPlayer(Vector3 position, string name = null)
This is my method to spawn a player GameObject in the scene. By using Network.Instantiate rather than Instantiate, we create the object on all the client machines too :) We then can set the players name and update it, both of these will be covered in the PlayerControler section.
void SpawnPlayer(Vector3 position, string name = null)
{
GameObject thisPlayer = (GameObject)Network.Instantiate(PlayerPrefab, position, Quaternion.identity, 0);
if (Network.isServer)
thisPlayer.GetComponent<PlayerControler>().DisplayName = "Server Plr";
if (name != null)
thisPlayer.GetComponent<PlayerControler>().DisplayName = name;
thisPlayer.GetComponent<PlayerControler>().UpdateNetworkPlayer();
}
public void UpdatePlayer(string id, string dispName, string text)
This is my method to update a given player in the scene based on an ID, this again will be covered in the PlayerControler section as it is called from there.
public void UpdatePlayer(string id, string dispName, string text)
{
List<GameObject> objs = new List<GameObject>(FindObjectsOfType<GameObject>());
GameObject updOb = objs.SingleOrDefault(e => e.networkView != null && e.networkView.viewID.ToString() == id);
if (updOb != null)
{
if (text.Contains("Score"))
{
int s = text.IndexOf("Score:") + 6;
int l = text.IndexOf("\n", s) - s;
int score = 0;
int kills = 0;
float hits = 1;
int.TryParse(text.Substring(s, l), out score);
s = text.IndexOf("Kills:") + 6;
l = text.IndexOf("\n", s) - s;
int.TryParse(text.Substring(s,l), out kills);
float.TryParse(text.Substring(s + l), out hits);
text = text.Substring(0, s+l);
updOb.GetComponent<PlayerControler>().Score = score;
updOb.GetComponent<PlayerControler>().Kills = kills;
updOb.GetComponent<PlayerControler>().hits = hits;
}
updOb.GetComponent<PlayerControler>().DisplayName = dispName;
updOb.GetComponentInChildren<GUIText>().text = text;
}
}
public void DestroyPlayer(string id, string playerName)
A method to update the network status to inform the user a player has been destroyed.
public void DestroyPlayer(string id, string playerName)
{
SetStatus(string.Format("{0} disconnected", playerName));
}
Player Prefab
So, we have a network manager, now we need a player to play the game with. I started off creating a Sprite GameObject in the scene, then, like with the Nework Manager gave it a child of GUText so it’s stats can be displayed. I then created a C# script called PlayerControler and attached this to the Sprite as well as giving it a RigidBody2D, a TrailRenderer a NewtorkView (most important) and a PollygonCollider2D. I then made this a prefab by dragging it into my Prefabs folder and deleted the one in the scene.
PlayerControler.cs
I guess this is where all the gameplay and gameplay networking sits, and to be honest, there is not a lot as the NetworkView does most of it for me. Here is the class in full, we can then take a look at what is going on here.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(NetworkView))]
public class PlayerControler : MonoBehaviour
{
public float ShipRotationSpeed = 1;
public float ShipThrust = 2;
public string DisplayName = "New Player";
public float Score = 0;
public int Kills = 0;
public float hits = 1;
bool canShoot;
TimeSpan lastShot = TimeSpan.Zero;
TimeSpan shootDelay = new TimeSpan(0, 0, 0, 0, 250);
public GameObject BulletPrefab;
public GameObject BoomPrefab;
public string DisplayStatus
{
get { return string.Format("{0}\nScore:{1:00000}\nKills:{2:0000}", DisplayName, Score, Kills); }
}
void Awake()
{
// assign me to the camera if this is my network session..
if (networkView.isMine)
Camera.main.GetComponent<CameraControler>().Player = gameObject;
}
public void Shoot()
{
if (DateTime.Now.TimeOfDay - lastShot >= shootDelay)
{
lastShot = DateTime.Now.TimeOfDay;
GameObject bullet = (GameObject)Network.Instantiate(BulletPrefab, transform.TransformPoint(new Vector3(.5f, .125f, 0)), Quaternion.identity, 0);
bullet.GetComponent<BulletControler>().owner = gameObject;
bullet.transform.rotation = transform.rotation;
bullet.rigidbody2D.velocity = transform.TransformDirection(Vector3.right) * 8;
bullet = (GameObject)Network.Instantiate(BulletPrefab, transform.TransformPoint(new Vector3(.5f, -.125f, 0)), Quaternion.identity, 0);
bullet.GetComponent<BulletControler>().owner = gameObject;
bullet.transform.rotation = transform.rotation;
bullet.rigidbody2D.velocity = transform.TransformDirection(Vector3.right) * 8;
}
}
void OnTriggerEnter2D(Collider2D collider)
{
BulletControler bullet = collider.gameObject.GetComponent<BulletControler>();
if (bullet)
{
PlayerControler plr = bullet.owner.GetComponent<PlayerControler>();
hits -= .1f;
if (hits <= 0)
{
Instantiate(BoomPrefab, transform.position, Quaternion.identity);
plr.Kills++;
plr.Score += 1000;
// Spawn client player
if(networkView.isMine)
FindObjectOfType<NetworkManager>().SpawnClientPlayer(DisplayName);
Network.Destroy(gameObject);
}
plr.UpdateNetworkPlayer();
UpdateNetworkPlayer();
plr.Score += 100;
}
}
// Update is called once per frame
void Update()
{
if (networkView.isMine)
{
if (Input.GetKey(KeyCode.LeftArrow))
transform.Rotate(Vector3.forward, ShipRotationSpeed);
if (Input.GetKey(KeyCode.RightArrow))
transform.Rotate(Vector3.back, ShipRotationSpeed);
if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.RightArrow))
{
if (transform.InverseTransformDirection(rigidbody2D.velocity).x > 0)
rigidbody2D.velocity = transform.TransformDirection(Vector2.right * rigidbody2D.velocity.magnitude);
else
rigidbody2D.velocity = transform.TransformDirection(Vector2.right * -rigidbody2D.velocity.magnitude);
}
if (Input.GetKey(KeyCode.UpArrow))
rigidbody2D.AddForce(transform.TransformDirection(Vector2.right * ShipThrust));
if (Input.GetKey(KeyCode.DownArrow))
rigidbody2D.AddForce(transform.TransformDirection(-Vector2.right * ShipThrust));
if (Input.GetKey(KeyCode.Space))
Shoot();
}
// healing..
if (hits < 1)
{
hits += .005f;
if (hits > 1)
hits = 1;
float c = Mathf.Lerp(0, 1, hits);
gameObject.renderer.material.color = new Color(1, c, c + .25f, 1);
if(networkView.isMine)
UpdateNetworkPlayer();
}
GetComponentInChildren<GUIText>().transform.position = Camera.main.WorldToViewportPoint(transform.position + (Vector3.down * 1));
}
// Method to set an RPC call to all the players on the network to update it's data for this player.
public void UpdateNetworkPlayer()
{
networkView.RPC("UpdatePlayer", RPCMode.AllBuffered, networkView.viewID.ToString(), DisplayName, DisplayStatus + "\n" + hits.ToString());
}
[RPC]
void UpdatePlayer(string id, string playerName, string status)
{
FindObjectOfType<NetworkManager>().UpdatePlayer(id, playerName, status);
}
void OnGUI()
{
if (networkView.isMine)
{
GUI.Label(new Rect(8, 8, 100, 24), "Your Name:");
DisplayName = GUI.TextArea(new Rect(80, 8, 110, 20), DisplayName, 13);
DisplayName = DisplayName.Replace("\n", "").Replace("\r", "");
UpdateNetworkPlayer();
}
List<PlayerControler> otherPlayers = new List<PlayerControler>(FindObjectsOfType<PlayerControler>());
GUI.Box(new Rect(8, 32, 300, 24 * (otherPlayers.Count + 1)), "Players");
if (networkView.isMine)
GUI.Label(new Rect(12, 56, 300, 24), string.Format("{0} [{1}]", DisplayStatus.Replace("\n"," : "), new Vector2(transform.position.x,transform.position.y)));
// Display the others..
int line = 1;
foreach (PlayerControler plr in otherPlayers)
{
if (!plr.networkView.isMine)
GUI.Label(new Rect(12, 56 + (24 * line++), 300, 24), string.Format("{0} [{1}]", plr.DisplayStatus.Replace("\n", " : "), new Vector2(plr.transform.position.x, plr.transform.position.y)));
}
}
}
We have a number of fields and properties here and they are all only relevant to the game play rather than the networking, so how fast the ship can rotate or translate, it’s score, kills, variable used for shooting and some prefabs for the bullets and sound. Lets work our way through the methods…
void Awake()
In here, we check if this is running as the players instance or another players instance, this is done by looking at the networkview and it’s flag isMine, if it is we set the camera so it is looking at this game object, we don’t want to be looking at other players do we…
void Awake()
{
// assign me to the camera if this is my network session..
if (networkView.isMine)
Camera.main.GetComponent<CameraControler>().Player = gameObject;
}
public void Shoot()
This method is used to create bullets and shoot them, two per shot.
public void Shoot()
{
if (DateTime.Now.TimeOfDay - lastShot >= shootDelay)
{
lastShot = DateTime.Now.TimeOfDay;
GameObject bullet = (GameObject)Network.Instantiate(BulletPrefab, transform.TransformPoint(new Vector3(.5f, .125f, 0)), Quaternion.identity, 0);
bullet.GetComponent<BulletControler>().owner = gameObject;
bullet.transform.rotation = transform.rotation;
bullet.rigidbody2D.velocity = transform.TransformDirection(Vector3.right) * 8;
bullet = (GameObject)Network.Instantiate(BulletPrefab, transform.TransformPoint(new Vector3(.5f, -.125f, 0)), Quaternion.identity, 0);
bullet.GetComponent<BulletControler>().owner = gameObject;
bullet.transform.rotation = transform.rotation;
bullet.rigidbody2D.velocity = transform.TransformDirection(Vector3.right) * 8;
}
}
void OnTriggerEnter2D(Collider2D collider)
This is our trigger, if we get hit by a bullet we deal with it as we need to, taking damage, increasing the shooters score and if we die, instantiate a sound effect, re spawn and remove us from the scene.
N.B need to change the re spawn code to re connect rather than just try and add a new player object.
void OnTriggerEnter2D(Collider2D collider)
{
BulletControler bullet = collider.gameObject.GetComponent<BulletControler>();
if (bullet)
{
PlayerControler plr = bullet.owner.GetComponent<PlayerControler>();
hits -= .1f;
if (hits <= 0)
{
Instantiate(BoomPrefab, transform.position, Quaternion.identity);
plr.Kills++;
plr.Score += 1000;
// Spawn client player
if(networkView.isMine)
FindObjectOfType<NetworkManager>().SpawnClientPlayer(DisplayName);
Network.Destroy(gameObject);
}
plr.UpdateNetworkPlayer();
UpdateNetworkPlayer();
plr.Score += 100;
}
}
void Update()
In here we have the player controls, note, you really should not write them like this, use the Unity configurable keys, not like this :S Note the use of networkView.isMine so we only move our player not all in the scene. Also, I keep the text for the player bound to it by using
GetComponentInChildren<GUIText>().transform.position = Camera.main.WorldToViewportPoint(transform.position + (Vector3.down * 1));
Also notice, I don’t have to send anything down the network, the transform is sent by the networkView :)
void Update()
{
if (networkView.isMine)
{
if (Input.GetKey(KeyCode.LeftArrow))
transform.Rotate(Vector3.forward, ShipRotationSpeed);
if (Input.GetKey(KeyCode.RightArrow))
transform.Rotate(Vector3.back, ShipRotationSpeed);
if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.RightArrow))
{
if (transform.InverseTransformDirection(rigidbody2D.velocity).x > 0)
rigidbody2D.velocity = transform.TransformDirection(Vector2.right * rigidbody2D.velocity.magnitude);
else
rigidbody2D.velocity = transform.TransformDirection(Vector2.right * -rigidbody2D.velocity.magnitude);
}
if (Input.GetKey(KeyCode.UpArrow))
rigidbody2D.AddForce(transform.TransformDirection(Vector2.right * ShipThrust));
if (Input.GetKey(KeyCode.DownArrow))
rigidbody2D.AddForce(transform.TransformDirection(-Vector2.right * ShipThrust));
if (Input.GetKey(KeyCode.Space))
Shoot();
}
// healing..
if (hits < 1)
{
hits += .005f;
if (hits > 1)
hits = 1;
float c = Mathf.Lerp(0, 1, hits);
gameObject.renderer.material.color = new Color(1, c, c + .25f, 1);
if(networkView.isMine)
UpdateNetworkPlayer();
}
GetComponentInChildren<GUIText>().transform.position = Camera.main.WorldToViewportPoint(transform.position + (Vector3.down * 1));
}
public void UpdateNetworkPlayer()
This method is the only one that does ANY network calls, and all it is doing is calling the UpdatePlayer RPC method I have set up in the PlayerControler which we will look at next.
public void UpdateNetworkPlayer()
{
networkView.RPC("UpdatePlayer", RPCMode.AllBuffered, networkView.viewID.ToString(), DisplayName, DisplayStatus);
}
As you can see, this RPC call is being sent to ALL clients in this session. You have a few options on how you make RPC calls, the whole enum looks like this
namespace UnityEngine
{
public enum RPCMode
{
Server = 0,
Others = 1,
All = 2,
OthersBuffered = 5,
AllBuffered = 6,
}
}
So, Server, would send to just the server player and none of the others, Others is to everyone except myself, All, is, you guessed it, to everyone. the buffered options mean that the server will store them and when players join later it will forward them on to them. I have chosen buffered so I don’t have to send the update all the time, just when I make changes to the data, then when new players join they will have the latest data on each players status.
I could have gone with OthersBuffered, but I wanted to have the code to update the status in one place and this lets me do this or I would have to make a second call to update myself as well as the other players on the network.
[RPC]
void UpdatePlayer(string id, string playerName, string status)
And so we come to the actual RPC call it’s self, again keeping the code in one place I decided the best place to put this was in the network manager code, so I pass on all the prams to it’s method and it goes through all the players in the scene and if it finds a matching ID, updates them.
[RPC]
void UpdatePlayer(string id, string playerName, string status)
{
FindObjectOfType<NetworkManager>().UpdatePlayer(id, playerName, status);
}
void OnGUI()
And here is where I set up the GUI, so a field for you to enter your player name and also show all the known players and their stats that are currently in play.
void OnGUI()
{
if (networkView.isMine)
{
GUI.Label(new Rect(8, 8, 100, 24), "Your Name:");
DisplayName = GUI.TextArea(new Rect(80, 8, 110, 20), DisplayName, 13);
DisplayName = DisplayName.Replace("\n", "").Replace("\r", "");
UpdateNetworkPlayer();
}
List<PlayerControler> otherPlayers = new List<PlayerControler>(FindObjectsOfType<PlayerControler>());
GUI.Box(new Rect(8, 32, 300, 24 * (otherPlayers.Count + 1)), "Players");
if (networkView.isMine)
GUI.Label(new Rect(12, 56, 300, 24), string.Format("{0} [{1}]", DisplayStatus.Replace("\n"," : "), new Vector2(transform.position.x,transform.position.y)));
// Display the others..
int line = 1;
foreach (PlayerControler plr in otherPlayers)
{
if (!plr.networkView.isMine)
GUI.Label(new Rect(12, 56 + (24 * line++), 300, 24), string.Format("{0} [{1}]", plr.DisplayStatus.Replace("\n", " : "), new Vector2(plr.transform.position.x, plr.transform.position.y)));
}
}
As I mentioned earlier in this post, there are other bits in here too, like the star field, the sound and the bullets, but as I am giving you all the code anyway, I don’t see the point to showing these here as this was really about how I set up the networking, and as you can see it’s pretty simple really.
Now, naturally there are things in here I want to change, but, it works, well enough for a start and I would have never gotten around to writing this post if I kept adding stuff so forgive me if this post does not go far enough for you.
As ever, all comments and criticisms are welcome, all you have to do is post them.
If you want to get all the source for this you can find it all in a zip file
here.
If you want to play the game as it currently stands then have a look
here.