2013-02-26

Basics of creating a multiplayer web based game with jQuery and SignalR

In this article, I’m going to show how to use jQuery to move an object on web page and how we can use SignalR to notify all the connected clients about the movement and replicate that movement on each client.

The basic rule of a multiplayer web based game is, you have to reflect the changes done in a one player machine on other players machines, real-time. The challenge is how you are going to do this in real time. If you are using some polling mechanism, then some movements will get missed or slower to replicate in other players machine. Use with SignalR, multiplayer web based games are much easier to develop.

Let’s have a look at how to accomplish the basic rules of a multiplayer game, i.e. reflecting the changes real time in other clients, in an ASP.NET application with the help of SignalR.

Before we begin, following is a screen shot of the sample application. As you can see, I’m running the same application on two different browsers and I’m expecting the same movement of Scooby on both web pages without browser refresh.

Untitled

First of all, we have to add the reference to SignalR client and server libraries. Easiest way to do this is by NuGet packages. Right click on your project and click on Manage NuGet Packages…. Then type SignalR on the search box and click Install. Now you have SignalR in your application. I’m going to use some animation in my game, so let’s also install the jQuery UI from NuGet Packages.

Now we are ready to develop our application.

First, I’m going to add the Hub routing in my Global.asax file as shown below

protected void Application_Start(object sender, EventArgs e)
{
    RouteTable.Routes.MapHubs();
}

Then, I’m going to add the page where I’m going to develop the game. In there, I’m referencing to the jQuery latest version, jQuery UI library, SignalR client library. SignalR Hubs which will dynamically be created on the fly.

<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js" charset="utf-8">
</script>
<script src="Scripts/jquery-ui-1.10.0.js"></script>
<script src="Scripts/jquery.signalR-1.0.0.js"></script>
<script src="/signalr/hubs"></script>

Next I’m going to develop my game using the jQuery/JavaScripts. Following is the code for that

<script type="text/javascript">
       $(function () {
           var moveScooby = $.connection.moveScoobyHub;

           moveScooby.client.runScoobyForward = function () {
               $("#scooby").animate({
                   marginLeft: '+=1'
               }, 0);
           }

           moveScooby.client.runScoobyBackward = function () {
               $("#scooby").animate({
                   marginLeft: '-=1'
               }, 0);
           }

           moveScooby.client.runScoobyUpward = function () {
               $("#scooby").animate({
                   marginTop: '-=30'
               }, 500, 'easeOutCubic', function () {
                   $("#scooby").animate({
                       marginTop: '+=30'
                   }, 500, 'easeInExpo');
               });
           }

           $.connection.hub.start().done(function () {
               $(document).keydown(function (e) {
                   var audio = document.getElementById("snd");
                   switch (e.which) {
                       case 32:
                           $("#scooby").animate({
                               marginTop: '-=30'
                           }, 500, 'easeOutCubic', function () {
                               $("#scooby").animate({
                                   marginTop: '+=30'
                               }, 500, 'easeInExpo');
                           });
                           moveScooby.server.moveScoobyUpward();
                           break;
                       case 37:
                           $("#scooby").animate({
                               marginLeft: '-=1'
                           }, 0);
                           moveScooby.server.moveScoobyBackward();
                           break;
                       case 39:
                           $("#scooby").animate({
                               marginLeft: '+=1'
                           }, 0);
                           moveScooby.server.moveScoobyForward();
                           break;
                   }
               });
           }
           );
       });
   </script>

Here, I will be using a Scooby-doo image and I will move it on the web page as we press arrow keys. Scooby will jump when you press the space bar.

My hub class name is MoveScoobyHub.cs. In SignalR, we must use lower case camel notation in order to access the server properties/methods. So I access my hub like below

var moveScooby = $.connection.moveScoobyHub;

Note the “m” in “moveScoobyHub” is simple even though the class name has capital “M”.

Next I’ve declared 3 methods to move the Scooby on all the client machines.

moveScooby.client.runScoobyForward = function () {
    $("#scooby").animate({
        marginLeft: '+=1'
    }, 0);
}

