Quantcast
Channel: Yudiz Solutions Ltd.
Viewing all articles
Browse latest Browse all 595

Developer’s Guide to Unity3D MMO With Node.JS Using Socket.IO

$
0
0

What is Node.js ?

Node.js is a JavaScript engine which is built on Chrome’s V8. It is network application for communicates between client and server.It uses event-driven non blocking I/O model. Node.js’s package ecosystem its called “npm”, which includes open source libraries in the world.Node is specially designed to broadly and scalable network applications for the device/computers.

What is Socket Programming ?

Sockets is the mechanism of communication between two computers or devices using TCP. A client program which is installed in device/computer which creates a socket at the end of the communication when socket connect to server. When connection is done, then server creates a its own socket object from the server side when end of the communication.

Game Architecture between Unity Engine and Server:

 

1

Goals :

  • Describe the mechanism of events works with client side and server side.
  • Unity 3d broadcasting a player’s X-Y-Z position, per frame, to other players grabbing the data via node.js.
  • Update all players position,rotation, player data etc.
  • Updating each time someone connects or disconnects with the socket.
  • Refreshing the gameobject after taken any action by the user/client.

Requirements :

  • Unity Engine
  • Any Dedicated Server

Required External Tools :

Conceptual Overview :

The object broadcasting the information of the other player like X,Y,Z coordinates of players.
Here i have demonstrated the basic game concept of moving one game object within screen, the same reaction will be affected with the other user’s screen also.
It can works for Android/iOS/Windows Mobile device, as well it can works fine with stand alone build for Windows and Mac PC also.
For the cross platform no need to do any extra stuff.

How Event Works ?

2

How to use :

Step – 1 : Import namespace

using System;
using BestHTTP;
using BestHTTP.SocketIO;

Step – 2 : Create Socket

Here we have to create socket for connecting socket of client with server.

