前言
想當年Team有無數人在玩大菠蘿,我被忽悠進來做肉盾,選了蠻子,從1.0開始,經歷了103、105、108、2.0、2.1。這個遊戲對我最大的幫助是學習了不同的技術,比如XAML、比如xcode開發、比如WP的開發。
這篇文章不會step by step的介紹如何從0開始做WP開發,我會重點記錄開發過程中要注意的坑,以及一些釋出上架時的注意事項。
文中大部分內容對於熟悉XAML的人來講,可能過於簡單。放在這裡,希望對初學者有個幫助,尤其是如我這樣做winform開發的人。
先上幾張圖,
官方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>
這裡分為兩部分,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資訊
這是優化過的利用內建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"));
高效能的方式
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");
//上面程式碼會立刻返回,只是定義了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);
//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
8.1的系列官方blog
http://blogs.msdn.com/b/thunbrynt/archive/2014/03/31/windows-phone-8-1-for-developers-overview.aspx