Quantcast
Channel: Start
Viewing all articles
Browse latest Browse all 15

Building a real time online multiplayer Windows Store game with SignalR v1.1.3 : part 1

$
0
0

Last week I tried using SignalR on Windows Store apps and it worked as it should. Now I’ll try doing that again but this time with a game with more. I know this is now ideal for advanced games, but let’s try it out for SIMPLE 2 player games Smile

Note: I am not sure if this is the correct way, but as of now I am contented that it is working both on C#/XAML and HTML5/JavaScript, in-sync

Create the server: SignalR

1. Create a new Project –> Visual C# –> Web

2. Select the ASP.NET Empty Web Application, name if “GameServer”

3. Add the SignalR to the Project. Under tools –> Library Package manager –> Package manager Console

4. Type and run the Command:

Install-Package Microsoft.AspNet.SignalR -Version 1.1.3

5. Right on the Project. Add –> SignalR Hub Class. Name it “GameHub”

6. Right on the Project –> Add –> New Item –> “Global Application Class”

7. Add  then following code it Application_Start()

// Register the default hubs route: ~/signalr/hubs
RouteTable.Routes.MapHubs();

8. Let’s add the needed class on the GameHub.cs, just to speed up things. Just above the Class definition.

#region Classes
    public class Player
    {
        public string ConnectionID { get; set; }
        public string ClientLanguage { get; set; }
        public Guid? RoomID { get; set; }
    }
    public class Room
    {
        public Guid ID { get; set; }
        public List Players { get; set; }
        public int NumberToGuess { get; set; }
        public bool Playing { get; set; }
        public int Turn { get; set; }
    }
    #endregion

*I’ve added properties that I might use later, don’t think to much why is this here.

9. Now let’s add the static variables that we can use to persist data.

#region Static
private int MAX_OPTION = 9;
private static List Rooms = new List();
private static List Players = new List();
#endregion

10. Let’s override the OnConnected method to see that we really are connecting, add a breakpoint anywhere:

public override System.Threading.Tasks.Task OnConnected()
{
    string id = Context.ConnectionId;
    return base.OnConnected();
}

11. Add this Connected method that we’ll call whenever we’re connected. I like this style instead of relying on the OnConnected, that sometimes slow to fire:

 

public void Connected(string lang)
{
   Players.Add(new Player() { ClientLanguage = lang, ConnectionID = Context.ConnectionId });
   Clients.Caller.connectedResult(Context.ConnectionId, Players.Count, JsonConvert.SerializeObject(Rooms));
   Clients.Others.otherConnected(Context.ConnectionId, Players.Count);
}

Your Game.csharp project should be structured like this, with the GameHub.cs

image

