開發WP版本的大菠蘿英雄榜

鞠強發表於2014-10-27

前言

 

想當年Team有無數人在玩大菠蘿,我被忽悠進來做肉盾,選了蠻子,從1.0開始,經歷了103、105、108、2.0、2.1。這個遊戲對我最大的幫助是學習了不同的技術,比如XAML、比如xcode開發、比如WP的開發。

這篇文章不會step by step的介紹如何從0開始做WP開發,我會重點記錄開發過程中要注意的坑,以及一些釋出上架時的注意事項。

文中大部分內容對於熟悉XAML的人來講,可能過於簡單。放在這裡,希望對初學者有個幫助,尤其是如我這樣做winform開發的人。

 

先上幾張圖,

wp_ss_20141024_0001wp_ss_20141024_0002wp_ss_20141024_0005wp_ss_20141024_0006wp_ss_20141024_0007wp_ss_20141024_0008 wp_ss_20141024_0009       

 

官方API

玻璃渣現在有兩套API在並行執行,官方文件老版本的地址:https://github.com/blizzard/d3-api-docs,新版本的地址:https://dev.battle.net/io-docs

兩者的區別是,前者不包含諸如堅韌、聖教軍等資料片中出現的內容,當然也不包括天梯、附魔等內容。後者不包含每個裝備的item tooltip html。同時,後者必須要註冊一個開發者賬號(免費的)。

XAML繫結

Appbar的寫法

<phone:PhoneApplicationPage.ApplicationBar>

    <shell:ApplicationBar BackgroundColor="Black" ForegroundColor="White"  Mode="Default"  Opacity="1.0" IsMenuEnabled="True" IsVisible="True"> 
        <shell:ApplicationBar.MenuItems> 
            <shell:ApplicationBarMenuItem Text="Feedback" Click="Email_Click"/> 
            <shell:ApplicationBarMenuItem Text="Share" Click="Share_Click"/> 
            <shell:ApplicationBarMenuItem Text="Score" Click="Score_Click"/> 
            <shell:ApplicationBarMenuItem Text="Clear Cache" Click="ClearCache_Click"/> 
            <!--<shell:ApplicationBarMenuItem Text="Server Status" Click="ServerStatus_Click"/>--> 
        </shell:ApplicationBar.MenuItems> 

        <shell:ApplicationBarIconButton IconUri="/assets/appbar/search.png" Text="Search Friend" Click="AppbarAddFriend_Click"/> 

    </shell:ApplicationBar> 
</phone:PhoneApplicationPage.ApplicationBar>
View Code

 

這裡分為兩部分,MenuItems是右下角三個點對應的選單項,IconButton對應的是圖示按鈕。前者無對應圖示,如果是英文,則全部小寫字母;後者可以指定是否只顯示圖示,或者同時顯示圖示與文字。

畫線

<Line X2="300" Stroke="White" Height="1" StrokeThickness="3"></Line>

 

這段XAML畫一條白色的線,注意顏色及線寬都是用Stroke***屬性指定的。

指定格式繫結數字

<TextBlock HorizontalAlignment="Right" Text="{Binding Toughness, Converter={StaticResource IntConverter}}">Toughness</TextBlock>

這個文字框繫結資料片中英雄的堅韌屬性,如果想按照千分位(或者你自己別的格式顯示),則再指定Converter的class資訊。

如,千分位的Convert程式碼如下:

public class IntConverter : IValueConverter 
    { 
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
        { 
            return String.Format("{0:N0}", value); 
        }

 

 

而如果顯示小數點後兩位的浮點數,則對應程式碼為:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
        { 

            return String.Format("{0:f2}", value); 
        }

 

屬性的巢狀繫結

<Border BorderThickness="1" Height="130" Canvas.Left="72" Canvas.Top="515" Width="68" BorderBrush="{Binding ItemList[mainhand].BorderBrush]}"  Tap="MainHand_Tap"> 
    <Border.Background> 
        <ImageBrush ImageSource="{Binding ItemList[mainhand].BorderBackGround}"/> 
    </Border.Background> 
    <Image Source="{Binding ItemList[mainhand].ItemImage}" Stretch="None" Height="128" Width="64" Margin="0,0,0,0"/> 
</Border>

 

這個page的DataContext是Hero hero,而Hero的部分結構如下:

public class Hero 
{ 
    private int id; 
    private string name; 
//。。。

    private Dictionary<string, Item> itemList = new Dictionary<string, Item>();

 

主手武器的圖片,如果用code behind方式寫,對應程式碼中的hero.ItemList[“mainhand”].ItemImage。XAML方式則對應為:"{Binding ItemList[mainhand].ItemImage}" 。注意mainhand屬性在xaml中沒有了雙引號。

屬性的巢狀繫結2

<Image Canvas.Left="254" Canvas.Top="38" Source="{Binding SkillList[1].SkillImage}" Stretch="None" Tap="Skill1_Tap"></Image>

這是Skill中的滑鼠右鍵技能,大菠蘿目前一共有2個滑鼠技能,4個Action技能,4個被動技能(資料片之前是3個),程式碼中簡單的用SkillList包含了這10個技能。所以對於滑鼠右鍵技能,繫結的Xaml就變成了"{Binding SkillList[1].SkillImage}" ,同理,對於第一個被動技能,則對應的是="{Binding SkillList[6].SkillImage}"

程式碼相關

Unix時間的轉換

D3中的last updated是Unix時間,是一個ulong型別的值,轉換為DateTime的程式碼如下:

DateTime unix = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 
DateTime last = unix.AddSeconds(lastUpdated);

 

檔名過長

為了提高效率,對於裝備的圖片,程式碼中進行了快取,儲存在該應用的IsolatedStorage目錄下。D3中的tooltip名字都很長,Windows系統中,路徑+檔名長度不能超過260個位元組。所以簡單的做法,是對檔名做了一個Hash,來作為快取檔名稱。(當然,會有偶爾的衝突,這個程式碼沒有做處理)

localfile = Math.Abs(localfile.GetHashCode()).ToString();//localfile就是本地快取的檔名。

 

手機可用空間

string free = String.Empty; 

long freeSize=IsolatedStorageFile.GetUserStoreForApplication().AvailableFreeSpace;

if (freeSize >> 30 >= 1) free = String.Format("{0:N0}GB", (freeSize >> 30)); 
else if (freeSize >> 20 >= 1) free = String.Format("{0:N0}MB", (freeSize >> 20)); 
else if (freeSize >> 10 >= 1) free = String.Format("{0:N0}KB", (freeSize >> 10)); 
else free = String.Format("{0:N0}", freeSize);

 

讀取本地資源

public BitmapImage BackGround 
        { 
            get 
            { 
                return new BitmapImage(new Uri("/assets/background/" + this._class.Replace("-", "").ToLower() + this.male + "_background.jpg", UriKind.Relative)); 

            } 
        }

注意Uri的路徑寫法。

展示適配WP螢幕的HTML資訊

wp_ss_20141024_0009

這是優化過的利用內建WebBrowser展示的我左手華戒的tooltip資訊。如果直接用WebBrowser展示,該html會非常小,基本不可讀。

對於這個問題,可以通過設定viewport來解決。官方很有價值的一篇文章,請戳這裡:http://blogs.msdn.com/b/iemobile/archive/2011/01/21/managing-the-browser-viewport-in-windows-phone-7.aspx

放程式碼如下:

StringBuilder sb = new StringBuilder(); 

sb.AppendLine("<html>"); 
sb.AppendLine("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en-us\">"); 
sb.AppendLine("<head xmlns:og=\"http://ogp.me/ns#\" xmlns:fb=\"http://ogp.me/ns/fb#\">"); 
sb.AppendLine("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />");//這行很重要 
sb.AppendLine("<meta name=\"viewport\" content=\"width=370,minimum-scale=1\" />");//這行也很重要

sb.AppendLine("</head>"); 
sb.AppendLine("<body style=\"background-color:black\">"); 
sb.AppendLine(lines); //這就是D3返回給我的華戒的html描述資訊

sb.AppendLine("</body>"); 
sb.AppendLine("</html>");

 

並行獲取資料

每個英雄有14個裝備,每個裝備的資訊都要單獨獲取對應的圖片及tooltip。如果採用await GetItemByKey的方式,則14個裝備的圖片全部讀完,讀取時間至少在8秒之上。

利用Task的並行處理方式,我們的處理效率則大大提高了。

效能差的方式

list.Add("head", await GetItemByKey(person, "head")); 
list.Add("torso", await GetItemByKey(person, "torso")); 
list.Add("feet", await GetItemByKey(person, "feet")); 
list.Add("hands", await GetItemByKey(person, "hands")); 
list.Add("shoulders", await GetItemByKey(person, "shoulders")); 
list.Add("legs", await GetItemByKey(person, "legs")); 
list.Add("bracers", await GetItemByKey(person, "bracers")); 
list.Add("mainhand", await GetItemByKey(person, "mainHand")); 
list.Add("offhand", await GetItemByKey(person, "offHand")); 
list.Add("waist", await GetItemByKey(person, "waist")); 
list.Add("rightfinger", await GetItemByKey(person, "rightFinger")); 
list.Add("leftfinger", await GetItemByKey(person, "leftFinger")); 
list.Add("neck", await GetItemByKey(person, "neck")); 
list.Add("special", await GetItemByKey(person, "special"));
View Code

 

高效能的方式

Task<Item> head = GetItemByKey(person, "head"); 
Task<Item> torso = GetItemByKey(person, "torso"); 
Task<Item> feet = GetItemByKey(person, "feet"); 
Task<Item> hands = GetItemByKey(person, "hands"); 
Task<Item> shoulders = GetItemByKey(person, "shoulders"); 
Task<Item> legs = GetItemByKey(person, "legs"); 
Task<Item> bracers = GetItemByKey(person, "bracers"); 
Task<Item> mainhand = GetItemByKey(person, "mainHand"); 
Task<Item> offhand = GetItemByKey(person, "offHand"); 
Task<Item> waist = GetItemByKey(person, "waist"); 
Task<Item> rightfinger = GetItemByKey(person, "rightFinger"); 
Task<Item> leftfinger = GetItemByKey(person, "leftFinger"); 
Task<Item> neck = GetItemByKey(person, "neck"); 
Task<Item> special = GetItemByKey(person, "special");
View Code

//上面程式碼會立刻返回,只是定義了task而已。

await Task.WhenAll(head, torso, feet, hands, shoulders, legs, bracers, mainhand, offhand, waist, rightfinger, leftfinger, neck, special);

//這行程式碼會並行執行這14個任務,等待所有資訊完成。

list.Add("head", head.Result); 
list.Add("torso", torso.Result); 
list.Add("feet", feet.Result); 
list.Add("hands", hands.Result); 
list.Add("shoulders", shoulders.Result); 
list.Add("legs", legs.Result); 
list.Add("bracers", bracers.Result); 
list.Add("mainhand", mainhand.Result); 
list.Add("offhand", offhand.Result); 
list.Add("waist", waist.Result); 
list.Add("rightfinger", rightfinger.Result); 
list.Add("leftfinger", leftfinger.Result); 
list.Add("neck", neck.Result); 
list.Add("special", special.Result);
View Code

//14個任務的結果加入到list中。

判斷Json片段是否為空

private async Task<List<Skill>> GetSkillList(dynamic skills) 
{ 
    List<Skill> skillList = new List<Skill>(); 
    foreach (var skill in skills) 
    { 
        if (skill.ToString()!="{}")//skill不是null,如果不存在,則對應{}

 

釋出到商店

WebBrowser的許可權

如果應用中用了WebBrowser,則需要指定相關許可權。具體位置在:project-Properties-WMAppManifest.xml-Capabilities中,要check上ID_CAP_WEBBROWSERCOMPONENT

SL8.1版本的釋出

對於SL8.1版本的WP應用,Package.appxmanifest檔案的內容,要做修改。我這個程式是從8.0升級上來的,所以還是SL核心的版本,如果是一個新建的8.1WP應用,則無需做下面的修改。

  • <publisherDiaplsyName>與開發商名字一致
  • <Identity>下面的Name要與你在dev center中預留的名字一致
  • <Publisher>與dev center中的開發商GUID一致

Deploy error: Package could not be registered

官方論壇上有這個解答:https://social.msdn.microsoft.com/Forums/en-US/da89f2ee-03b6-43ed-aa21-97ef091798c9/deploy-error-package-could-not-be-registered?forum=WindowsPhonePreviewSDK

8.1的系列官方blog

http://blogs.msdn.com/b/thunbrynt/archive/2014/03/31/windows-phone-8-1-for-developers-overview.aspx

相關文章