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.

Tuesday, 25 February 2014

Unity3D: Scrolling 2D Background

I came across a Facebook Unity3D user group post where someone was having an issue with a scrolling 2D background. The OP was doing the scrolling programmatically and was ending up having a gap appear in the background. I did exactly the same thing a while back, but found that a better way of doing this in Unity3D was to use Animators and Animation Clips.
So, here is a tutorial on how to do this, Ill start off creating a 2D project in Unity3D
image
We are now going to create a Texture\Backgrounds folder
image
I have a number of background textures, that I am going to use:
BGForeGround_1_1BGForeGround_1_2BGForeGround_1_3
But, for now we will work with the first texture, we will get to the others in a little while. First thing we need to do is make sure that out images are imported as sprites.
image
Next thing we are going to do is set up the sprite on the screen in relation to the camera, so just drag and drop the first image into the scene.
image
Now, we are going to set the textures position to 0,0,1 and stretch it to fit the full view of the camera, so we will scale it to 1.2, 2.4,1
image
Now, make a copy of it, and paste it into the scene, line it up with the edge of the first texture by setting the position to 19.125,0,0
image
Create an empty GameObject, name it Background and in the Hierarchy drag the two images we have just set up in the scene into it.
image
In our container GameObject, add an Animator component, so Add Component, Miscilanious and select Animator.
image
We are now going to create our Animator, first create a folder called Animation
image
In here we will add another two folders Clips and Controllers
image
Now create an Animator Controller
image
Name it BackgroundAnimation, double click it and we will see the Animator window. Now in clips, create a new Animation.
image
Name is BGScroller. Add this Animation to the BackgroundAnimation Animator Controller by dragging and dropping it in and set the speed to .125
image
image
Now, in our Background GameObject (the one that is holding our sprites) set the Animator to use the new BackgroundAnimator we just set up.
image
With the Background GameObject selected open up the Animation Window
image
Select Add Curve, select Transform and Position
image
Expand this out, to show the elements of the position element, drag the far right set of dots out to the 10 second mark and set the x element to -19.125.
image
Now run it, it will now smoothly scroll our textures from right to left :D, BUT, after 10 seconds it stops (if played at the speed of 1) :S not what we want, so, go into the BGScroller Animation and set the Loop Time to true.
image
Run it now, and it will scroll on and on, looping lovely with no issue :)

Parallax

So, what if we want some parallax scrolling background?
Well, we just do what we did before, but we will create a different Animator Controller for each layer. First thing ill do is set the first to background z positions to 0, but set their container, Background", z to 1, this was we can set the layer order.
So, for the next layer, we add the next texture like before.
image
As before, create an empty GameObject to put them in and call it Background2 and add an Animator component to it.
image
Now in the controller folder create a new Animator Controller and call it Backgrouhd2Animation, drag and drop the same Animation clip into it, BGScroller, but we will set the speed to .25 and then set Background2’s Animator to the new controller Background2Animation.
Run it and the two sets of backgrounds scroll at different speeds :D
Now do the same for the another set of backgrounds, this time set the animation speed to .5, then we will create another empty GameObject called Backgrounds, and put all three of our container GameObjects in it.
image
So, this is what it will look like
You can get the project for this here

Tuesday, 11 February 2014

Direct3D Rendering Cookbook By Justin Stenning

Packt Publishing have asked me to review another book, the Direct3d Rendering Cookbook

I didn’t know where to post this, my many blogs are either XNA (C#), DX11 C++ or Unity3D, or a combination of the three lol, anyway, decided as this is my most recent blog, to post it here, hope it’s not too off topic :P

7101OT_Direct3D Rendering Cookbook

One of the first things I noticed and liked as a .NET developer is that it’s for C# developers using SharpDX. I have never used SharpDX before, have played a little with SlimDX when I was helping out on the ST: Excalubur project (which you should check out, the guys are doing an amazing job with it, check out the Youtube channel too)

Chapter 1

The first chapter lays out the fundamental’s for getting up and running, like most books of this ilk as well as going into the graphics pipeline, and it’s pretty in depth I have to say. The chapter then covers creating your first project using the SharpDX library with great detail and a fair bit of depth.

Chapter 2

Goes into great detail again on setting up your code to render 3D, again, this is quite in depth and a lot of info is given. What I do find odd is that the creation of the renderer code is described as a recipe, I know there are a number of ways to do this, but I would not have had this as a recipe as it’s pretty fundamental to you rendering full stop, it’s something you should have in place, and this chapter should really just be a way of doing it. To me, its more of a preparation step than a recipe…None the less, there is great detail again here.

Chapter 3

We are then given recipes on rendering meshes, we start of creating the mesh in code, then look into some simple lighting techniques and texturing and thankfully (as I think this tends to get missed out a fait bit) it gives and example of loading a mesh from file, and what I do like is that Blender3D is used :D Great stuff..

Chapter 4

Then moves on to skinned meshes, again, this is a great chapter and full of detail :)

