65.9K
CodeProject 正在变化。 阅读更多。
Home

Bubble Wrap App: 将您的分数放入云端

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (14投票s)

2010年12月14日

CPOL

11分钟阅读

viewsIcon

46851

downloadIcon

1162

一个简单的解决方法,用于 Windows Phone 7 应用直接与 SQL Azure 协同工作。

图 1. 游戏屏幕

图 2. 玩家个人资料屏幕

图 3. 全球顶级玩家记分板

目录

引言

截至 2010 年 12 月,Windows Phone 7 设备缺乏对数据库的原生支持。是的,这个问题有第三方解决方案,但它们要么付费,例如 Perst DB,要么不够稳定,例如 CodePlex 上的 Windows Phone 7 Database

当我开始开发我第一个真正简单的应用程序 BubbleWrap 时(我知道每个移动平台都有这样的应用程序),我从未想过我需要数据库。然后我读到这个网站上的用户评论:Windows Phone 7 AppReviews(它只显示所有市场用户评论的总和)。评论大多是负面的;用户责怪我没有在我的应用程序中包含任何评分系统。

在我看来,一个本地(在手机上)的顶级玩家记分板会很无聊且没有竞争力,所以我开始考虑使用 Microsoft Azure 云技术。当我搜索互联网上的可能解决方案时,很多人说:“是的,Windows Phone 7 手机很酷,因为它们允许轻松与云集成。” 但找到一个可行的例子并不容易。

对于顶级玩家记分板,我需要 SQL Azure 数据库。我必须能够直接读写数据库。但 Windows Phone 7 没有提供这样的类!是的,有一个适用于 Windows Phone 7 的 OData 客户端版本,但该项目处于 CTP 状态,并且只能从数据库读取。我需要一些真正稳定且简单的东西,并且我需要将玩家结果记录到数据库。一切都改变了,当我看到 Steve Marx 的演讲:“使用 Windows Azure Platform 构建 Windows Phone 7 应用程序”在 PDC10 上。他演示了如何使用 System.Net.WebClient 类来访问 Azure Web Service。“找到了!”——我说道。Windows Phone 7 有一个 WebClient,它可以调用带有参数的 Azure WebService 方法,而 Azure WebService 可以完全访问 SQL Azure 数据库(甚至不需要设置防火墙规则)!

因此,我开发了我的解决方案,现在我想与您分享。

第一部分:Bubble Wrap 应用

我大约在两个月前开始学习 Windows Phone 7 OS 的编程。我不知道如何为这个平台制作复杂而美观的用户界面,所以我用 Silverlight 制作了最简单的对话框。这是示意图

正如您所见,我没有制作多个对话框;相反,我使用了三个网格,并在单击按钮时更改它们的 Visibility 属性。

气泡

要创建一个带有气泡的屏幕,我只需要创建一个具有足够行和列的网格,并在其中放置图像元素。重要提示:每个气泡应该跨越两列(这样我们就可以得到一个独特的图案)。别忘了在背景上放置合适的图片!

<Grid x:Name="LayoutRoot" Grid.Row ="1">
         <Grid.RowDefinitions>
                 <RowDefinition />
                 <RowDefinition />
                 <RowDefinition />
                 <RowDefinition />
                 <RowDefinition />
                 <RowDefinition />
                 <RowDefinition />
                 <RowDefinition />
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
                 <ColumnDefinition />
         </Grid.ColumnDefinitions>
 
         <Grid.Background>
                 <ImageBrush ImageSource="..\Resources\Images\image 90.jpg"/>
         </Grid.Background>
 
         <!--Row 0-->
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="1" 
           Grid.ColumnSpan="2" Grid.Row="0" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="3" 
           Grid.ColumnSpan="2" Grid.Row="0" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="5" 
           Grid.ColumnSpan="2" Grid.Row="0" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="7" 
           Grid.ColumnSpan="2" Grid.Row="0" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="9" 
           Grid.ColumnSpan="2" Grid.Row="0" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="11" 
           Grid.ColumnSpan="2" Grid.Row="0" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
 
         <!--Row 1-->
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="0" 
           Grid.ColumnSpan="2" Grid.Row="1" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="2" 
           Grid.ColumnSpan="2" Grid.Row="1" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="4" 
           Grid.ColumnSpan="2" Grid.Row="1" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="6" 
           Grid.ColumnSpan="2" Grid.Row="1" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="8" 
           Grid.ColumnSpan="2" Grid.Row="1" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="10" 
           Grid.ColumnSpan="2" Grid.Row="1" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
         <Image MouseLeftButtonDown="ImageMouseLeftButtonDown" Grid.Column ="12" 
           Grid.ColumnSpan="2" Grid.Row="1" 
           Source="..\Resources\Images\image 60.png" Stretch="Fill" />
 
         <!—other rows-->