var socketManagerRef = new SocketManager(new Uri(“http://chat.socket.io/socket.io/”));

The /socket.io/ path in the url is very important, by default the Socket.IO server will listen on this query. So don’t forget to append it to your test url too!

Example :

public void CreateSocketRef ()
    {
        TimeSpan miliSecForReconnect = TimeSpan.FromMilliseconds (1000);

        options = new SocketOptions ();
        options.ReconnectionAttempts = 3;
        options.AutoConnect = true;
        options.ReconnectionDelay = miliSecForReconnect;

        //Server URI
        socketManagerRef = new SocketManager (new Uri ("http://127.0.0.1:4001/socket.io/"), options);
    }

Step – 3 : Connecting To Namespace

By default the SocketManager will connect to the root(“/”) namespace while connecting to the server. You can access it through the SocketManager’s Socket property:

Socket root = manager.Socket;
Non-default namespaces can be accessed through the GetSocket(“/nspName”) function or through the manager’s indexer property:

Socket nsp = manager[“/customNamespace”];
// the same as this methode:
Socket nsp = manager.GetSocket(“/customNamespace”);

First access to a namespace will start the internal connection process.

Example :

public void SetNamespaceForSocket ()
    {
        namespaceForCurrentPlayer = socketNamespace;
        mySocket = socketManagerRef.GetSocket (“/Room-1);
    }

Step – 4 : Subscribing and receiving events

You can subscribe to predefined and custom events. Predefined events are “connect”, “connecting”, “event”, “disconnect”, “reconnect”, “reconnecting”, “reconnect_attempt”, “reconnect_failed”, “error”. Custom events are programmer defined events that your server will send to your client. You can subscribe to an event by calling a socket’s On function:

manager.Socket.On("login",OnLogin);
manager.Socket.On("new message", OnNewMessage);

An event handler will look like this:

void OnLogin(Socket socket, Packet packet, params object[] args) {
Debug.Log(“Login.”);
}

  • The socket parameter will be the namespace-socket object that the server sent this event.
  • The packet parameter contains the internal packet data of the event. The packet can be used to access binary data sent by the server, or to use a custom Json parser lib to decode the payload data. More on these later.
  • The args parameter is a variable length array that contains the decoded objects from the packet’s payload data. With the default Json encoder these parameters can be ‘primitive’ types(int, double, string) or list of objects(List<object>) or Dictionary<string, object> for objects.

A message emitted on the server(node.js):

// send a message to the client
socket.emit('message', ‘MyNick’, ‘Msg to the client’); can be caught by the client:

// subscribe to the "message" event                        manager.Socket.On("message", OnMessage);

// event handler

void OnMessage(Socket socket, Packet packet, params object[] args) {
// args[0] is the nick of the sender
// args[1] is the message
Debug.Log(string.Format("Message from {0}: {1}", args[0], args[1])); 
}

  • “connect”: Sent when the namespace opens.
  • “connecting”: Sent when the SocketManager start to connect to the socket.io server.
  • “event”:Sentoncustom(programmerdefined)events.
  • “disconnect”: Sent when the transport disconnects, SocketManager is closed, Socket is closed or when no Pong message received from the server in the given time specified in the handshake data.
  • “reconnect”: Sent when the plugin successfully reconnected to the socket.io server.
  • “reconnecting”: Sent when the plugin will try to reconnect to the socket.io server.
  • “reconnect_attempt”: Sent when the plugin will try to reconnect to the socket.io server.
  • “reconnect_failed”: Sent when a reconnect attempt fails to connect to the server and the ReconnectAttempt reaches the options’ ReconnectionAttempts’ value.
  • “error”: Sent on server or internal plugin errors. The event’s only argument will be a BestHTTP.SocketIO.Error object.

Example :

private void SetAllEvents ()    {     
//Connect 

mySocket.On ("connect", OnConnect); 

//Get UserAction Data From Server

mySocket.On ("action", OnGetActionData); 

//Leave Roommy

Socket.On ("leave", OnLeaveRoomData); //Disconnect mySocket.On ("disconnect", OnDisconnect);             }    

//=== On Event's Methods ===// 

//Connect To Room 
private void OnConnect (Socket socket, Packet packet, params object[] args) {
Debug.Log ("Connect...");  
}

//Get User Action Data 

private void OnGetActionData (Socket socket, Packet packet, params object[] args) { 
var res = JSON.Parse (packet.ToString ()); Debug.Log(res); //Here display response... } 

//Leave Room Data 

private void OnLeaveRoomData (Socket socket, Packet packet, params object[] args) { Debug.Log ("Leave Room"); }  

//Disconnect From Room  

private void OnDisconnect (Socket socket, Packet packet, params object[] args)  {Debug.Log ("Disconnect..."); }

Step – 5 : Sending Events

Here Emit function is for the sending event. We have to pass the event name for the primary parameter and others parameters are optionally. These all the data will encoded to json/ dictionary data and then send these data to server. Optionally all the server response set to the callback.

// Send a custom event to the server with two parameters

manager.Socket.Emit("message", "userName", "message");

// Send an event and define a callback function that will be called as an // acknowledgement of this event

manager.Socket.Emit("custom event", OnAckCallback, "param 1", "param 2");

void OnAckCallback(Socket socket, Packet originalPacket, params object[] args) { 
Debug.Log("OnAckCallback!"); 
}

Sending acknowledgement to the server

You can send back an acknowledgement to the server by calling the socket’s EmitAck function. You have to pass the original packet and any optional data:

manager["/customNamespace"].On("customEvent", (socket, packet, args) => {socket.EmitAck(packet, "Event", "Received", "Successfully"); });

You can keep a reference to the packet, and call the EmitAck from somewhere else.

Example :

//Send User Action Data
public void SendGameActionDataToServer (float objXPos, float objYPos, string senderDeviceUniqueId) {
Dictionary<string, object> gameActionData = new Dictionary<string, object> ();
gameActionData.Add ("objXPos", objXPos);
gameActionData.Add ("objYPos", objYPos);
gameActionData.Add ("senderDeviceUniqueId", senderDeviceUniqueId);
mySocket.Emit ("action", OnSendEmitDataToServerCallBack, gameActionData);
}

//Send Leave Room Data    
public void LeaveRoomFromServer (){
mySocket.Emit ("leave", OnSendEmitDataToServerCallBack);
}

Step – 6 : Sending Binary Data [Optional]

This is the most efficient way, because it will not convert the byte array to a Base64 encoded string on client side, and back to binary on server side.

byte[] data = new byte[10];manager.Socket.Emit("eventWithBinary", "textual param", data);

Step – 7 : Receiving Binary Data [Optional]

On client side these packets will be collected and will be merged into one packet. The binary data will be in the packet’s Attachments property.

Socket.On("frame", OnFrame);void OnFrame(Socket socket, Packet packet, params object[] args){texture.LoadImage(packet.Attachments[0]); }

Step – 8 : Make Game concept in Unity Engine

3

4

Step – 9 : Create and Implement Socket.IO script

Add MySocketManager script to empty gameobject.

5

MySocketManager.cs

using UnityEngine;
using System;
using BestHTTP;
using BestHTTP.SocketIO;
using SimpleJSON;
using System.Collections.Generic;

public class MySocketManager : MonoBehaviour
{
    #region PUBLIC_VARS

    //Instance
    public static MySocketManager instance;

    //Set Namespace For Current Player
    public string namespaceForCurrentPlayer;

    //SocketManager Reference
    public SocketManager socketManagerRef;

    //Socket Reference
    public Socket mySocket;

    //Options
    SocketOptions options;

    #endregion

    #region PRIVATE_VARS

    #endregion

    #region UNITY_CALLBACKS

    // Use this for initialization
    void Start ()
    {
        //Instance
        instance = this;

        //Create Socket Ref
        CreateSocketRef ();
    }

    //Leave Room After Quite The Game
    void OnApplicationQuit ()
    {
        LeaveRoomFromServer ();
        DisconnectMySocket ();
    }

    #endregion

    #region PUBLIC_METHODS

    public void CreateSocketRef ()
    {
        TimeSpan miliSecForReconnect = TimeSpan.FromMilliseconds (1000);

        options = new SocketOptions ();
        options.ReconnectionAttempts = 3;
        options.AutoConnect = true;
        options.ReconnectionDelay = miliSecForReconnect;

        //Server URI
        socketManagerRef = new SocketManager (new Uri ("http://127.0.0.1:4001/socket.io/"), options);

    }

    public void SetNamespaceForSocket (string socketNamespace)
    {
        namespaceForCurrentPlayer = socketNamespace;
        mySocket = socketManagerRef.GetSocket (namespaceForCurrentPlayer);

        //Set All Events, When Join The New Room
        SetAllEvents ();
    }

    //=== Emit Events ===//

    //Send User Action Data
    public void SendGameActionDataToServer (float objXPos, float objYPos, string senderDeviceUniqueId)
    {
        Dictionary<string, object> gameActionData = new Dictionary<string, object> ();
        gameActionData.Add ("objXPos", objXPos);
        gameActionData.Add ("objYPos", objYPos);
        gameActionData.Add ("senderDeviceUniqueId", senderDeviceUniqueId);

        mySocket.Emit ("action", OnSendEmitDataToServerCallBack, gameActionData);
    }

    //Send Leave Room Data
    public void LeaveRoomFromServer ()
    {
        mySocket.Emit ("leave", OnSendEmitDataToServerCallBack);
    }

    //Disconnect My Socket
    public void DisconnectMySocket ()
    {
        mySocket.Disconnect ();
    }

    #endregion

    #region PRIVATE_METHODS

    private void SetAllEvents ()
    {

        //Get UserAction Data From Server
        mySocket.On ("action", OnGetActionData);

        //Leave Room
        mySocket.On ("leave", OnLeaveRoomData);

        //Connect
        mySocket.On ("connect", OnConnect);

        //Re-Connect
        mySocket.On ("reconnect", OnReConnect);

        //Re-Connecting
        mySocket.On ("reconnecting", OnReConnecting);

        //Re-Connect Attempt
        mySocket.On ("reconnect_attempt", OnReConnectAttempt);

        //Re-Connect Attempt
        mySocket.On ("reconnect_failed", OnReConnectFailed);

        //Disconnect
        mySocket.On ("disconnect", OnDisconnect);

    }

    //=== On Event's Methods ===//

    private void OnConnect (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Connect...");
    }

    private void OnReConnect (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Re-Connect...");
    }

    private void OnReConnecting (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Re-Connecting...");
    }

    private void OnReConnectAttempt (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Re-Connect Attempt...");
    }

    private void OnReConnectFailed (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Re-ConnectFailed...");
    }

    //Get User Action Data
    private void OnGetActionData (Socket socket, Packet packet, params object[] args)
    {
        var res = JSON.Parse (packet.ToString ());
        GameManager.instance.RefreshGamePlayerUI (res [1]);
    }

    //Leave Room Data
    private void OnLeaveRoomData (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Leave Room");
    }

    //Disconnect From Room
    private void OnDisconnect (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Disconnect...");
    }

    //=== Emit Event's Methods ===//

    private void OnSendEmitDataToServerCallBack (Socket socket, Packet packet, params object[] args)
    {
        Debug.Log ("Send Packet Data : " + packet.ToString ());
    }

    #endregion
}

Step – 10 : Create and Implement GameManager script

6

MyDeviceUnique ID : Get device’s unique id for getting unique user for real MMO(Massively Multiplayer Online) functionality.
Main Menu Screen : Mainmenu UI Screen.
Game Play Screen : Gameplay Object Screen.
Game Play UI : Gam Play UI Screen.
Blue Obj : Reference object for display own movement.
Red Obj : Reference object for showing opponent(Red Obj)’s movement.
Movement Speed : Object movement speed.

GameManager.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using SimpleJSON;

public class GameManager : MonoBehaviour
{
    #region PUBLIC_VARS
    //Instance
    public static GameManager instance;

    //My Device Unique ID
    public string myDeviceUniqueID;

    //GameOver Screen
    public GameObject mainMenuScreen;
    public GameObject gamePlayScreen;
    public GameObject gamePlayUI;

    //Objects
    public Transform blueObj;
    public Transform redObj;

    //Movement Speed
    public float movementSpeed;
    #endregion

    #region PRIVATE_VARS
    //Store RedObj New Position
    private Vector2 newPosition;

    #endregion

    #region UNITY_CALLBACKS

    // Use this for initialization
    void Start ()
    {
        //Instance
        instance = this;

        //Set My Device Unique ID
        myDeviceUniqueID = SystemInfo.deviceUniqueIdentifier;

        //Init Thief New Position
        newPosition = new Vector2 (0, 0);
    }

    #endregion

    #region PUBLIC_METHODS

    //On Let's Play Btn Clicked
    public void OnLetsPlay ()
    {
        GetRoomData ();
    }

    //On PlayAgain Btn Clicked
    public void OnPlayAgain ()
    {
        Time.timeScale = 1;
        Application.LoadLevel (0);
    }

    //Show My Screen
    public void ShowMyScreen (GameObject myScreen)
    {
        myScreen.SetActive (true);
    }

    //Hide My Screen
    public void HideMyScreen (GameObject myScreen)
    {
        myScreen.SetActive (false);
    }

    //Move Object
    public void MoveObject (string ObjDir)
    {
        if (ObjDir == "Left") {
            blueObj.Translate (Vector3.left * movementSpeed * Time.deltaTime);
        } else if (ObjDir == "Right") {
            blueObj.Translate (Vector3.left * -movementSpeed * Time.deltaTime);
        } else if (ObjDir == "Up") {
            blueObj.Translate (Vector3.down * -movementSpeed * Time.deltaTime);
        } else if (ObjDir == "Down") {
            blueObj.Translate (Vector3.down * movementSpeed * Time.deltaTime);
        }

        //clamping
        blueObj.position = new Vector2 (Mathf.Clamp (blueObj.localPosition.x, -28, 28), Mathf.Clamp (blueObj.localPosition.y, -15, 15));

        //Send Object Position To Server
        MySocketManager.instance.SendGameActionDataToServer (blueObj.position.x, blueObj.position.y, myDeviceUniqueID);
    }

    //Refresh Game UI Data
    public void RefreshGamePlayerUI (JSONNode gameUIData)
    {
        newPosition.x = gameUIData ["objXPos"].AsFloat;
        newPosition.y = gameUIData ["objYPos"].AsFloat;

        if (gameUIData ["senderDeviceUniqueId"].Value != myDeviceUniqueID) {
            redObj.localPosition = newPosition;
        } 
    }

    #endregion

    #region PRIVATE_METHODS

    private void GetRoomData ()
    {
        //Get Room Data
        WWW getRoomData = new WWW ("http://127.0.0.1:4001/ws/getRoom");
        StartCoroutine (WaitForGetRoomDataResponse (getRoomData));
    }

    IEnumerator WaitForGetRoomDataResponse (WWW www)
    {
        yield return www;
        var res = JSON.Parse (www.text);

        if (res ["status"].Value == "true") {
            HideMyScreen (mainMenuScreen);
            ShowMyScreen (gamePlayScreen);
            ShowMyScreen (gamePlayUI);

            //=== Send Data To Server For Create Namespace ===//
            string namespaceForRoom = "/room-" + res ["data"] ["room_id"].Value;

            //Create Namespace For Current User And Send To Server
            MySocketManager.instance.SetNamespaceForSocket (namespaceForRoom);
        }
    }

    #endregion
}

Step – 11 : Build the Apk or standalone file and let’s play and enjoy the game.

Sudhir Kotila

Sudhir Kotila | Unity3D Game Developer

I'm a professional Game Developer at Yudiz Solutions Pvt. Ltd - a leading mobile game development company. I'm enjoying my work with Unity3D since last 3 years and playing with my code. I love to develop different types of addictive as well high quality games .I'm always ready to learn new gaming concepts for enhance my knowledge. I'm very passionate about my work and ready to take up challenging projects.

Viewing all articles
Browse latest Browse all 595

Trending Articles