*Let’s stop here for now, and let’s create the Client (Windows Store app, C#/XAML), yeah I know you saw the other project (Game.javascript) :3 We’ll get to that on part 2(sleepy head here)

Create the Windows Store app Game ( C# )
1. Create a new Project –> Visual C# –> Windows Store
2. Select Grid App, name it “Game.csharp”
3. Add the SignalR to the Project. Under tools –> Library Package manager –> Package manager Console
4. Type and run the Command:

Install-Package Microsoft.AspNet.SignalR.Client -Version 1.1.3

5. First thing, remove all pages that came with it( GroupedItems, GroupDetail, itemDetail.

6. Add a new folder, name it ViewModels..

7. Before we add the pages, let’s put the HubConnection and other necessary variables on App.xaml.cs, open that file and add these lines before the constructor:

public IHubProxy gameHub { set; get; }
public string SERVER_PROXY = "http://localhost:47354/";
public SynchronizationContext Context { get; set; }
public HubConnection hubConnection { get; set; }

8. Add this after the suspending part:

((App)(App.Current)).hubConnection = new HubConnection(SERVER_PROXY);

9. Change the first page by replacing GroupedItemsPage to RoomsPage, we’ll create that next.

10. Add a new Class to ViewModels folder, name it RoomsPageViewModel, and paste this code:

using Game.csharp.DataModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Game.csharp.ViewModels
{
    public class RoomsPageViewModel: INotifyPropertyChanged
    {
        #region Fields
        private ObservableCollection iItems;

        public ObservableCollection IItems
        {
            get { return iItems; }
            set
            {
                iItems = value;
                NotifyPropertyChanged("IItems");
            }
        }
        public RoomsPageViewModel()
        {
            IItems = new ObservableCollection();
        }
        #endregion
        #region NotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion
    }
}

10. Add a new Basic Page, name it RoomsPage.

11. Open RoomsPage.xaml.cs, and copy paste this code:

using Game.csharp.DataModel;
using Game.csharp.ViewModels;
using Microsoft.AspNet.SignalR.Client.Hubs;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237

namespace Game.csharp
{

    public sealed partial class RoomsPage : Game.csharp.Common.LayoutAwarePage
    {
        RoomsPageViewModel viewModel;
        public RoomsPage()
        {
            this.InitializeComponent();
            viewModel = new RoomsPageViewModel();
            DataContext = viewModel;
            ConnectToGame();
        }
        async private void ConnectToGame()
        {
            try
            {
                ((App)(App.Current)).gameHub = ((App)(App.Current)).hubConnection.CreateHubProxy("GameHub");
                ((App)(App.Current)).Context = SynchronizationContext.Current;

                ((App)(App.Current)).gameHub.On("createRoomResultSuccess",
                    (_newRoom) =>
                         ((App)(App.Current)).Context.Post(delegate
                        {
                            Room newRom = JsonConvert.DeserializeObject(_newRoom);
                            viewModel.IItems.Add(newRom);
                        }, null)
                        );
                ((App)(App.Current)).gameHub.On("createRoomResultFailed",
                   (_result) =>
                        ((App)(App.Current)).Context.Post(delegate
                       {
                           MessageDialog md = new MessageDialog("Failed to create Room");
                           md.ShowAsync();
                       }, null)
                       );
                ((App)(App.Current)).gameHub.On<string, int,="" string="">("connectedResult",
                    (_id, _roomCount, _rooms) =>
                         ((App)(App.Current)).Context.Post(delegate
                        {
                            viewModel.IItems = JsonConvert.DeserializeObject<observablecollection>(_rooms);

                        }, null)
                        );
                ((App)(App.Current)).gameHub.On("otherNewRoomCreated",
                   (_newRoom) =>
                        ((App)(App.Current)).Context.Post(delegate
                       {
                           Room newRom = JsonConvert.DeserializeObject(_newRoom);
                           viewModel.IItems.Add(newRom);
                       }, null)
                       );
                ((App)(App.Current)).gameHub.On<int, string="">("otherDisconnected",
                   (_playerCount, _roomsLeft) =>
                        ((App)(App.Current)).Context.Post(delegate
                       {
                           viewModel.IItems = JsonConvert.DeserializeObject<observablecollection>(_roomsLeft);

                       }, null)
                       );
                ((App)(App.Current)).gameHub.On<string, string="">("otherConnected",
                   (_id, _count) =>
                        ((App)(App.Current)).Context.Post(delegate
                       {

                       }, null)
                       );
                await ((App)(App.Current)).hubConnection.Start();
                await ((App)(App.Current)).gameHub.Invoke("Connected", "C#");
            }
            catch (Exception ex)
            {
            }
        }
        private void CreateRoom_click(object sender, RoutedEventArgs e)
        {
            ((App)(App.Current)).gameHub.Invoke("CreateRoom", "C#");
        }
    }
}

12. Now for RoomsPage.xaml:

<common:LayoutAwarePage
    x:Name="pageRoot"
    x:Class="Game.csharp.GamePage"
    DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Game.csharp"
    xmlns:common="using:Game.csharp.Common"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>

        <!-- TODO: Delete this line if the key AppName is declared in App.xaml -->
        <x:String x:Key="AppName">Game page</x:String>
    </Page.Resources>

    <!--
        This grid acts as a root panel for the page that defines two rows:
        * Row 0 contains the back button and page title
        * Row 1 contains the rest of the page layout
    -->
    <Grid Style="{StaticResource LayoutRootStyle}">
    	<Grid.ColumnDefinitions>
    		<ColumnDefinition Width="341*"/>
    		<ColumnDefinition Width="342*"/>
    	</Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="140"/>
            <RowDefinition Height="113*"/>
            <RowDefinition Height="515*"/>
        </Grid.RowDefinitions>

        <!-- Back button and page title -->
        <Grid Grid.ColumnSpan="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
            <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{StaticResource AppName}" Style="{StaticResource PageHeaderTextStyle}"/>
        </Grid>
        <ItemsControl Grid.Column="0" Margin="226,10,84,10" Grid.ColumnSpan="2" Grid.Row="1" ItemsSource="{Binding TileCards, Mode=TwoWay}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel VerticalAlignment="Top" Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding Content}" Opacity="{Binding Active}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{Binding Hex}" Width="100" Height="100" Click="Button_Click"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
       
        <TextBlock HorizontalAlignment="Left" Margin="118,38,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Colors" VerticalAlignment="Top" FontSize="32"/>
        <TextBlock x:Name="your_turn" HorizontalAlignment="Left" Margin="478,141,0,0" Grid.Row="2" TextWrapping="Wrap" VerticalAlignment="Top" FontSize="96" Grid.ColumnSpan="2" Width="457" Visibility="Collapsed">
        	<Run Text="You"/>
        	<Run Text="r"/>
        	<Run Text=" "/>
        	<Run Text="turn"/>
        </TextBlock>

        <VisualStateManager.VisualStateGroups>

            <!-- Visual states reflect the application's view state -->
            <VisualStateGroup x:Name="ApplicationViewStates">
                <VisualState x:Name="FullScreenLandscape"/>
                <VisualState x:Name="Filled"/>

                <!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
                <VisualState x:Name="FullScreenPortrait">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>

                <!-- The back button and title have different styles when snapped -->
                <VisualState x:Name="Snapped">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </Grid>
</common:LayoutAwarePage>

For now this should be a working client and server already, you can see that you’re connecting. If you’re familiar by now with SignalR, you can go and write the missing methods on creating a room, notifying other connected users and all.

I’ll show pics of what the output of this. Then will continue part 2 by this week.

All pic here where screenshot on 2 screen, that’s why I can see both at the same time.

Room created:

room created

 

 

In game:

in game

Wait for turn

wait for turn

 

 

 

 

 

 

Player 2′s turn:

player 2 turn

 

Game result

game result

 

 

Just check out my blog this week for updates on part 2 w/ the HTML/JavaScript part. I already have it, just composing the blog :) Have a great day!


Viewing all articles
Browse latest Browse all 15

Latest Images

Trending Articles





Latest Images