Friday, 12 December 2014

Object Pooling–Unity3D

I know, I know, there are already lots of solutions out there for this in Unity3D, and you have probably already got your own mechanism in place to handle this, but I thought I would post how I have object pooling in my current game project.
Ill start with an apology, I have anew laptop, have not blogged in a while and don’t seem to be able to find the Windows Live Plugin I use to use for pasting code from Visual Studio.
So, what is object pooling, well the usual application of this is for things like bullets, I am also using it for bombs, missiles as well as blast fragments.
As you can imagine you could end up having to create a lot of these sorts of items on the fly using the Unity3D Intrinsic “Instantiate” which will create a totally new object when called. So, a way around this is to have a group of pre instantiated objects, a pool of objects :)

Object Pooler

Lest look at my pool class, we will start with the variables we are going to use. It’s derived from MonoBehavior so we can attach it to an empty game object in the editor.
    public int StartingPoolSize = 25;

    public GameObject ObjectType;

    List<GameObject> Queue = new List<GameObject>();
    List<GameObject> Live = new List<GameObject>();

    public int TotalObjects = 0;
    public int QueuedObjects = 0;
    public int LiveObjects = 0;

So, we have a starting pool size, I have a default of 25 for this, but in the editor you can set this to be what ever you like.

We then have an ObjectType, this is the GameObject that is going to be pooled.

I then have two lists, one is the list of items that are not yet in use, the Queue list and a list of those in use, the Live list.

I then have a few counters, these are just so I can see what’s going on while I am debugging the pool.




void Start()


Now, the Start method looks like this:

    void Start()
    {
        for (int o = 0; o < StartingPoolSize; o++)
            AddObject();
    }

So, all we are doing here is initializing the Queue list to the size of our default Queue size, so lets look at what the AddObject method is doing.




void AddObject()


    public void AddObject()
    {
        Queue.Add((GameObject)Instantiate(ObjectType));
        Queue[Queue.Count - 1].transform.parent = transform;
        Queue[Queue.Count - 1].gameObject.SetActive(false);
        TotalObjects++;
    }

All that this method is doing is instantiating an object and adding it to the Queue list, for now making it a child of this game object, and ensuring it is not active. I then increment the total objects count.




GameObject InstanciateObject()


So, now we need a method to get a new object from the Queue and put it in the game world, this is where this method comes in.

    public GameObject InstanciateObject()
    {
        if (Queue.Count == 0)
        {
            // Create 3 more...
            for (int i = 0; i < 3; i++)
                AddObject();
        }

        AddLive(Queue[Queue.Count - 1]);

        Queue.RemoveAt(Queue.Count - 1);

        return Live[Live.Count - 1];
    }

The first thing I do is see if there are any object left in the Queue list, the list is reduced each time we take an object and put it in the game world, so if they are all used, we need to pad the list out a little, so if it is empty I create another 3 objects. I guess I could add another public property for growth, so rather than a hard coded 3 the list will grow by this property as it’s set in the editor.

I then call my AddLive method, this moved the object over to the Live list, makes sure it’s ready for the world and then is activated. I then remove this object from the Queue and return the new live object.




void AddLive(GameObject o)


    void AddLive(GameObject o)
    {
        Live.Add(o);

        if (Live[Live.Count - 1].GetComponent<GameObjectDeath>() != null)
        {
            Live[Live.Count - 1].GetComponent<GameObjectDeath>().ReSet();
            Live[Live.Count - 1].GetComponent<GameObjectDeath>().IsPooled = true;
        }        

        Live[Live.Count - 1].gameObject.SetActive(true);
    }

As you can see, it adds the object to the Live list, then there is a bit of code that makes sure that if this object is being used for the nth time, by that I mean, it’s not the first time it’s been used, that I reset my object. I have a GameObjectDeath script, this script will “Destroy” an object if it collides with another object, or it can be given a life span, this bit of code just makes sure that this gets set.

Finally it makes the object active.




void DestroyObject(GameObject o)


    public void DestroyObject(GameObject o)
    {
        o.SetActive(false);
        Queue.Add(o);
        Live.Remove(o);
    }

And so, when an object  is destroyed, rather than calling the intrinsic Unity3D method, we use this method. It deactivates the object and moves it from the Live list back into the Queue.

To use this now, we can create an empty game object in the editor and attach the script, we can then set these variables in the editor like this:

image

As you can see this pool is managing bullets. The bullet object is a prefabricated game object I have already created.








OK, so now we can pool an object, but wouldn't it be great if we could do this for lots of types of object?




ObjectPoolManager


And that’s where this bit of code comes in :D

All we need in here are two variables

    public static ObjectPoolManager thisObjectPoolManager;

    List<ObjectPooler> pools;

I have a static so I can get at the class and it’s methods from any other script and a list of ObjectPoller objects. These will be child game objects held in this game object.




void Start()


 void Start () 
    {
        thisObjectPoolManager = this;

        pools = GetComponentsInChildren<ObjectPooler>().ToList();
    }