</Grid>

对于图像,我们应该将 **生成操作** 设置为“**资源**”。

当我们按压气泡时,它应该保持按压状态。因此,我们为被按破的气泡添加额外的图片作为应用 **资源**。

<phone:PhoneApplicationPage.Resources>
     <Image x:Name="original" Source="..\Resources\Images\image 60.png"/>
     <Image x:Name="pushed1" Source="..\Resources\Images\image 62.png"/>
     <Image x:Name="pushed2" Source="..\Resources\Images\image 65.png"/>
     <Image x:Name="pushed3" Source="..\Resources\Images\image 68.png"/>
     <Image x:Name="pushed4" Source="..\Resources\Images\image 71.png"/>
     <Image x:Name="pushed5" Source="..\Resources\Images\image 74.png"/>
     <Image x:Name="pushed6" Source="..\Resources\Images\image 77.png"/>
</phone:PhoneApplicationPage.Resources>

声音

Windows Phone 7 应用程序中有两种播放声音的方式。

第一种是使用 MediaElement 控件。这是最简单的方法,但也有缺点。您不能在一个对话框中放置多个 MediaElement,并且在该控件中更改音轨有点复杂。对于我的应用程序,我需要几种声音,因为当气泡破裂时,它们会发出不同种类的声音。

所以我选择了另一种方式,为我的 Silverlight 应用程序添加了 XNA 框架支持。我所做的只是将 Microsoft.Xna.Framework 添加到我的应用程序作为引用程序集。在这种情况下,您可以使用 XNA 播放声音(仅支持 *.wav 格式)。

您需要做的就是播放声音

  1. 添加所需的 using 指令
  2. using System.IO;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
  3. 呼叫
  4. var stream = TitleContainer.OpenStream("sound 1.wav");
    var effect = SoundEffect.FromStream(stream);
    FrameworkDispatcher.Update();
    effect.Play();
  5. 我将声音放在项目目录中,并将其生成操作设置为**内容**。

按破

所有气泡都只对一个事件做出反应:MouseLeftButtonDown。我使用一个随机变量来检测要放置的图片和要播放的声音。要更改图像,您必须为其设置新的 Source

private void ImageMouseLeftButtonDown(object sender, 
             System.Windows.Input.MouseButtonEventArgs e)
{
     var image = sender as Image;
     if (image == null)
        return;
     var bitmapImage = image.Source as BitmapImage;
     var originalImage = original.Source as BitmapImage;
     if (bitmapImage == null)
        return;
     if (originalImage == null)
        return;
     if (bitmapImage.UriSource != originalImage.UriSource)
        return;
 
     switch (_random.Next(5))
     {
         case 0:
              image.Source = pushed1.Source;
              break;
         case 1:
              image.Source = pushed2.Source;
              break;
         case 2:
              image.Source = pushed3.Source;
              break;
         case 3:
              image.Source = pushed4.Source;
              break;
         case 4:
              image.Source = pushed5.Source;
              break;
         case 5:
              image.Source = pushed6.Source;
              break;
     }
 
     //update score
     settings[_playerName + "score"] = _score++;
     Score.Text = ScoreText.Text = "Bubbles popped so far: " + _score;
     //play sound
 
     Stream stream = null;
     switch (_random.Next(5))
     {
         case 0:
              stream = TitleContainer.OpenStream("sound 64.wav");
              break;
         case 1:
              stream = TitleContainer.OpenStream("sound 67.wav");
              break;
         case 2:
              stream = TitleContainer.OpenStream("sound 70.wav");
              break;
         case 3:
              stream = TitleContainer.OpenStream("sound 73.wav");
              break;
         case 4:
              stream = TitleContainer.OpenStream("sound 76.wav");
              break;
         case 5:
              stream = TitleContainer.OpenStream("sound 79.wav");
              break;
     }
 
     if (stream == null)
        return;
 
     var effect = SoundEffect.FromStream(stream);
     FrameworkDispatcher.Update();
     effect.Play();
}

