What is Node.js?
Node.js is a JavaScript engine which is built on Chrome’s V8. It is network application for communicates with 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 are 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 connection to a server. When a connection is done, the server creates its own socket object from the server side when an end of the communication.
Game Architecture between Unity Engine and Server
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 from the socket.
- Refreshing the game object after taken any action by the user/client.
Requirements :
Unity Engine
Any Dedicated Server
Required External Tools :
Node.js – For connecting via server side.
Download Link : https://nodejs.org/en/
Tutorial Link for setup node server
Best HTTP Unity Package – For connecting via client side.
Download Link : https://assetstore.unity.com/packages/tools/network/best-http-2-155981
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 a screen, the same reaction will be affected by the other user’s screen also.
It can works on Android/iOS/Windows Mobile device, as well it can work fine with a standalone build for Windows and Mac PC also.
For the cross-platform no need to do any extra stuff.
How Event Works?
How to use :
First, you have to setup the server with Node.js.
Tutorial Link for setup node server
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;
options.ConnectWith = BestHTTP.SocketIO.Transports.TransportTypes.WebSocket;
//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
- “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 ://ConnectmySocket.On (“connect”, OnConnect);//Get UserAction Data From ServermySocket.On (“action”, OnGetActionData);//Leave RoomSocket.On (“leave”, OnLeaveRoomData); //Disconnect mySocket.On (“disconnect”, OnDisconnect); }//=== On Event’s Methods ===////Connect To Roomprivate void OnConnect (Socket socket, Packet packet, params object[] args) {Debug.Log (“Connect…”);}//Get User Action Dataprivate void OnGetActionData (Socket socket, Packet packet, params object[] args) {var res = JSON.Parse (packet.ToString ()); Debug.Log(res); //Here display response… }//Leave Room Dataprivate void OnLeaveRoomData (Socket socket, Packet packet, params object[] args) { Debug.Log (“Leave Room”); }//Disconnect From Roomprivate 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 parametersmanager.Socket.Emit(“message”, “userName”, “message”);// Send an event and define a callback function that will be called as an //acknowledgement of this eventmanager.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 serverYou 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 Datapublic 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 Datapublic 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
Step – 9 : Create and Implement Socket.IO script
Add MySocketManager script to empty gameobject.MySocketManager.csusing UnityEngine;using System;using BestHTTP;using BestHTTP.SocketIO;using SimpleJSON;using System.Collections.Generic;public class MySocketManager : MonoBehaviour{//Instancepublic static MySocketManager instance;//Set Namespace For Current Playerpublic string namespaceForCurrentPlayer;//SocketManager Referencepublic SocketManager socketManagerRef;//Socket Referencepublic Socket mySocket;//OptionsSocketOptions options;// Use this for initializationvoid Start (){//Instanceinstance = this;//Create Socket RefCreateSocketRef ();}//Leave Room After Quite The Gamevoid OnApplicationQuit (){LeaveRoomFromServer ();DisconnectMySocket ();}public void CreateSocketRef (){TimeSpan miliSecForReconnect = TimeSpan.FromMilliseconds (1000);options = new SocketOptions ();options.ReconnectionAttempts = 3;options.AutoConnect = true;options.ReconnectionDelay = miliSecForReconnect;options.ConnectWith = BestHTTP.SocketIO.Transports.TransportTypes.WebSocket;//Server URIsocketManagerRef = 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 RoomSetAllEvents ();}//=== Emit Events ===////Send User Action Datapublic 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 Datapublic void LeaveRoomFromServer (){mySocket.Emit (“leave”, OnSendEmitDataToServerCallBack);}//Disconnect My Socketpublic void DisconnectMySocket (){mySocket.Disconnect ();}private void SetAllEvents (){//Get UserAction Data From ServermySocket.On (“action”, OnGetActionData);//Leave RoommySocket.On (“leave”, OnLeaveRoomData);//ConnectmySocket.On (“connect”, OnConnect);//Re-ConnectmySocket.On (“reconnect”, OnReConnect);//Re-ConnectingmySocket.On (“reconnecting”, OnReConnecting);//Re-Connect AttemptmySocket.On (“reconnect_attempt”, OnReConnectAttempt);//Re-Connect AttemptmySocket.On (“reconnect_failed”, OnReConnectFailed);//DisconnectmySocket.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 Dataprivate void OnGetActionData (Socket socket, Packet packet, params object[] args){var res = JSON.Parse (packet.ToString ());GameManager.instance.RefreshGamePlayerUI (res [1]);}//Leave Room Dataprivate void OnLeaveRoomData (Socket socket, Packet packet, params object[] args){Debug.Log (“Leave Room”);}//Disconnect From Roomprivate 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 ());}}
Step – 10 : Create and ImplementGameManager script
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.csusing UnityEngine;using System.Collections;using System.Collections.Generic;using SimpleJSON;public class GameManager : MonoBehaviour{//Instancepublic static GameManager instance;//My Device Unique IDpublic string myDeviceUniqueID;//GameOver Screenpublic GameObject mainMenuScreen;public GameObject gamePlayScreen;public GameObject gamePlayUI;//Objectspublic Transform blueObj;public Transform redObj;//Movement Speedpublic float movementSpeed;//Store RedObj New Positionprivate Vector2 newPosition;// Use this for initializationvoid Start (){//Instanceinstance = this;//Set My Device Unique IDmyDeviceUniqueID = SystemInfo.deviceUniqueIdentifier;//Init Thief New PositionnewPosition = new Vector2 (0, 0);}//On Let’s Play Btn Clickedpublic void OnLetsPlay (){GetRoomData ();}//On PlayAgain Btn Clickedpublic void OnPlayAgain (){Time.timeScale = 1;Application.LoadLevel (0);}//Show My Screenpublic void ShowMyScreen (GameObject myScreen){myScreen.SetActive (true);}//Hide My Screenpublic void HideMyScreen (GameObject myScreen){myScreen.SetActive (false);}//Move Objectpublic 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);}//clampingblueObj.position = new Vector2 (Mathf.Clamp (blueObj.localPosition.x, -28, 28), Mathf.Clamp (blueObj.localPosition.y, -15, 15));//Send Object Position To ServerMySocketManager.instance.SendGameActionDataToServer (blueObj.position.x, blueObj.position.y, myDeviceUniqueID);}//Refresh Game UI Datapublic void RefreshGamePlayerUI (JSONNode gameUIData){newPosition.x = gameUIData [“objXPos”].AsFloat;newPosition.y = gameUIData [“objYPos”].AsFloat;if (gameUIData [“senderDeviceUniqueId”].Value != myDeviceUniqueID) {redObj.localPosition = newPosition;}}private void GetRoomData (){//Get Room DataWWW 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 (gamePlayS creen);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 ServerMySocketManager.instance.SetNamespaceForSocket (namespaceForRoom);}}}
Step – 11 : Build the Apk or standalone file and let’s play and enjoy the game.
Leave a Reply