In the Start method we set the static to this, then get all the child ObjectPooler game objects.




GameObject InstanciateObject(GameObject gameObj)


    public GameObject InstanciateObject(GameObject gameObj)
    {
        ObjectPooler pl = pools.First(p => p.ObjectType.name == gameObj.name);
        if (pl != null)
            return pl.InstanciateObject();
        else
        {
            print("Can't find pool for " + gameObj.name);
            return null;
        }        
    }

So, now when another script want’s to instantiate a new game object, it will call the manager, the manger in turn then gets the pool related to the object they want to instantiate, by comparing the pool’s ObjectType.name property with the name property of the object to be instantiated, if found it then calls the pools InstanciateObject method and returns the world ready object to the caller.

If it can’t find it (and this will be because you have not set up a pool for the required object) then it will return null.




GameObject InstanciateObject(GameObject gameObj, Vector3 Position, Quaternion rotation)


Sometimes you want p instantiate an object at a position, with a rotation, and this overloaded method will do just that.

    public GameObject InstanciateObject(GameObject gameObj, Vector3 Position, Quaternion rotation)
    {
        GameObject go = InstanciateObject(gameObj);
        go.transform.position = Position;
        go.transform.rotation = rotation;

        return go;
    }





void DestroyObject(GameObject gameObj)


    public void DestroyObject(GameObject gameObj)
    {
        pools.First(p => p.ObjectType.name+"(Clone)" == gameObj.name).DestroyObject(gameObj);
    }

Again, when we want to destroy an object, we call this method, like the InstanciateObject method(s) it finds the required pool, this time it post fixes the ObjectType.name property with “(Clone)” as when you instance an object it has this at the end. I guess you could replace this with a Contains, rather than getting an exact match. Upon finding it, it calls the pool sDestroyObject method passing in the object to be destroyed.





So, in the editor, you can create an empty game object add the ObjectPoolManager script to it, then put all your pool game objects in it like this.

image

Well, that’s my current approach to object pooling, and for the limited way I am using it it’s working out quite well. If you have any questions or suggestions, then please feel free to post here.

I am also a member of the Unity Indie Devs on Facebook, if you are not already a member, join up, join the chatter :)

Thursday, 19 June 2014

Ball Shooter Script–Unity3D

This blog is becoming more and more Unity3D focused, the XB1 program is so quiet I have not had anything to post about, maybe I should just recycle the ID game releases that are happening….
So, was catching up with events on FaceBook and I spotted a post in a group that I am an admin for, asking if anyone knew how to script shooting a ball, as the code they had written was not working as they expected. So, being the kind of person that likes to help people out, I wrote this.
using UnityEngine;
using System.Collections;

public class BallShooter : MonoBehaviour
{

    bool mousehold = false;
    bool shoot = false;

    public float PowerBuild = .1f;

    public float MaxVelocity = 2;

    public float power = 0;

    public GameObject ball;
   
    // Update is called once per frame
    void Update ()
    {
        // Player has clicked the left mouse button...
        if (Input.GetMouseButtonDown(0))
        {
            mousehold = true;
            shoot = false;           
        }

        // Player has released the left mouse button..
        if (Input.GetMouseButtonUp(0))
        {
            if (mousehold)
                shoot = true;

            mousehold = false;
        }

        // While the player has the left mouse button pressed power up the shot..
        if (mousehold && power < 1)
        {
            power += PowerBuild;
            if (power > 1)
                power = 1;
        }

        // Shoot the ball!!!
        if (shoot)
        {
            shoot = false;
           
            // Get mouse pos in the view.
            Vector3 mp = Camera.main.ScreenToViewportPoint(Input.mousePosition);

            // (.5,.5) is center, so we can elevate and pan the shot angle based on this while creating the velocity.
            Vector3 velocity = new Vector3(MaxVelocity * (mp.x - .5f), MaxVelocity * (mp.y - .5f), MaxVelocity * power);

            // Create the ball.
            GameObject shot = (GameObject)Instantiate(ball, Camera.main.transform.position + Vector3.forward, Quaternion.identity);

            // Give it some velocity.
            shot.rigidbody.velocity = velocity;

            // Reset the power ready for the next shot.
            power = 0;
        }
    }
}
As you can see, it’s a pretty simple script, check if the player has the left mouse button pressed, if they do then set the hold and shoot variables, if the player releases the left mouse button, then set the shoot flag and un set the mouse hold flag.
While the left mouse button is down, build up power for the shot. Once released, set the shoot flag.
If the shoot flag is set, calculate the velocity, create an instance of the ball and apply the velocity to it, and that’s about it…
QuickPic
If you want to get the whole unity scene, then you can download it off my server here.

Thursday, 20 March 2014

Unity Network Game Sample

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.
UnityNetworking
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
UnityNetworkGameSample
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:
image
I then added a C# script to the project and called it NetworkManager and added it to the empty GameObject.
image

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.
image

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.
image
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.