有时,我们会用完气泡。在这种情况下,我们必须用新的源替换所有气泡图像。这就是“还要更多!”按钮的作用

//put new bubbles
if (!Renovate())
         return;
 
//play sound
var stream = TitleContainer.OpenStream("sound 1.wav");
var effect = SoundEffect.FromStream(stream);
FrameworkDispatcher.Update();
effect.Play();

这是 Renovate() 函数的主要代码

foreach (var image in LayoutRoot.Children.Select(child => child as Image))
{
     if (image == null)
        return false;
     var bitmapImage = image.Source as BitmapImage;
     var originalImage = original.Source as BitmapImage;
     if (bitmapImage == null)
        return false;
     if (originalImage == null)
        return false;
     image.Source = original.Source;
}

到目前为止,我们已经具备了基本功能:我们的气泡在按压,声音在播放。

第二部分:用户个人资料

接下来是创建简单的用户个人资料。我的想法是这样的:假设每部手机都有一个 GUID(由应用程序本身生成,而非用户生成 - 他们应该不知道它的存在)。然后,我们有一个玩家,他的分数和尝试次数。

Windows Phone 7 有一个称为“Tombstoning”的系统用于保存应用程序状态。我从 Jeff Blankenburg 的 文章中读到了关于它的信息。

现在我将向您展示我如何使用它。

  1. 声明一个代表独立存储的变量。
  2. IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
  3. 创建手机 GUID(仅一次)。如果您想显示特定手机的所有玩家结果(例如,本地排行榜),这实际上非常方便。
  4. private void InitializePhoneGuid()
    {
         if (settings.Contains("guid"))
         {
            _phoneGuid = (Guid) settings["guid"];
         }
         else
         {
             _phoneGuid = Guid.NewGuid();
             settings.Add("guid", _phoneGuid);
         }
    }
  5. 玩家姓名验证:这有点棘手。如果我们已经有玩家玩过游戏,我们应该直接从本地存储加载他的姓名,并加载分数和尝试次数,否则我们应该将用户带到设置菜单并强制他输入姓名。
  6. private void InitializePlayerName()
    {
         if (settings.Contains("player"))
         {
             _playerName = (string) settings["player"];
    
             PlayerName.Text = _playerName;
             PlayerNameTextBox.Text = _playerName;
    
             InitializeScore();
             InitializeAttempts();
             ShowGrid(Grids.LayoutRootGrid);
         }
         else
         {
             //init new player
             ShowGrid(Grids.SettingsGrid);
         }
    }
  7. 在加载玩家的尝试次数和分数时,我们应该使用他的姓名。
  8. private void InitializeScore()
    {
         if (settings.Contains(_playerName + "score"))
         {
            _score = (int)settings[_playerName + "score"];
         }
         else
         {
             _score = 0;
             settings.Add(_playerName + "score", 0);
         }
         Score.Text = ScoreText.Text = "Bubbles popped so far: " + _score;
    }
    
    private void InitializeAttempts()
    {
         if (settings.Contains(_playerName + "attempts"))
         {
            _attempts = (int)settings[_playerName + "attempts"];
         }
         else
         {
             _attempts = 0;
             settings.Add(_playerName +"attempts", 0);
         }
    
         settings[_playerName + "attempts"] = _attempts++;
         Attempts.Text = "You had " + _attempts + " attempts";
    }
  9. 至于玩家姓名验证,我使用了两个简单的规则
    1. 它应该与默认的“Player”不同
    2. 它不能为空
    private bool CheckValidPlayer()
    {
        var playerNameString = PlayerNameTextBox.Text;
    
         //trim player name string
         playerNameString = playerNameString.Trim();
    
         //player name should not be empty string
         if (String.IsNullOrEmpty(playerNameString))
         {
             MessageBox.Show("Player's name could not be empty.");
             return false;
         }
         //player name could not be default name "Player"
         if (String.Compare(playerNameString, "Player", 
             StringComparison.InvariantCultureIgnoreCase) == 0)
         {
             MessageBox.Show("You can't set default name \"Player\".");
             return false;
         }
    
         //set players name everywhere
         {
             _playerName = playerNameString;
             if (settings.Contains("player"))
             {
                settings["player"] = _playerName;
             }
             else
             {
                settings.Add("player", _playerName);
             }
             PlayerName.Text = _playerName;
             PlayerNameTextBox.Text = _playerName;
         }
    
         //everything is OK, initialize players
         //attempts and scores and return true
         InitializeScore();
         InitializeAttempts();
    
         return true;
    }