Chapter 5

Looks at Hardware Tessellation, some great detail here too, the diagrams are great at for describing the pipeline. It goes on to describe tessellating bicubic Bezier surfaces, refining a mesh with Phong tessellation and loads more.

Chapter 6

Covers normal and displacement mapping, and again, this goes into masses and masses of detail, with some great screen shots

Chapter 7

We take a break from geometry and move on to image processing. There is some awesome stuff in here too, from desaturation, contrast and brightness to implementing box blur, gaussian blur and Sobel edge detection and luminance histograms.

Chapter 8

Covers using a physics engine, and again, I am glad to see BulletSharp being used as I have used it in my own engines, including my XNA Illuminati deferred lighting engine. This chapter also covers rendering waves (personally prefer this being done as a post process) and instanced particles, again another topic close to my heart as again I use them in my own engine.

Chapter 9

Now goes even deeper with rendering on multiple threads and deferred contexts, now, due to me coming from a DX9 background, this was very interesting.

Chapter 10

Moves onto Deferred Rendering, another topic I love having implemented it in XNA :)  Again, masses and masses of detail here, this book has really got me wanting to start writing my own engine again lol

Chapter 11

Covers integrating Direct3D into Windows 8.1 and the use of XAML, this chapter will be of more use to me once I finally get a decent development machine. Hoping to sort that out some time this year :) Again though, lots and lost of details in here.

In Summary…

I loved this book, even if you are not a C# developer, you can port/include these techniques to/in your C++ engine. If you are just starting out, this may not be the book for you, if you have been working with earlier versions of DX and looking for an interesting read, then I don’t think you can go wrong with this book.

I have reviewed a few books by Packt Publishing, and I have to say, they have tended to turn up short, however, the last two books I have reviewed have been truly excellent, keep up the good work :) Check out my last review here.

Where can I get this book?

here.

Sunday, 22 December 2013

Learning Windows 8 Game Development Written by Michael Quandt

Packt

I have reviewed one or two books that have been published by Packt publishing, and I have to be honest, the content, while normally accurate, has been a little thin on the ground. I am pleased to say, not with this book!
I am pleased to say this as I have known the author for a number of years. As is the case with on line communities, I have never met him physically, but I have known Michael for many years.

Chr0n1x

I first encountered him when I started to get into XNA on a well know community board called the Hazy Mind, he went by the name Chr0n1x then, board was ran by another XNA/DX guru Micael Schuld. Chr0n1x was one of the main go to guys on that board after the admin, and he helped me a great deal, not just in the early days but even today with various GPU related issues I have. At that time I don’t think Michael was even in college/Uni then but his understanding and knowledge of the GPU and it’s pipeline was hard to beat.

The Book

So, enough of all that, what about this book, we know it’s rich in content, but is it any good? I think it is, it takes you right from the basics of setting up your project and creating a simple game loop along with a graphics device, drawing sprites and using input devices and accelerometers to live tiles, networking publishing to the store and monetization. Now, I know, this all sounds a bit 2D, but in the appendix Michael quickly covers some of the basics of 3D too, but to be honest, you really need the first lot of tools and can create a great 2D game, but you still need the skills from the previous chapters to get your game together whether its 2D or 3D.

Should I buy this book?

If you are not new to C++, but new to DirectX and/or game development and want a great book to get you into it, and take you to an intermediate level then, yes, this is the book for you. If you know C++ and have done some game development, then yes, there is some great stuff in here for building games for Windows 8, personally I am going to find it very useful for padding out a lot of holes in my C++ and DX knowledge.