moveScooby.client.runScoobyBackward = function () {
    $("#scooby").animate({
        marginLeft: '-=1'
    }, 0);
}

moveScooby.client.runScoobyUpward = function () {
    $("#scooby").animate({
        marginTop: '-=30'
    }, 500, 'easeOutCubic', function () {
        $("#scooby").animate({
            marginTop: '+=30'
        }, 500, 'easeInExpo');
    });
}

First method will take care of moving the Scooby forward. It will add 1 unit to the margin left of the scooby object. I’m using jQuery animate() method to add the motion to the object. To make the movement to a normal speed, I’m setting the duration of the animation to 0. You can make the movement slow by increasing the duration. Same as in runScoobyForward function, I’ve written the runScoobyBackward function to move the Scooby backward. There I’m deducting a one unit from the left margin so the object will move backward.

Third method is for make the Scooby jump. I’m setting the top margin of the scooby object to –30px so the object will move upwards. To make the jumping looks real, I’m adding easeOutCubic to the animation. Once the animation is done, I’m calling the animate() method once again to move the Scooby to the ground (He can’t jump and stay forever in the air!). To do that, I’m adding 30px for the scooby object and I’m using easeInExpo for the animation so it looks more realistic with the gravity effect. So now the Scooby will back in the ground (-30px + 30px = 0)

Now those are the three methods I’m using to to do the movement of Scooby.

$.connection.hub.start().done(function () {
    $(document).keydown(function (e) {
        switch (e.which) {
            case 32:
                moveScooby.server.moveScoobyUpward();
                break;
            case 37:
                moveScooby.server.moveScoobyBackward();
                break;
            case 39:
                moveScooby.server.moveScoobyForward();
                break;
        }
    });
}
);

Once the connection to hub starts, I’m adding a function to keydown event of the web page. In that function I’m getting the keycode of the key pressed. If it’s the space bar (keycode=32), I’m calling the moveScoobyUpward method in the server. It looks like below

public void moveScoobyUpward()
{
    Clients.All.runScoobyUpward();
}

As you can see, it will in return call the runScoobyUpward() function on all the clients.

If it’s the left arrow (keycode=37), I’m calling the moveScoobyBackward method in the server. It looks like below

public void moveScoobyBackward()
{
    Clients.All.runScoobyBackward();
}

As you can see, it will in return call the runScoobyBackward() function on all the clients.

If it’s the right arrow (keycode=39), I’m calling the moveScoobyForward method in the server. It looks like below

public void moveScoobyForward()
{
    Clients.All.runScoobyForward();
}

As you can see, it will in return call the runScoobyForward() function on all the clients.

So that’s it! As for my markup, I’m simply using an image inside a div. I’ve set a background image for the div.

<body>
    <div style="background-image: url('Images/scooby01.jpg'); background-repeat: no-repeat; height: 438px; width: 584px">
        <img src="Images/aniscooby.gif" id="scooby" width="100" height="100" style="margin-top: 340px;" />
    </div>
</body>

Altogether, following is the complete aspx page markup. Of course there are no code behind code for that. You can surely copy paste this to a simple HTML page and it will work.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="UI.aspx.cs" Inherits="SignalRUIChange.UI" %>

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript"
        src="
http://code.jquery.com/jquery-latest.js" charset="utf-8">
    </script>
    <script src="Scripts/jquery-ui-1.10.0.js"></script>
    <script src="Scripts/jquery.signalR-1.0.0.js"></script>
    <script src="/signalr/hubs"></script>
    <script type="text/javascript">
        $(function () {
            var moveScooby = $.connection.moveScoobyHub;

            moveScooby.client.runScoobyForward = function () {
                $("#scooby").animate({
                    marginLeft: '+=1'
                }, 0);
            }

            moveScooby.client.runScoobyBackward = function () {
                $("#scooby").animate({
                    marginLeft: '-=1'
                }, 0);
            }

            moveScooby.client.runScoobyUpward = function () {
                $("#scooby").animate({
                    marginTop: '-=30'
                }, 500, 'easeOutCubic', function () {
                    $("#scooby").animate({
                        marginTop: '+=30'
                    }, 500, 'easeInExpo');
                });
            }

            $.connection.hub.start().done(function () {
                $(document).keydown(function (e) {
                    switch (e.which) {
                        case 32:
                            moveScooby.server.moveScoobyUpward();
                            break;
                        case 37:
                            moveScooby.server.moveScoobyBackward();
                            break;
                        case 39:
                            moveScooby.server.moveScoobyForward();
                            break;
                    }
                });
            }
            );
        });
    </script>