所以现在我们有了一个简单的游戏,并且我们保存了玩家的分数和尝试次数。是时候将这些数据上传到云端,以便所有人都能看到。

第三部分:使用 Windows Azure

在我的解决方案中,我使用了两种 Azure 技术:Azure SQL Database 和托管服务。我不得不说,使用云技术的过程相对简单!

到目前为止,这是我文章中最激动人心的部分!

数据库

我们将从创建 Azure SQL 数据库开始。为此,我们将使用 Microsoft SQL Server 2008 R2 的 SQL Server Management Studio。我作为 Microsoft BizSpark 计划的参与者获得了我的副本,我想您也可以尝试一下。

注意:出于安全原因,我将隐藏我的真实服务器地址,因此在您输入您的服务器名称之前,提供的解决方案将无法正常工作。

注意 2:此内容中的某些部分可能与 Windows Azure Platform 培训套件重复。确实,在这个主题上很难说出完全新颖的内容。

如果您已经 有机会获得 Windows Azure Platform 30 天试用版,那么您就可以使用您的凭据登录到 http://windows.azure.com 并看到以下起始页面。

通过点击 **数据库** 按钮,您将看到您的服务器信息和当前数据库。

如果您还没有创建数据库服务器,请点击“创建新服务器”按钮并按照说明进行操作。不幸的是,我无法向您展示该过程,因为每个订阅只能拥有一个 Azure SQL Server。

不要忘记设置 **防火墙规则** 以允许您自己和 Windows Azure 服务访问。

现在是时候启动 Microsoft SQL Server Management Studio 了。确保您已输入服务器名称并选择了 SQL Server 身份验证。使用您的管理员登录名和密码登录。

然后点击 **选项**,并在 **连接到数据库** 字段中输入“master”。

恭喜,您已成功连接到 master 数据库!

现在,让我们创建一个新数据库来保存玩家的最高分数。执行以下查询

Create Database TopScore

如果查询成功执行,您将看到以下消息

现在,我们将创建新用户的凭据

CREATE LOGIN
SomeUser WITH password='SomePassword1'

GO

现在我们应该使用管理员凭据连接到新创建的数据库

在 **连接属性** 选项卡中更改数据库名称。

我们将使用之前输入的登录名和密码创建用户。

-- Create a new user from the login and execute
CREATE USER SomeUser FROM LOGIN SomeUser
GO

确保您是在 **TopScore** 数据库上执行此查询,而不是在 master 数据库上。否则,您将为您的 master 数据库创建新用户。

要检查新用户是否已创建,请打开 **安全性 > 用户** 分支。

