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






4.97/5 (14投票s)
一个简单的解决方法,用于 Windows Phone 7 应用直接与 SQL Azure 协同工作。
目录
引言
截至 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 格式)。
您需要做的就是播放声音
- 添加所需的
using
指令 - 呼叫
- 我将声音放在项目目录中,并将其生成操作设置为**内容**。
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
var stream = TitleContainer.OpenStream("sound 1.wav");
var effect = SoundEffect.FromStream(stream);
FrameworkDispatcher.Update();
effect.Play();
按破
所有气泡都只对一个事件做出反应: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 的 文章中读到了关于它的信息。
现在我将向您展示我如何使用它。
- 声明一个代表独立存储的变量。
- 创建手机 GUID(仅一次)。如果您想显示特定手机的所有玩家结果(例如,本地排行榜),这实际上非常方便。
- 玩家姓名验证:这有点棘手。如果我们已经有玩家玩过游戏,我们应该直接从本地存储加载他的姓名,并加载分数和尝试次数,否则我们应该将用户带到设置菜单并强制他输入姓名。
- 在加载玩家的尝试次数和分数时,我们应该使用他的姓名。
- 至于玩家姓名验证,我使用了两个简单的规则
- 它应该与默认的“Player”不同
- 它不能为空
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
private void InitializePhoneGuid()
{
if (settings.Contains("guid"))
{
_phoneGuid = (Guid) settings["guid"];
}
else
{
_phoneGuid = Guid.NewGuid();
settings.Add("guid", _phoneGuid);
}
}
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);
}
}
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";
}
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
现在当用户想要查看顶级用户列表时,我们执行以下操作
- 提交分数
- 请求顶级玩家列表
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.");
}
}
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;
在我们的对话框中,我们有一个带有 ListBox
的 Grid
。
<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。