</head>
<body>
    <div style="background-image: url('Images/scooby01.jpg'); background-repeat: no-repeat; height: 438px; width: 584px">
        <img src="Images/aniscooby.gif" id="scooby" width="100" height="100" style="margin-top: 340px;" />
    </div>
</body>
</html>

And following is the server side code for the Hub. Note that I’m inheriting it from the SignalR Hub class in order to make it a hub.

using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SignalRUIChange
{
    public class MoveScoobyHub: Hub
    {
        public void moveScoobyForward()
        {
            Clients.All.runScoobyForward();
        }

        public void moveScoobyBackward()
        {
            Clients.All.runScoobyBackward();
        }

        public void moveScoobyUpward()
        {
            Clients.All.runScoobyUpward();
        }
    }
}

2013-02-18

Logging with ASP.NET - Part 1 (Configuring log4net)

Here I've shown how to do basic logging with an ASP.NET application. I've used log4net logging provider for that. I'm using Visual Studio 2012 and C#.NET to do the demonstration.

Here is the first part of a video tutorial series I’m hoping to do in log4net. If the text isn’t clear in the demo, please use the HD format and full screen mode.

2013-02-11

Creating a chat application in ASP.NET with SignalR

There are many things we can do with SignalR. It’s never been this easier to build real time web applications. In this post, I’m going to show how to build a chat application and add some additional functionality to it.

First of all, we have to get the SignalR library and include it in our ASP.NET web application. Easiest way to do this is by going to NuGet package manager and install it from there. You can go to the NuGet package manager in Project – > Manage NuGet Packages…

After you install it, you can see there are new script files in the Script folder as well as some new references. Scripts are for the SignalR client library and references are there for SignalR server library.

Now we are ready to develop our application with SignalR.

Let’s start by building the web page. Following is the body section of my HTML page. Note that I’m using a simple HTML page as my chat page.

<body style="border: solid gray; height: 90%;">
    <input type="hidden" id="displayname" />
    <div style="height: 80%;">
        <div id="chats" style="width: 80%; float: left;"></div>
        <div id="onlineList" style="width: 19%; float: right; border-left: solid red 2px; height: 100%;">
            <div style="font-size: 20px; border-bottom: double">Online Users</div>
        </div>
    </div>
    <div style="height: 19%; border-top: double black; background-color: lightgray">
        <div style="float: left; height: 90%; top: 10%; position: relative;">
            <textarea spellcheck="true" id="message" style="width: 625px; height: 80%"></textarea>
        </div>
        <div style="position: relative; top: 30%; float: left;">
            <input type="button" id="sendmessage" value="Send" />
        </div>
        <div style="position: relative; top: 30%; float: left;">
            <select id="users">
                <option value="All">All</option>
            </select>
        </div>
    </div>
</body>

I’m using a hidden field to store the username. A div called ‘chats’ to display the chat messages. A div called ‘onlineList’ to display the online users. At the bottom, I’ve placed the text area along with the send button. Also, I’ve included a dropdown list to display the online users so the user can select to whom (s)he is going to send the message or (s)he can simply select ‘All’ option to send the message to all users.

So that’s the layout of my webpage. Now let’s look at the head section of it.