现在我们将为 **SomeUser** 设置权限。不要忘记我们将把分数保存到数据库并加载顶级玩家列表。

执行两个查询

-- Add the new user to the db_datareader role and execute
EXEC sp_addrolemember 'db_datareader', 'SomeUser'
GO

-- Add the new user to the db_datawriter role and execute
EXEC sp_addrolemember 'db_datawriter', 'SomeUser'
GO

现在只剩下一件事了:我们应该创建一个表来记录玩家的分数。

CREATE TABLE BubbleWrapTopScore(
    [PhoneId] [nvarchar](50) NOT NULL,
    [PlayerName] [nvarchar](50) NOT NULL,
    [Score] [int] NULL,
    [Attempts] [int] NOT NULL
    CONSTRAINT PKplayer PRIMARY KEY ([PhoneId],[PlayerName])
)

这是一个非常简单的表:我们只保存手机 ID、玩家姓名、分数和尝试次数。将来,我们可以添加更多列,例如,记录时间,显示当天的最佳玩家、当月的最佳玩家等等。或者您可以添加列来显示玩家是否达到了某些值得注意的成就。

我们必须最后检查一件事:我们是否真的可以用新用户的凭据连接。断开与数据库的连接,然后使用他的登录名和密码重新连接。

确保您连接到正确的数据库。

Web 角色

下一个任务是创建一个 Windows Azure Cloud 服务,该服务将实际向数据库发出请求。

打开 Visual Studio,启动一个新项目,选择 **C# > Cloud > Windows Azure Cloud Service**。

输入一个新名称

在我的项目中,我使用的是 **ASP.NET MVC 2 Web Role**。这是最容易实现的。

我决定不为我的项目创建单元测试。

我们将修改 HomeController.cs 文件。

在这种情况下,我们将创建一个服务,该服务接收来自 Windows Phone 7 应用的所有数据(服务器名称、用户密码、登录名等),这大大简化了它。

HomeController 中添加两个方法,用于从数据库保存和读取结果。

public ActionResult SaveScore(string id)
{
     try
     {
         var result = id.Split(',');
         var databaseWrite = 
             new AdoConnection(result[0], result[1], result[2], result[3]);
         databaseWrite.WriteToDataBase(result[4], result[5], 
                       result[6], result[7], result[8]);
         return Content("Score saved.");
     }
     catch(Exception)
     {
        return Content("Failed to save score.");
     }
}
 
public ActionResult TopScore(string id)
{
     try
     {
         var result = id.Split(',');
         var databaseWrite = new AdoConnection(result[0], 
                                 result[1], result[2], result[3]);
         var topScore = databaseWrite.ReadFromDataBase(result[4]);
         return Content(topScore);
     }
     catch (Exception)
     {
        return Content("Failed to read top score table.");
     }
}

然后向同一文件添加新类。

public class AdoConnection : SqlAzureConnection
{
     public AdoConnection(string userName, string password, 
            string dataSource, string databaseName)
            : base(userName, password, dataSource, databaseName)
     {
     }
 
     protected override DbConnection CreateConnection(string userName, 
               string password, string dataSource, string databaseName)
     {
          return new SqlConnection(CreateAdoConnectionString(userName, 
                                   password, dataSource, databaseName));
     }
 
     private static string CreateAdoConnectionString(string userName, 
             string password, string dataSource, string databaseName)
     {
         // create a new instance of the SQLConnectionStringBuilder
         var connectionStringBuilder = new SqlConnectionStringBuilder
         {
              DataSource = dataSource,
              InitialCatalog = databaseName,
              Encrypt = true,
              TrustServerCertificate = false,
              UserID = userName,
              Password = password,
 
         };
         return connectionStringBuilder.ToString();
     }
 
     protected override DbCommand CreateCommand(DbConnection connection)
     {
         return new SqlCommand { Connection = connection as SqlConnection };
     }
}
 
public abstract class SqlAzureConnection
{
     private readonly DbConnection _connection;
 