Anything Missing?

The only thing I would have liked to have seen in this book was audio, it gets a mention, but there is no implementation shown, which is a shame, but you know what, the amount of stuff that’s in here, you can find this out somewhere else anyway.

Where Is It?

You can get a copy of the book here.

Friday, 13 December 2013

Reinventing some of Unity’s Wheels: Terrain

Why?

First of all, why? Why create this when there is a perfectly good terrain package that comes with Unity? Well, I do like to have a mess about and find out what I can do, no other reason than that really, why? Because I can :) I dare say that the Unity Terrain asset is a much better way of creating your terrain, having not really played with it I don’t know, but I would like to implement my version of Geo Clip mapping see here:
And to do that I am going to need to implement height maps and generate the geometry as I need to. Also, later on I may want to procedurally generate my terrain, and I am guessing Ill need something like this.

How?

I started off trying to find out if I could generate my own geometry in Unity, and it turns out you can, this was great news on my quest as it meant I can pass my script a height map and using that to generate the mesh needed to render the terrain.
I started off creating a script called TerrainGenerator in my Unity project, in here I added a number of public parameters so they can be set in the editor and I can pass in the height map and the min and max height of my terrain, the txMod is just a variable I can use to modify the texcoords in the vertex, it’s a throw back to the version I did in XNA, and as you can change an assets tiling in the editor, probably not needed now.
public class TerrainGenerator : MonoBehaviour
{
    public Texture2D HeightMap;

    public float min = 0;
    public float max = 30;

    public float txMod = 4.25f;
I then add an Awake method, this gets called when the script is instanced, in here is where I read the height map and turn it into a mesh, this mesh, if a mesh collider is provided can then be used for that mesh collider, naturally you can’t set this in the editor as the mesh has not been created yet :)
Naturally you will need a mesh filter to store the mesh in, if you don’t Unity will throw an error when you go to set the mesh in it. I then check to see if a height map has been given and if not generate a plane and render that instead.
Here is the script in it’s entirety:-
public class TerrainGenerator : MonoBehaviour
{
    public Texture2D HeightMap;

    public float min = 0;
    public float max = 30;

    public float txMod = 4.25f;

    int myWidth;
    int myHeight;
    float[,] height;



