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

Windows Phone 7 XNA 游戏的排行榜

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (8投票s)

2012 年 2 月 27 日

CPOL

6分钟阅读

viewsIcon

42734

downloadIcon

1558

为 Windows Phone 7 (WP7) XNA 游戏创建排行榜的解决方案

336116/highscore.gif

老式高分榜,来源于 Coding Horror

引言

本文讨论了为 Windows Phone 7 (WP7) XNA 游戏创建一个活跃排行榜的解决方案,以便将游戏分数发布到后端存储库,并在游戏中显示前十名分数的排行榜。

背景

我正在为 WP7 开发一系列休闲益智游戏。我希望在这些游戏中实现的一个功能是活跃排行榜或高分榜。当玩家完成游戏时,游戏会将分数发布到一个网页。移动设备也可以从网页请求排行榜。

由于我希望我的排行榜功能与我现有的网站配合使用,因此我想实现一个简单的网页,该网页将接受对当前排行榜的请求,并接受来自 WP7 设备的新游戏分数。

使用代码

数据库

由于我的互联网主机提供商使用 SQL Server 作为后端数据库,因此我将在本文中使用它,以便将其集成到我当前的网站中。

为了开始处理数据库,我在 Microsoft SQL Server Management Studio 中创建了一个名为“Leaderboard”的新数据库。在该“Leaderboard”数据库中,我创建了一个名为“Leaderboard”的新表,其架构如下:

     CREATE TABLE [dbo].[Leaderboard](
    [RowId] [int] IDENTITY(1,1) NOT NULL,
    [GameId] [int] NOT NULL,
    [PlayerId] [int] NOT NULL,
    [Score] [int] NOT NULL,
    [Moves] [int] NOT NULL,
    [TimestampUTC] [datetime] NOT NULL,
    [TimestampServer] [datetime] NOT NULL,
    [TimestampDevice] [datetime] NULL,
    [IPAddress] [nvarchar](50) NULL,
    [CountryCode] [nvarchar](50) NULL,
    [CountryName] [nvarchar](50) NULL,
    [RegionName] [nvarchar](50) NULL,
    [CityName] [nvarchar](50) NULL,
    [ZipPostalCode] [nvarchar](50) NULL,
    [Latitude] [nvarchar](50) NULL,
    [Longitude] [nvarchar](50) NULL,
    [GmtOffset] [nvarchar](50) NULL
) ON [PRIMARY]

RowId 是一个 INT Identity 字段。TimestampUTC 默认为 GETUTCDATE()TimestampServer 默认为 GETDATE()

您应该有一个额外的表用于存储玩家信息,您可以在 PlayerId 上与之建立关系,但为了使本文保持简单,我只在排行榜中显示 PlayerId。

ASP.NET

该服务可以通过几种不同的方式编写。

一种方法是创建一个网页,该网页将接受查询字符串,解析它以获取相关数据,然后将该数据写入数据库。同一页面还可以通过查询字符串中的正确参数返回现有的排行榜。

另一种方法是创建一个 Web 服务来处理排行榜请求。

发布高分

地理位置信息

在 ASP.NET 代码中,我使用 WP7 设备报告的 IP 地址来获取额外的地理位置信息。我曾考虑在未来的地理位置游戏功能中使用这些信息。如果您想使用此功能,您需要从 IPInfoDB获取免费的 API 密钥。否则,如果您对该功能不感兴趣,只需注释掉整个 try/catch 代码块即可。

try
{
    // Get your own API key at http://ipinfodb.com
    string ApiKey = "xxxx you'll need to get your own api key xxxx";
    string ApiUrlFormat = "http://api.ipinfodb.com/v3/ip-city/?key={0}&ip={1}";

    string reqUrl = string.Format(ApiUrlFormat, ApiKey, IpAddress);
    HttpWebRequest httpReq = (HttpWebRequest) HttpWebRequest.Create(reqUrl);
    string webResponseString = string.Empty;
    HttpWebResponse webResponse = (HttpWebResponse) httpReq.GetResponse();

    using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
    {
        webResponseString = reader.ReadToEnd();
    }

    string[] webResponseArray = webResponseString.Split(';');

    if (webResponseArray.Length > 0)
    {
        apiStatus = webResponseArray[0];
        apiIp = webResponseArray[2];
        apiCountryCode = webResponseArray[3];
        apiCountyName = webResponseArray[4];
        apiRegionName = webResponseArray[5];
        apiCityName = webResponseArray[6];
        apiZipPostalCode = webResponseArray[7];
        apiLatitude = webResponseArray[8];
        apiLongitude = webResponseArray[9];
        apiGmtOffset = webResponseArray[10];
    }
}

请求排行榜

当 WP7 设备请求排行榜时,网页会查询数据库以获取相关信息。一旦请求的数据存储在 DataTable 中,就会调用 DataTable 的 WriteXml() 方法,将数据和数据结构以 XML 格式写入字符串。此 XML 块将发送到请求的 WP7 设备。

dataTable = dataSet.Tables[0];
using (StringWriter stringWriter = new StringWriter())
{
    dataTable.WriteXml(stringWriter);
    result = stringWriter.ToString().Trim();
}
    

为方便测试,我不得不将 Response 对象的缓存设置为一秒后过期,否则我无法在 WP7 设备上看到快速更新。此设置应移至 web.config,以便以后需要时进行更改。

Response.Cache.SetExpires(DateTime.Now.AddSeconds(1));
        

Windows Phone 7

WP7 项目是一个最精简的 XNA 项目。

用户界面

WP7 用户界面仅包含两个按钮。“Post Score”按钮会将一个随机游戏分数发布到网页。“Get Scores”按钮会从网页请求排行榜并在 WP7 屏幕上显示它。