     protected SqlAzureConnection(string userName, string password, 
               string dataSource, string databaseName)
     {
        _connection = CreateConnection(userName, password, 
                                       dataSource, databaseName);
     }
 
     protected abstract DbConnection CreateConnection(string userName, 
               string password, string dataSource, string databaseName);
 
     protected abstract DbCommand CreateCommand(DbConnection connection);
 
     public void WriteToDataBase(string tableName, string phoneGuid, 
            string playerName, string playerScore, string playerAttempts)
     {
         _connection.Open();
         var command = CreateCommand(_connection);
 
         command.CommandText="UPDATE " + tableName +
             " SET Score = '" + playerScore + 
             "', Attempts = '" + playerAttempts +
             "' WHERE PhoneId = N'" + phoneGuid + 
             "' AND PlayerName = N'" + playerName + "' " +
             "IF @@ROWCOUNT=0 " +
             "INSERT INTO " + tableName + 
             " (PhoneId, PlayerName, Score, Attempts) VALUES (N'" +
             phoneGuid + "', N'" + playerName + "', " + 
             playerScore + ", " + playerAttempts + ")";
 
         command.ExecuteNonQuery();
         _connection.Close();
     }
 
     public string ReadFromDataBase(string tableName)
     {
         _connection.Open();
         var command = CreateCommand(_connection);
 
          command.CommandText = 
            "SELECT TOP 10 PlayerName, Score, Attempts FROM " + 
            tableName + " ORDER BY Score DESC, Attempts ASC";
 
         IDataReader reader = command.ExecuteReader();
 
         var result = "";
 
         // loop over the results and write them out to the console
         while (reader.Read())
         {
                  for (var col = 0; col < reader.FieldCount; col++)
                  {
                           result += reader.GetValue(col) + ",";
                  }
         }
         reader.Close();
 
         //removing last ',' sign
         result = result.Substring(0, result.Length - 1);
         return result;
     }

}

这两个类创建了到 SQL Azure 数据库的连接并执行必要的查询。我们以逗号分隔值字符串的形式提交和返回数据。

要将服务部署到服务器,请执行以下操作。

右键单击 SomeTopScoreCloudService 项目,然后选择“发布”项。

选择“仅创建服务包”项。

这将为您创建必要的文件。

现在转到您的管理门户。打开“托管服务、存储帐户和 CDN”对话框,然后点击“创建新托管服务”按钮。

输入您的订阅数据(名称、URL 等),并将 Visual Studio 在发布时创建的文件指向其中。

点击 **确定**,您就完成了!

注意:系统会提示您建议同时运行两个服务实例,因为这将实现 99% 的正常运行时间。忽略它,因为我们的服务负载非常低,而 Azure 服务可能会很贵。

手机应用

现在我们已经在 Azure 平台上运行了服务,我们应该向我们的应用程序添加 Web 服务调用。

我在我的应用程序中将数据库参数保存为常量。我知道:这可能不太安全,但这样更容易。

#region constants
const string UserName = "SomeUser";
const string Password = "SomePassword1";
const string Datasource = "[server].database.windows.net";
const string DatabaseName = "TopScore";
const string TableName = "TopScore";
#endregion