    void Awake()
    {
        MeshFilter meshFilter = GetComponent<MeshFilter>();

        meshFilter.mesh = new Mesh();

        if (HeightMap != null)
        {
            myWidth = HeightMap.width;
            myHeight = HeightMap.height;

            transform.position = new Vector3(transform.position.x - (myWidth / 2), transform.position.y, transform.position.z - (myHeight / 2));

            Color[] heightMapCol = new Color[myWidth * myHeight];

            Vector3[] verts = new Vector3[myWidth * myHeight];
            Vector3[] normals = new Vector3[myWidth * myHeight];
            Vector2[] uv = new Vector2[myWidth * myHeight];
            Vector4[] tan = new Vector4[myWidth * myHeight];
            Color[] col = new Color[myWidth * myHeight];

            height = new float[myWidth, myHeight];

            heightMapCol = HeightMap.GetPixels();

        
            for (int x = 0; x < myWidth; x++)
                for (int y = 0; y < myHeight; y++)
                    height[x, y] = Mathf.Lerp(min, max, heightMapCol[x + y * myWidth].r);


            // Verts
            for (int x = 0; x < myWidth; x++)
                for (int y = 0; y < myHeight; y++)
                {
                    verts[x + y * myWidth] = new Vector3(y, height[x, y], x);
                    normals[x + y * myWidth] = Vector3.up;
                    uv[x + y * myWidth] = new Vector2((float)x / (myWidth / txMod), (float)y / (myHeight / txMod));

                    // blend
                    col[x + y * myWidth].r = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - 0) / 8, 0, 1);
                    col[x + y * myWidth].g = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - 12) / 6, 0, 1);
                    col[x + y * myWidth].b = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - 20) / 6, 0, 1);
                    col[x + y * myWidth].a = Mathf.Clamp(1.0f - Mathf.Abs(height[x, y] - 30) / 6, 0, 1);

                    float totalWeight = col[x + y * myWidth].r;
                    totalWeight += col[x + y * myWidth].g;
                    totalWeight += col[x + y * myWidth].b;
                    totalWeight += col[x + y * myWidth].a;

                    col[x + y * myWidth].r /= totalWeight;
                    col[x + y * myWidth].g /= totalWeight;
                    col[x + y * myWidth].b /= totalWeight;
                    col[x + y * myWidth].a /= totalWeight;
                }

        
            // Calc normals
            for (int x = 0; x < myWidth; x++)
                for (int y = 0; y < myHeight; y++)
                {
                    // Tangent Data.
                    if (x != 0 && x < myWidth - 1)
                        tan[x + y * myWidth] = verts[x + 1 + y * myWidth] - verts[x - 1 + y * myWidth];
                    else
                        if (x == 0)
                            tan[x + y * myWidth] = verts[x + 1 + y * myWidth] - verts[x + y * myWidth];
                        else
                            tan[x + y * myWidth] = verts[x + y * myWidth] - verts[x - 1 + y * myWidth];

                    tan[x + y * myWidth].Normalize();

                    // Normals
                    Vector3 normX = Vector3.one;
                    Vector3 normZ = Vector3.one;

                    if (x != 0 && x < myWidth - 1)
                        normX = new Vector3((verts[x - 1 + y * myWidth].y - verts[x + 1 + y * myWidth].y) / 2, 1, 0);
                    else
                        if (x == 0)
                            normX = new Vector3((verts[x + y * myWidth].y - verts[x + 1 + y * myWidth].y) / 2, 1, 0);
                        else
                            normX = new Vector3((verts[x - 1 + y * myWidth].y - verts[x + y * myWidth].y) / 2, 1, 0);

                    if (y != 0 && y < myHeight - 1)
                        normZ = new Vector3(0, 1, (verts[x + (y - 1) * myWidth].y - verts[x + (y + 1) * myWidth].y) / 2);
                    else
                        if (y == 0)
                            normZ = new Vector3(0, 1, (verts[x + y * myWidth].y - verts[x + (y + 1) * myWidth].y) / 2);
                        else
                            normZ = new Vector3(0, 1, (verts[x + (y - 1) * myWidth].y - verts[x + (y) * myWidth].y) / 2);

                    normals[x + y * myWidth] = normX + normZ;
                    normals[x + y * myWidth].Normalize();
                }

        
            meshFilter.mesh.vertices = verts;
            meshFilter.mesh.tangents = tan;
            meshFilter.mesh.normals = normals;
            meshFilter.mesh.uv = uv;
            meshFilter.mesh.colors = col;

            // Index
            int[] terrainIndices = new int[(myWidth - 1) * (myHeight - 1) * 6];
            for (int x = 0; x < myWidth - 1; x++)
            {
                for (int y = 0; y < myHeight - 1; y++)
                {
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 5] = ((x + 1) + (y + 1) * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 4] = ((x + 1) + y * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 3] = (x + y * myWidth);

                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 2] = ((x + 1) + (y + 1) * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6 + 1] = (x + y * myWidth);
                    terrainIndices[(x + y * (myWidth - 1)) * 6] = (x + (y + 1) * myWidth);
                }
            }

            meshFilter.mesh.triangles = terrainIndices;
        }
        else
        {

            // Verts
            Vector3[] verts = new Vector3[4];

            verts[0] = new Vector3(-1, 0, -1);
            verts[1] = new Vector3(1, 0, -1);
            verts[2] = new Vector3(-1, 0, 1);
            verts[3] = new Vector3(1, 0, 1);

            meshFilter.mesh.vertices = verts;

            // Index
            int[] index = new int[6];

            index[0] = 0;
            index[1] = 2;
            index[2] = 1;

            index[3] = 2;
            index[4] = 3;
            index[5] = 1;

            meshFilter.mesh.triangles = index;

            // Normals
            Vector3[] normals = new Vector3[4];

            normals[0] = Vector3.up;
            normals[1] = Vector3.up;
            normals[2] = Vector3.up;
            normals[3] = Vector3.up;

            meshFilter.mesh.normals = normals;

            // UV
            Vector2[] uv = new Vector2[4];

            uv[0] = new Vector2(0, 0);
            uv[1] = new Vector2(1, 0);
            uv[2] = new Vector2(0, 1);
            uv[3] = new Vector2(1, 1);

            uv[0] = Vector3.zero;
            uv[1] = Vector3.zero;
            uv[2] = Vector3.zero;
            uv[3] = Vector3.zero;

            meshFilter.mesh.uv = uv;
        }

        MeshCollider mc = GetComponent<MeshCollider>();

        if (mc != null)
            mc.sharedMesh = meshFilter.mesh;
    }

}
Now, a word of warning, in order for this to work you MUST set your heightmap textures in Unity to be readable, by default the texture importer locks it, simply select your height map, choose the “Advanced” texture type and set the “Read\Write” enabled to true. If you don’t do this you will get an error on the line calling GetPixels()
I had a bit of a shock when I first ported this code from XNA, nothing rendered, but I had no errors, then I found out that Unity is left handed, so I just had to reverse the winding order of the index and it worked :) something to be mindful of if you are porting code from XNA.
So, that will generate your mesh AND if you give it a mesh collider, will add it to the Unity Physics system too, it was quite cool to then just add some sphere’s and cubes, give them a rigid body and watch them roll and tumble down the hills :D
So, you can now render the terrain and with a diffuse shader will look something like this:
885017_10151806984342218_1902863551_o[1]
If you look at the code above, you can see I am setting the vertex colour, I am actually not using this as a colour, but a blend weight used to blend the textures as the height of the terrain alters, my custom shader will now use that value to determine what textures should be used where. Also note that Unity (well there may be a way around this, but I am new to Unity so forgive me) will not render large height maps with this technique, if I go for a big height map texture I get this error:
Mesh.vertices is too large. A mesh may not have more than 65000 vertices.
UnityEngine.Mesh:set_vertices(Vector3[])  