<head>
    <style>
        .border {
            border-bottom: solid gray 1px;
        }

        .smileys {
            height: 15px;
        }
    </style>

    <title>Chat demo in ASP.NET using SignalR</title>

    <script src="Scripts/jquery-1.6.4.min.js"></script>

    <script src="Scripts/jquery.signalR-1.0.0-rc2.js"></script>

    <!--Reference the autogenerated SignalR hub script. -->
    <script src="/signalr/hubs"></script>

    <script type="text/javascript">
        $(function () {

            var chat = $.connection.chatHub;

            // Get the user name.
            $('#displayname').val(prompt('Enter your name:', ''));

            chat.client.differentName = function (name) {
                // Prompts for different user name
                $('#displayname').val(prompt('Please enter different username:', ''));
                chat.server.notify($('#displayname').val(), $.connection.hub.id);
            };

            chat.client.online = function (name) {
                // Update list of users
                if (name == $('#displayname').val())
                    $('#onlineList').append('<div class="border" style="color:red">You: ' + name + '</div>');
                else {
                    $('#onlineList').append('<div class="border">' + name + '</div>');
                    $("#users").append('<option value="' + name + '">' + name + '</option>');
                }
            };

            chat.client.enters = function (name) {
                $('#chats').append('<div class="border"><i>' + name + ' enters chatroom</i></div>');
                $("#users").append('<option value="' + name + '">' + name + '</option>');
                $('#onlineList').append('<div class="border">' + name + '</div>');
            };

            // Create a function that the hub can call to broadcast chat messages.
            chat.client.broadcastMessage = function (name, message) {
                //Interpret smileys
                message = message.replace(":)", "<img src=\"/emoticons/smile.png\" class=\"smileys\" />");
                message = message.replace(";)", "<img src=\"/emoticons/wink.png\" class=\"smileys\" />");
                message = message.replace(":D", "<img src=\"/emoticons/laugh.png\" class=\"smileys\" />");

                //display the message
                $('#chats').append('<div class="border"><span style="color:blue">' + name + '</span>: ' + message + '</div>');
            };

            chat.client.disconnected = function (name) {
                //Calls when someone leaves the page
                $('#chats').append('<div class="border"><i>' + name + ' leaves chatroom</i></div>');
                $('#onlineList div').remove(":contains('" + name + "')");
                $("#users option").remove(":contains('" + name + "')");
            }

            // Start the connection.
            $.connection.hub.start().done(function () {
                //Calls the notify method of the server
                chat.server.notify($('#displayname').val(), $.connection.hub.id);

                $('#sendmessage').click(function () {
                    if ($("#users").val() == "All") {
                        // Call the Send method on the hub.
                        chat.server.send($('#displayname').val(), $('#message').val());
                    }
                    else {
                        chat.server.sendToSpecific($('#displayname').val(), $('#message').val(), $("#users").val());
                    }
                    // Clear text box and reset focus for next comment.
                    $('#message').val('').focus();
                });

            });
        });
    </script>
</head>

At the head section of the HTML file, I’ve added styles, references to the jQuery library, SignalR library and auto generated SignalR hub script. Next I’ve included the client side functions which are responsible for displaying chats, notify when a user enters or leaves the chat room, sending the chats and maintaining up to date online users list.

Following is the whole code behind responsible for handling the client side events. I have written it in a CS files called ChatHub.cs.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace SingalRTest
{
    public class ChatHub : Hub
    {
        static ConcurrentDictionary<string, string> dic = new ConcurrentDictionary<string, string>();

        public void Send(string name, string message)
        {
            // Call the broadcastMessage method to update clients.
            Clients.All.broadcastMessage(name, message);
        }

        public void sendToSpecific(string name, string message, string to)
        {
            // Call the broadcastMessage method to update clients.
            Clients.Caller.broadcastMessage(name, message);
            Clients.Client(dic[to]).broadcastMessage(name, message);
        }

        public void Notify(string name, string id)
        {
            if (dic.ContainsKey(name))
            {
                Clients.Caller.differentName();
            }
            else
            {
                dic.TryAdd(name, id);

                foreach (KeyValuePair<String, String> entry in dic)
                {
                    Clients.Caller.online(entry.Key);
                }

                Clients.Others.enters(name);
            }
        }

        public override Task OnDisconnected()
        {
            var name = dic.FirstOrDefault(x => x.Value == Context.ConnectionId.ToString());
            string s;
            dic.TryRemove(name.Key, out s);
            return Clients.All.disconnected(name.Key);
        }
    }
}