现在当用户想要查看顶级用户列表时,我们执行以下操作

  1. 提交分数
  2. void SubmitScore()
    {
           var id = UserName + ',' + Password + ',' + Datasource + ',' + 
                    DatabaseName + ',' + TableName + ',' + 
                    _phoneGuid + ',' + PlayerName.Text + ',' + 
                    _score + ',' + _attempts;
           var client = new WebClient();
           client.DownloadStringCompleted += SubmitScoreCompleted;
           client.DownloadStringAsync(
             new Uri("http://[your URL].cloudapp.net/home/SaveScore/" + id));
    }
    void SubmitScoreCompleted(object sender, DownloadStringCompletedEventArgs args)
    {
       try
       {
            //MessageBox.Show(args.Result);
            MakeTopList();
       }
       catch (Exception)
       {
           //MessageBox.Show("Can't submit the scores to server.");
       }
    }
  3. 请求顶级玩家列表
  4. void MakeTopList()
    {
             var id = UserName + ',' + Password + ',' + Datasource + ',' + 
                      DatabaseName + ',' + TableName;
             var client = new WebClient();
             client.DownloadStringCompleted += MakeTopListCompleted;
             client.DownloadStringAsync(
               new Uri("http:// [your URL].cloudapp.net/home/TopScore/" + id));
    }
     
    void MakeTopListCompleted(object sender, DownloadStringCompletedEventArgs args)
    {
         try
         {
             if (String.IsNullOrEmpty(args.Result))
                return;
     
             var result = args.Result.Split(',');
             var position = result.Length/3;
             _playerPositions.Clear();
             for (var i = 0; i < position; i++)
             {
                _playerPositions.Add(new PlayerPosition(i+1, 
                   result[i * 3], result[i * 3 + 1], result[i * 3 + 2]));
             }
     
             topPlayersList.ItemsSource = _playerPositions;
         }
         catch (Exception)
         {
            MessageBox.Show("Can't connect to server.");
         }
    }

要显示顶级玩家列表,我们有一个特殊的类

public class PlayerPosition
{
     public int Position { get; set; }
     public string Name { get; set; }
     public string Score { get; set; }
     public string Attempts { get; set; }

     public PlayerPosition(int position, string name, 
                           string score, string attempts)
     {
         Position = position;
         Name = name;
         Score = score;
         Attempts = attempts;
     }
}

以及一个包含玩家排名的列表

List<PlayerPosition> _playerPositions;

在我们的对话框中,我们有一个带有 ListBoxGrid

<ListBox x:Name="topPlayersList">
     <ListBox.ItemTemplate>
         <DataTemplate>
              <Grid Margin="0,10,0,5">
                   <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                   </Grid.RowDefinitions>
                   <Grid.ColumnDefinitions>
                        <ColumnDefinition />
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition />
                        <ColumnDefinition Width="120"/>
                        <ColumnDefinition />
                   </Grid.ColumnDefinitions>

                   <TextBlock FontSize="44" Text="{Binding Position}" 
                       Grid.RowSpan="2" FontWeight="Bold" />

                   <TextBlock FontSize="22" Text="{Binding Name}" 
                     Grid.Column="1" Grid.ColumnSpan="4" 
                     Margin="20,0,0,0" FontWeight="Bold" />
                   <TextBlock FontSize="22" Text="Score:" 
                     Grid.Row="1" Grid.Column="1" Margin="20,0,0,0" />
                   <TextBlock FontSize="22" Text="{Binding Score}" 
                     Grid.Row="1" Grid.Column="2" Margin="0,0,0,20" />
                   <TextBlock FontSize="22" Text="Attempts:" 
                     Grid.Row="1" Grid.Column="3" Margin="20,0,0,0" />
                   <TextBlock FontSize="22" Text="{Binding Attempts}" 
                     Grid.Row="1" Grid.Column="4" />
               </Grid>
         </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>

现在我们已经实现了所有功能。

第四部分:广告平台

我的第一个应用程序是免费的,但 Windows Azure 服务不是。24/7 运行的虚拟服务可能会非常昂贵。所以我决定坚持使用 Microsoft 广告平台并在我的应用程序中放置一个横幅广告。我必须说——这绝对值得!

部署非常简单。您可以在 这里找到所有说明。

结论

在我的文章中,我演示了一种相对简单的方法,可以将 Windows Azure 支持添加到您的应用程序中。可以通过向数据库表中添加更多列来改进它,这些列可以表示玩家的成就、游戏时间等。我想要强调的一个重要功能是,您有机会为您的 Android、iOS 和 Windows Phone 7 应用程序创建一个单一的排行榜,因为您的应用程序只需要向 Azure 服务发出 Web 请求并获得响应。

非常欢迎任何反馈!

我想感谢 Steve Marx 的 精彩演讲,以及 PDC10。

© . All rights reserved.