Custom Terrain Shader

Again, this was a pretty simple port from my XNA sample to Unity, main issue I had was working out how to extend the vertex structure, but finding you can’t and having to use the Color element of the vertx to store my blend values in. I guess I could calculate them in the shader, but that might be another post ;) Again, the online Unity docs were a massive help with this.
My shader properties are pretty simple, 4 textures along with their bump maps:
Properties
{
  _MainTex ("Dirt", 2D) = "red" {}
  _BumpMap ("Dirt Bumpmap", 2D) = "bump" {}

  _MainTex2 ("Grass", 2D) = "green" {}
  _BumpMap2 ("Grass Bumpmap", 2D) = "bump" {}

  _MainTex3 ("Stone", 2D) = "grey" {}
  _BumpMap3 ("Stone Bumpmap", 2D) = "bump" {}

  _MainTex4 ("Snow", 2D) = "white" {}
  _BumpMap4 ("Snow Bumpmap", 2D) = "bump" {}
}
I did run out of arithmetic instruction so had to set the SM to 3.0 as well as add a vertex fragment to the shader to push on my blend data like this
#pragma surface surf Lambert vertex:vert
#pragma target 3.0
Then we just calculate the texture and bump map values based on the blend settings in the surface shader :)
void vert (inout appdata_full v, out Input o)
{
    UNITY_INITIALIZE_OUTPUT(Input,o);
    o.customColor = abs(v.color);
}