Now let’s have a look at both the client side events and server side events. First, I’m referencing to my server hub using the below code in client side

var chat = $.connection.chatHub;

Note that I’m using the same name as the class name to refer to the hub. My class, ChatHub is inherited from the SignalR Hub class.

Then I’m prompting the user for a username and I’m storing the username in the ‘displayname’ hidden field. That’s what the below code do

            $('#displayname').val(prompt('Enter your name:', ''));

In code behind, I’m using a concurrent static Dictionary to maintain the online users lists.

static ConcurrentDictionary<string, string> dic = new ConcurrentDictionary<string, string>();

Whenever a user enters the chat room, I’m adding the username as the key and connection ID as the value to that dictionary. That’s what the below server side method do

        public void Notify(string name, string id)
        {
            if (dic.ContainsKey(name))
            {
                Clients.Caller.differentName();
            }
            else
            {
                dic.TryAdd(name, id);

                foreach (KeyValuePair<String, String> entry in dic)
                {
                    Clients.Caller.online(entry.Key);
                }

                Clients.Others.enters(name);
            }
        }

As you can see, I’m passing two parameters, name and id.

As soon as the connection made with the server, I’m calling that method.

$.connection.hub.start().done(function () {
    //Calls the notify method of the server
    chat.server.notify($('#displayname').val(), $.connection.hub.id);
.............................................……………………………………..

As you can see from the above code, I’m passing the username I’ve stored in the hidden field and I’m passing the unique ID which was generated when the connection has been made with the server.

If the Dictionary already contains the username entered, I’m calling the method diiferentName for the Caller. Note that I’m calling that method ONLY to the calling client. That’s why I’ve used Client.Caller. So now, it will call the differentName method in the client side for the Caller Client. Following is the differentName method in my client side script.

chat.client.differentName = function (name) {
    // Prompts for different user name
    $('#displayname').val(prompt('Please enter different username:', ''));
    chat.server.notify($('#displayname').val(), $.connection.hub.id);
};

As you can see, I’m prompting again for a different username. Then I’m again calling the Notify method in the server by passing the new username and connection ID.

If there is no user by that name, then it will add the username to the Dictionary.

dic.TryAdd(name, id);

Then I have to display the online list to the just entered client. For that, I’m calling the online method for the Caller Client. Note that, online client function will only get called for the calling client, not for all the clients.

foreach (KeyValuePair<String, String> entry in dic)
{
    Clients.Caller.online(entry.Key);
}

For each user in the list, I’m calling the online function. Below is that function

chat.client.online = function (name) {
    // Update list of users
    if (name == $('#displayname').val())
        $('#onlineList').append('<div class="border" style="color:red">You: ' + name + '</div>');
    else {
        $('#onlineList').append('<div class="border">' + name + '</div>');
        $("#users").append('<option value="' + name + '">' + name + '</option>');
    }
};

As you can see, if the name equals to the caller client, I’m adding a You: in front of the name and it will get displayed in red color. Otherwise I’m just appending the usernames to the onlineList div and adding an option to the dropdown list.

For the rest of the clients, I’m notifying that a new user enters the chat room. For that, I’m using the below code

Clients.Others.enters(name);

Note that I’m using Clients.Others, which will display the message to all the connected clients except the caller (There is no point of notifying the caller that (s)he just entered to the chat room). So I am calling the enters method in the client side and I’m passing the username so I can notify others who has just entered to the chat room. Following is the enters function.

chat.client.enters = function (name) {
    $('#chats').append('<div class="border"><i>' + name + ' enters chatroom</i></div>');
    $("#users").append('<option value="' + name + '">' + name + '</option>');
    $('#onlineList').append('<div class="border">' + name + '</div>');
};

As you can see, I’m showing username enters chatroom in the chats div. For rest of the clients, I have to update the online users list too. So I’m going to do that in the enters method by appending the new user name to the onlineList div and adding a new option to the users dropdown list.

Now that’s what happened when a client made a connection with the server, in this example, when the user joins the chat room. Now let’s see how to send the chat messages to users. For that, I’ve bound the below client side function to the Send button.

$('#sendmessage').click(function () {
    if ($("#users").val() == "All") {
        // Call the Send method on the hub.
        chat.server.send($('#displayname').val(), $('#message').val());
    }
    else {
        chat.server.sendToSpecific($('#displayname').val(), $('#message').val(), $("#users").val());
    }
    // Clear text box and reset focus for next comment.
    $('#message').val('').focus();
});

In the dropdown list, if the user has selected “All” option, then I’m calling the Send method in the server. I’m passing the username along with the text in the message textarea. Following is the send method in the server

public void Send(string name, string message)
{
    // Call the broadcastMessage method to update clients.
    Clients.All.broadcastMessage(name, message);
}

It will accept the two parameters and it will call the broadcastMessage function for all the clients. Since the user has selected “All” option in the dropdown list, now it will send the message to all the connected clients. The broadcastMessage function will get called for all the clients.

chat.client.broadcastMessage = function (name, message) {
    //Interpret smileys
    message = message.replace(":)", "<img src=\"/emoticons/smile.png\" class=\"smileys\" />");
    message = message.replace(";)", "<img src=\"/emoticons/wink.png\" class=\"smileys\" />");
    message = message.replace(":D", "<img src=\"/emoticons/laugh.png\" class=\"smileys\" />");

    //display the message
    $('#chats').append('<div class="border"><span style="color:blue">' + name + '</span>: ' + message + '</div>');
};

As you can see, it will accept the two parameters which were passed from the server. First it will check for the smiley syntaxes in the message and replace those with emoticons. Smile

After building the final message with the images, I’m appending it to the chats div along with the username. Now all the clients will get the message with the username!

Now let’s move on to the part where a user can send messages only to specific user (not all the clients). For that I’m calling the sendToSpecific server side method. As you can see from the Send button event, if the value selected in the dropdown list is not “All”, I’m calling the sendToSpecific method by passing the caller name, message and the callee name (The selected value in the dropdown list). My sendToSpecific method looks like below

public void sendToSpecific(string name, string message, string to)
{
    // Call the broadcastMessage method to update clients.
    Clients.Caller.broadcastMessage(name, message);
    Clients.Client(dic[to]).broadcastMessage(name, message);
}

Since now I need to display the message only between two users, I’m calling the Clients.Caller.broadcastMessage and Clients.Client(dic[to]).broadcastMessage. By calling the Clients.Caller.broadcastMessage, I’m displaying the message to the caller. By calling the below line, I’m displaying the message ONLY to the particular client. That’s why I’m passing the connection ID of that client to the Client method.

Clients.Client(dic[to]).broadcastMessage(name, message);

I’m passing the callee name to the Dictionary so it will return the connection ID associated for that username. Now it will broadcast the message only to that specific client.

So that’s how we can broadcast messages between two specific clients. Now let’s move to the final part, what will happen when a user leaves the chat room? Pretty simple, I will need to remove that username from the online user lists and from the dropdown list in client side and I will need to remove the particular dictionary entry associated for that user from the server side. Let’s see how to do that.

There is a server side method in the SignalR library, called OnDisconnected(), which will get called whenever the connection get closed from a client. So we can handily use that for our purpose here.

public override Task OnDisconnected()
{
    var name = dic.FirstOrDefault(x => x.Value == Context.ConnectionId.ToString());
    string s;
    dic.TryRemove(name.Key, out s);
    return Clients.All.disconnected(name.Key);
}

As you can see, first I’m removing the entry in the dictionary associated with that username. Then I’m calling the disconnected client side function for all the clients.

chat.client.disconnected = function (name) {
    //Calls when someone leaves the page
    $('#chats').append('<div class="border"><i>' + name + ' leaves chatroom</i></div>');
    $('#onlineList div').remove(":contains('" + name + "')");
    $("#users option").remove(":contains('" + name + "')");
}

From the disconnected function, I’m simply notifying all the clients that particular user leaves the chat and then remove his/her name from both the online list and dropdown list.

Well, that’s how we can simply create a chat application in ASP.NET with SignalR. SmileFollowing are some screenshots of running application

Untitled

leaves