发布分数

为了获取 WP7 设备的 IP 地址,会异步调用 http://whatismyip.org

Uri uri = new Uri("http://whatismyip.org");
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Address_DownloadStringCompleted);
webClient.DownloadStringAsync(uri); 
           

一旦知道了 WP7 设备的 IP 地址,就会将分数发布到网页。

string IpAddress = e.Result;

string GameId = "1";
string PlayerId = "21";
string Score = new Random().Next(999).ToString();
string Moves = new Random().Next(100).ToString();

string PostFormat = "post={0}|{1}|{2}|{3}|{4}";
string Post = String.Format(PostFormat, GameId, PlayerId, Score, Moves, IpAddress);

Uri uri = new Uri("https://:45291/Leaderboard.aspx?" + Post);
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(PostScore_DownloadStringCompleted);
webClient.DownloadStringAsync(uri);
        

在这里,GameId 是当前正在玩的游戏的 ID。传递 GameId 是为了让排行榜可以维护不同游戏的高分。理论上听起来不错,但在实践中可能效果不佳,因为不同的游戏可能以不同的方式存储分数。

PlayerId 是当前玩家的玩家 ID。应该实现某种注册系统,以便玩家可以注册。但目前,这超出了本文的范围。

当前 URL 设置为测试目的。

请求排行榜

通过在异步调用中将 GameId 传递给网页来请求排行榜。当前 URL 设置为测试目的。

Uri uri = new Uri("https://:45291/Leaderboard.aspx?request=1");
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(Leaderboard_DownloadStringCompleted);
webClient.DownloadStringAsync(uri);             
        
正则表达式

使用正则表达式确保我们只获取 <Leaderboard> 标签之间(包含)的 XML 块。其余的响应对象将被忽略。

result = result.Replace(System.Environment.NewLine, string.Empty);
result = result.Trim();
string xml_block = string.Empty;
System.Text.RegularExpressions.MatchCollection matchCollectionTitle = System.Text.RegularExpressions.Regex.Matches(result, @"
<leaderboard>(.*?)</Leaderboard>");
if (matchCollectionTitle.Count == 1)
{
    xml_block = matchCollectionTitle[0].Value;
}
        
将 XML 块转换为 XML 文档

现在我们有了清理过的 XML 块,它被转换为 XML 文档进行处理。xml 块字符串被转换为字节数组。然后从那里转换为内存流。内存流随后被转换为流读取器。最后,我们将 xml 块放入可以加载到 XML 文档的格式中。

byte[] byteArray = Encoding.UTF8.GetBytes(xml_block);
MemoryStream stream = new MemoryStream(byteArray);

StreamReader streamReader = new StreamReader(stream);

XDocument doc = XDocument.Load(streamReader);     
        
LINQ 和匿名对象

一旦创建了 XML 文档,就可以利用 LINQ 和匿名对象的魔力来解析 XML 文档并填充排行榜。

var xmlLeaderboardEntries = from xmlLeaderboardEntry in doc.Descendants("Table")
                select new
                    {
                        PlayerId = xmlLeaderboardEntry.Element("PlayerId").Value,
                        Score = xmlLeaderboardEntry.Element("Score").Value,
                        Moves = xmlLeaderboardEntry.Element("Moves").Value,
                        TimestampUTC = xmlLeaderboardEntry.Element("TimestampUTC").Value,
                    };

leaderboard.Clear();
foreach (var xmlLeaderboardEntry in xmlLeaderboardEntries)
{
    LeaderboardEntry leaderboardEntry = new LeaderboardEntry();
    leaderboardEntry.PlayerId = xmlLeaderboardEntry.PlayerId;
    leaderboardEntry.Score = xmlLeaderboardEntry.Score;
    leaderboardEntry.Moves = xmlLeaderboardEntry.Moves;
    leaderboardEntry.TimestampUTC = xmlLeaderboardEntry.TimestampUTC;
    leaderboard.Add(leaderboardEntry);
}      
        

测试

要测试该解决方案,请在一个 Visual Studio 2010 (VS2010) 实例中加载并执行 Web 项目。在第二个 VS2010 实例中加载 WP7 项目。在 WP7 模拟器中运行 WP7 项目。

在 WP7 模拟器中,点击“Post Score”以发布一些随机分数数据。

点击“Get Scores”以检索当前排行榜。

336116/wp7_1.png 336116/wp7_2.png

关注点

我遇到的一项问题是,当我反复尝试从 WP7 项目更新排行榜时,排行榜没有发生变化。WP7 项目一直获取响应的缓存副本。经过一些研究,我发现了一些解决此问题的方法。

我在 ASP.NET 代码中添加了一行,使响应对象缓存在一秒后过期。

或者,您可以在 WP7 项目的查询字符串中添加一个额外的字段,以包含一个随机数或一个日期时间戳。这将导致服务器始终发送新数据而不是缓存副本。

待办事项

本文所述解决方案的功能只是一个概念验证。我需要将解决方案部署到我的网站,将排行榜代码集成到我的一个 Windows Phone 7 游戏中,并进行测试。我计划在将解决方案迁移到我的 Web 主机之前实现一些额外的功能。

  • 将连接字符串和其他硬编码的配置设置移至 web.config 文件<
  • 将 SQL 代码从 C# 移至数据库本身中的存储过程
  • 加密发送到和来自移动设备的数据
  • 实现某种移动设备和 ASP.NET 之间的身份验证

摘要

请告诉我您对本文和解决方案的看法。任何问题、疑虑和批评都欢迎。

参考文献

链接

  • http://ipinfodb.com

历史

2012-02-26 初始文章

© . All rights reserved.