void surf (Input IN, inout SurfaceOutput o)
{

  float3 col = 0;
  col = tex2D(_MainTex,IN.uv_MainTex) * IN.customColor.x;
  col += tex2D(_MainTex2,IN.uv_MainTex) * IN.customColor.y;
  col += tex2D(_MainTex3,IN.uv_MainTex) * IN.customColor.z;
  col += tex2D(_MainTex4,IN.uv_MainTex) * IN.customColor.w;

  o.Albedo = pow(col * .5,1.25);

  float3 n = 0;
  n = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap) * IN.customColor.x);
  n += UnpackNormal (tex2D (_BumpMap2, IN.uv_BumpMap) * IN.customColor.y);
  n += UnpackNormal (tex2D (_BumpMap3, IN.uv_BumpMap) * IN.customColor.z);
  n += UnpackNormal (tex2D (_BumpMap4, IN.uv_BumpMap) * IN.customColor.w);
  o.Normal = n;
}
And shazam! The blighter only worked :D
1425267_10151810425112218_1951188246_o[1]
To implement it, I created an empty game object, gave it the terrain script, a mesh filter, a mesh renderer and a mesh collider, I then changed the shader to be my custom\terrainshader set the params on script and shader as I wanted.
There are a few other bits and bobs in there to iron out and polish, but think I am now ready to move on to that geo clip map implementation :) I hope it’s going to be as easy as I think it will be….
Here is my shader in full
Shader "Custom/TerrainShader"
{
    Properties
    {
      _MainTex ("Dirt", 2D) = "red" {}
      _BumpMap ("Dirt Bumpmap", 2D) = "bump" {}

      _MainTex2 ("Grass", 2D) = "green" {}
      _BumpMap2 ("Grass Bumpmap", 2D) = "bump" {}

      _MainTex3 ("Stone", 2D) = "grey" {}
      _BumpMap3 ("Stone Bumpmap", 2D) = "bump" {}

      _MainTex4 ("Snow", 2D) = "white" {}
      _BumpMap4 ("Snow Bumpmap", 2D) = "bump" {}
    }
    SubShader
    {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
  
      #pragma surface surf Lambert vertex:vert
      #pragma target 3.0

      struct Input
      {
        float2 uv_MainTex;
        float2 uv_BumpMap;

        float4 customColor;
      };
  
      sampler2D _MainTex;
      sampler2D _BumpMap;

      sampler2D _MainTex2;
      sampler2D _BumpMap2;

      sampler2D _MainTex3;
      sampler2D _BumpMap3;

      sampler2D _MainTex4;
      sampler2D _BumpMap4;

      void vert (inout appdata_full v, out Input o)
      {
          UNITY_INITIALIZE_OUTPUT(Input,o);
          o.customColor = abs(v.color);
      }
  
      void surf (Input IN, inout SurfaceOutput o)
      {

        float3 col = 0;
        col = tex2D(_MainTex,IN.uv_MainTex) * IN.customColor.x;
        col += tex2D(_MainTex2,IN.uv_MainTex) * IN.customColor.y;
        col += tex2D(_MainTex3,IN.uv_MainTex) * IN.customColor.z;
        col += tex2D(_MainTex4,IN.uv_MainTex) * IN.customColor.w;

        o.Albedo = pow(col * .5,1.25);

        float3 n = 0;
        n = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap) * IN.customColor.x);
        n += UnpackNormal (tex2D (_BumpMap2, IN.uv_BumpMap) * IN.customColor.y);
        n += UnpackNormal (tex2D (_BumpMap3, IN.uv_BumpMap) * IN.customColor.z);
        n += UnpackNormal (tex2D (_BumpMap4, IN.uv_BumpMap) * IN.customColor.w);
        o.Normal = n;
      }
      ENDCG
    }
    Fallback "Diffuse"
  }
As ever, comments are more than welcome.

[Edit]
The discovery of the class attribute [ExecuteInEditMode] means that my height map terrain can now be viewed in the editor too :D Awesome !!



[/Edit]


Monday, 2 December 2013

Unity3D 4.3 and WCF Web Service

This post is about how I got Unity3D to talk to my WCF web service. I don’t know if this will be of any use in the future for development on the XB1 as I have no idea what MS are going to allow on their network, I am guessing it will be quite locked down, just as it was for XNA on the 360, but felt it was worth looking at anyway, as I am using Unity3D I may want to deploy to other platforms anyway, so this could still be useful.

So, why can’t I just add a service reference and use that?

Well, you can, and in VS it will build, but, Unity3D won’t have a clue what you are trying to reference..

What helped me find the solution I came to?

As you do, I had a mooch on google, and came across a number of post and cross posts that pointed to this method of accessing a web service in Unity. For some reason, when I used this method, Unity3D would complain about the System.ServiceModel namespace, so, I thought, after finding this link saying I can just drop an assembly in and it gets picked up by Unity3D, I dragged and drop System.ServiceModel and System.Runtime.Serialization into my assets script folder. All this seemed to do was throw up another issue, with the HTTP binding. The assemblies I was adding were .NET 3.0, as I now know, Unity3D is really only compliant with 2.0, though I am sure I read somewhere that it’s a bit of a mix between 2.0 and 3.5, what ever the case, it didn’t like me assembles. In the original post I found it mentioned moving the assemblies to the mono folder, naturally I ignored this, I was not using mono, but, it gave me the idea to start looking around the Unity3D mono installation, and I found a set of C# assemblies in there, two of which are the ones I wanted, so, in an act of desperation, I copied those two assemblies over into my assets script folder….and it only bloody worked!

