Saturday, February 6, 2016

Using Unity3D's UNET to Make a Multi-Playered Model that Allows Users to Take Turns

Intro
So this is a base model that can be expanded to fit a number of multi-playered games that require the users to take turns, such as board games, Jenga, Battleship, etc... In this tutorial we're going to be making a "game" that allows for an infinite number of players to connect and change the color of a central block when it is their turn. https://youtu.be/c5dOCmh2RMQ

1)     Create a flat plain, color it if you wish.
2)     Create an Empty GameObject and name it NetworkManager
a)     Add the component Network->(Network Manager HUD)
3)     Create an empty GameObject, name it Player. Make it a prefab, delete from the Object hierarchy. Even though the player won’t have a physical body that they will be controlling, this step is necessary for the NetworkManager to manage the players.
a)     Give it a Network->NetworkIdentity Component.
b)     Create and attach the PlayerControllerScript to the Player Object.
using UnityEngine;

using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;

public class PlayerController : NetworkBehaviour
{
    [SyncVar]
    public Color cubeColor;
    [SyncVar]
    private GameObject objectID;
    //determines which player's turn it is
    [SyncVar]
    public int turn;
    //determines which player the current player is
    public uint player;
    private NetworkIdentity objNetId;

    Text playerText;
    Text identityText;

    void OnDestroy()
    {
        GameStateManager.removePlayer((int)player);
    }

    void Start()
    {
        //The network manager assigns network identities dynamically,
        //so just use this value as the current player's id
        player =  this.GetComponent<NetworkIdentity>().netId.Value;

        //add player id to network
        GameStateManager.addPlayer((int)player);

        //get the current turn
        turn = GameStateManager.getPlayerTurn();

        //initialize player text, tells who's turn it is
        playerText = GameObject.FindGameObjectWithTag("PlayerTurnText").GetComponent<Text>();
        playerText.text = "It's working";
        //tells you which player you are
        identityText = GameObject.FindGameObjectWithTag("IdentityText").GetComponent<Text>();
        identityText.text = "Player " + turn;
        identityText.color = new Color(Random.value, Random.value, Random.value, Random.value);
    }

    // Update is called once per frame
    void Update()
    {
        if (isLocalPlayer)
        {
            //checks to see if the player clicked
            CheckIfClicked();
            //makes sure that the player turn text is synced
            CmdGetTurn();
            changeTurnText();
        }
    }

    void CheckIfClicked()
    {
        if (Input.GetMouseButtonDown(0))
        {
            //update the variable
            CmdGetTurn();
            //check if it's my turn, if not, exit out
            if (turn != player) return;
            CmdChangeTurn();

            //change the color
            objectID = GameObject.FindGameObjectsWithTag("Tower")[0];
            cubeColor = new Color(Random.value, Random.value, Random.value, Random.value);
            CmdChangeColor(objectID, cubeColor);
        }
    }
 
    //updates state on the server
    [Command]
    void CmdChangeTurn()
    {
        if (turn == player) GameStateManager.nextTurn();
        turn = GameStateManager.getPlayerTurn();
        //syncs this all clients connected on server
        RpcUpdateTurn(turn);
    }

    /*
    * Changes the turn text of the player
    */
    void changeTurnText()
    {
        if (turn == player)
            playerText.text = "Your turn. ";
        else if (turn != player)
            playerText.text = "Opponent's turn. ";
    }

    [ClientRpc]
    void RpcUpdateTurn(int i)
    {
        turn = i;
        changeTurnText();
    }
       

    [Command]
    void CmdGetTurn()
    {
        turn = GameStateManager.getPlayerTurn();   
    }

    [Command]
    void CmdChangeColor(GameObject go, Color c)
    {
        objNetId = go.GetComponent<NetworkIdentity>();
        objNetId.AssignClientAuthority(connectionToClient);
        RpcUpdateTower(go, c);
        objNetId.RemoveClientAuthority(connectionToClient);
    }

    /*
     * Being sent from server to
     */
    [ClientRpc]
    void RpcUpdateTower(GameObject go, Color c)
    {
        go.GetComponent<Renderer>().material.color = c;
    
    }  
}
4)     Create a Cube Object, and name it Tower. Make it a prefab and delete from the hierarchy.
a)     Give it a Network->NetworkTransform. Ensure that the Transform Sync Mod is “Sync Transform”
5)     Create an empty object named TowerSpawnLoc, make it a prefab, delete from hierarchy.
6)     Create an empty object named GameManager
a)     Create and attach TowerSpawnLoc script to it
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using System;
using UnityEngine.UI;

public class TowerSpawnLoc : NetworkBehaviour
{
    [SerializeField]
    GameObject towerPrefab;
    [SerializeField]
    GameObject towerSpawn;

    public override void OnStartServer()
    {
        SpawnTower();
    }

    void SpawnTower()
    {
        GameObject go = GameObject.Instantiate(towerPrefab, towerSpawn.transform.position, Quaternion.identity) as GameObject;
        NetworkServer.Spawn(go);
    }
}
i) Add the parameters, Tower Prefab->Tower, Tower Spawn->TowerSpawnLoc
b)     Create and attach the GameStateManager
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using System.Collections.Generic;

public class GameStateManager : NetworkBehaviour
{
    //an enum, Player 1 = 1, Player 2 = 2.
    public static int playerTurn = -1;
    public static ArrayList connectedPlayers = new ArrayList();

    //call this every time a player connects
    public static void addPlayer(int networkId)
    {
        connectedPlayers.Add(networkId);
    }

    //call this every time a player disconnects
    public static void removePlayer(int networkId)
    {
        connectedPlayers.Remove(networkId);
    }

    public static int getPlayerTurn()
    {
        if (playerTurn == -1) playerTurn = (int)connectedPlayers[0];
        return playerTurn;
    }

    /*
     * Increment the player's turn
     */
    public static void nextTurn()
    {
        int currentIndex = connectedPlayers.IndexOf(playerTurn);
        if (currentIndex == connectedPlayers.Count - 1) playerTurn = (int)connectedPlayers[0];
        else playerTurn = (int)connectedPlayers[currentIndex + 1];
    }
}