How am I doing it now?

So, create your WCF service as you would normally, I am using .NET4.0 for mine. Before you do anything else, create a WebClient folder in your script folder, I do this just to keep it separate, you could just drop it anywhere in your assets folder I guess.
As in the original post describing how to do this I use svcutil to build the proxy class for my Unity3d scripts to use, I created a very simple cmd file that I can run for this, before you run it, make sure your service is running, or it wont be able to build it.

cd "f:\development\unity\killercore\webservice"
cls
svcutil -out:F:\Development\Unity\KillerCore\Assets\Scripts\WebClient\KillerCoreWebService.cs http://localhost:9997/KillerCoreWebService.svc?wsdl

copy "C:\Program Files (x86)\Unity\Editor\Data\Mono\lib\mono\2.0\System.Runtime.Serialization.dll" "F:\Development\Unity\KillerCore\Assets\Scripts\WebClient\System.Runtime.Serialization.dll"
copy "C:\Program Files (x86)\Unity\Editor\Data\Mono\lib\mono\2.0\System.ServiceModel.dll" "F:\Development\Unity\KillerCore\Assets\Scripts\WebClient\System.ServiceModel.dll"

IMPORTANT: Only run this cmd in a Visual Studio Command prompt.
As you can see it copies my resulting code into my script folder, it then also copies the mono assemblies I need into that folder too. Switch back to Unity3D and it will load the new cs script and dll’s
KCUWSRef
And that is pretty much that, I can now access my WCF web service from with in my Unity3D game.

KillerCoreWebService Call

My very simple service has one method at the moment called GetHighScoreTable, and for the sake of testing looks like this:
public HighScoreResponse GetHighScoreTable()
{
    HighScoreResponse hst = new HighScoreResponse();

    for (int x = 0; x < 10; x++)
        hst.HighScoreTable.Add(new HighScoreEntity(string.Format("High Score {0}", x + 1), (x + 1 * 501).ToString()));

    return hst;
}
So, my response class holds a list or array of HighScoreEntity, this “entity” just holds a name and a value for a high score, naturally I would wire this up to a database on my server, but this will do for my test, just passing back 10 values.

Client side use of the Web Service

I have a globals script that I use to hold stuff I want to use all over the place, so, the first thing I need is a client object to connect to the service, like this
static KillerCoreWebServiceClient client = new KillerCoreWebServiceClient(new BasicHttpBinding(BasicHttpSecurityMode.None), new EndpointAddress("http://localhost:9997/KillerCoreWebService.svc"));
This client is connecting to the service running on my system, so you would alter the url to point at the service on your server. I then create a static method I can use to call a method on the service.
public static List<HighScoreEntity> GetHighScores()
{
    HighScoreResponse response = client.GetHighScoreTable();

    return response.HighScoreTable.ToList();
}
So, this simply goes off to the web service and gets the high score table.. Now in my test UI I wire it up under a button click.
if (GUI.Button(new Rect(0, 0, 100, 50), "Get Highscores"))
{
    scores = Globals.GetHighScores();
}

if (scores.Count > 0)
{
    GUI.Label(new Rect(10, 110, 100, 20), string.Format("{0} Records", scores.Count));
    int cnt = 0;
    foreach (HighScoreEntity score in scores)
    {
        cnt++;
        GUI.Label(new Rect(10, 110 + (cnt * 20), 100, 20), string.Format("{0} - {1}", score.Name, score.Score));
    }
}
scores is just a list of HighScoreEntity, once populated I display how many records have been returned and the records returned.
1398370_10151784623107218_659661404_o[1]
So, after all my moaning and whinging, it turned out not to be that difficult to set up Unity3D so it can access a WCF web service. I hope this helps make your life a bit easier integrating WCF services into your Unity3D projects. If you find a better way, or have issues with this method, then let me know.

[UPDATE]
Tested this for the Web player and it does not work, I guess just the full executable version, if I do need WS ill end up having to write a post/get class to pull stuff back from a server, unless Unity3D gets an update to the .NET framework I guess.
[/UPDATE]