Silverlight網(wǎng)游可媲美客戶(hù)端的核心組件之一便是資源動(dòng)態(tài)下載。Silverlight為我們提供了穩健且豐富的WebClient,雖然它本身無(wú)法實(shí)現并發(fā)操作,然而憑借其強大的特性我們可輕松編寫(xiě)出一款資源下載器,滿(mǎn)足網(wǎng)游設計中各式各樣的資源獲取需求。
如何構建這條資源下載通道是游戲設計之初我們應該考慮的問(wèn)題。你希望它是一條普普通通的馬路人來(lái)車(chē)往隨意穿行游走;還是希望它是一條標示清晰、分道合理、次序井然的高速公路?
當然,你會(huì )說(shuō)越快越好,最好永遠不堵車(chē),路窄時(shí)就一個(gè)挨一個(gè)慢慢走;路寬了便分道揚鑣。
我們不妨將下載通道比作道路,能并排幾輛車(chē)通過(guò)即為“并行模式”;只能一輛一輛的通過(guò)則為“串行模式”。很明顯,并行的速度要比串行快得多,不過(guò)消耗和設計方面則要比串行多且復雜,各有利弊。另外,下載通道如果僅是單純的串行,則速度太慢且浪費資源;而如果僅是單純的并行,過(guò)多的并發(fā)時(shí)快時(shí)慢、有大有小極易導致UI卡死以及下載堵塞。
再次回到我們的高速公路上,不知你是否有過(guò)這樣的體會(huì ),行駛時(shí)常會(huì )見(jiàn)到很多大貨車(chē)以60公里/小時(shí)的速度順次行駛在慢車(chē)道上,而小車(chē)們則通常會(huì )以80-100公里/小時(shí)不等的速度在快車(chē)道與超車(chē)道上彼此間穿行。
人類(lèi)自身的活動(dòng)規律來(lái)檢驗理論的合理性非常具有參考價(jià)值,由此可見(jiàn)兼具“串行”與“并行”的游戲資源動(dòng)態(tài)下載器才是我們所追求的設計目標。比如游戲中隨時(shí)可能大量涌現的動(dòng)畫(huà)(animation)資源,我們可將它們放在串行隊列中依次獲??;而角色裝備、地圖區塊等更迫切需要呈現給玩家的資源則可放在并行通道中并發(fā)下載。
依據以上分析,思路清晰后僅僅只需百來(lái)行代碼便可輕松編寫(xiě)出一套完整的資源動(dòng)態(tài)下載組件- SerialDownloader和ParallelDownloader,它們共用一個(gè)完成資源表,且串行下載集成了優(yōu)先機制(DownloadPriority),并行下載也根據需要封裝了并行隊列模式(QueueParallelDownloader):
/// <summary>
/// 下載器基類(lèi)
/// </summary>
public class DownloadBase {
protected readonly static List<string> loadedUri = new List<string>();
/// <summary>
/// 獲取已下載完成的地址
/// </summary>
public static List<string> LoadedUri { get { return loadedUri; } }
/// <summary>
/// 下載失敗(錯誤)次數
/// </summary>
public static int Error { get; protected set; }
}

/// <summary>
/// 下載優(yōu)先級
/// </summary>
public enum DownloadPriority {
/// <summary>
/// 最高
/// </summary>
Highest = 0,
/// <summary>
/// 高
/// </summary>
High = 1,
/// <summary>
/// 普通
/// </summary>
Normal = 2,
/// <summary>
/// 低
/// </summary>
Low = 3,
/// <summary>
/// 最低
/// </summary>
Lowest = 4,
}
/// <summary>
/// 串行資源下載器
/// </summary>
public class SerialDownloader : DownloadBase {
/// <summary>
/// 資源下載完成
/// </summary>
public static event OpenReadCompletedEventHandler OpenReadCompleted;
/// <summary>
/// 資源下載進(jìn)度中觸發(fā)
/// </summary>
public static event DownloadProgressChangedEventHandler DownloadProgressChanged;
/// <summary>
/// 當前進(jìn)度百分比
/// </summary>
public static int ProgressPercentage { get; private set; }
static WebClient webClient = null;
readonly static List<string> loadingUri = new List<string>();
readonly static List<string> waitingUri = new List<string>();
/// <summary>
/// 獲取當前正在下載的地址
/// </summary>
public static List<string> LoadingUri { get { return loadingUri; } }
/// <summary>
/// 獲取等待下載地址隊列
/// </summary>
public static List<string> WaitingUri { get { return waitingUri; } }
/// <summary>
/// 為Image圖片控件設置圖像源
/// </summary>
/// <param name="image">目標圖片</param>
/// <param name="uri">圖像源地址</param>
/// <param name="isWaiting">是否等待下載完成后再賦值</param>
public static void SetImageSource(Image image, string uri, DownloadPriority priority, bool isWaiting) {
if (loadedUri.Contains(uri)) {
image.Source = GlobalMethod.GetWebImage(uri);
} else {
image.Source = null;
AddUri(uri, priority);
if (isWaiting) {
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(2000) };
EventHandler handler = null;
timer.Tick += handler = (s, e) => {
if (loadedUri.Contains(uri)) {
timer.Stop();
timer.Tick -= handler;
image.Source = GlobalMethod.GetWebImage(uri);
}
};
timer.Start();
}
}
}
/// <summary>
/// 添加預備下載地址
/// </summary>
/// <param name="uri">圖像源地址</param>
public static void AddUri(string uri, DownloadPriority priority) {
if (!waitingUri.Contains(uri)) { waitingUri.Insert((int)(((int)priority / 4d) * waitingUri.Count), uri); }
if (loadingUri.Count == 0) {
webClient = new WebClient();
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
webClient.OpenReadAsync(new Uri(GlobalMethod.WebPath(uri), UriKind.Relative), uri);
loadingUri.Add(uri);
}
}
static void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) {
WebClient wc = sender as WebClient;
wc.DownloadProgressChanged -= webClient_DownloadProgressChanged;
wc.OpenReadCompleted -= webClient_OpenReadCompleted;
string uri = e.UserState.ToString();
if (e.Error != null) {
//斷網(wǎng)處理,5秒后重試
Error++;
GlobalMethod.SetTimeout(delegate {
loadingUri.Remove(uri);
AddUri(uri, DownloadPriority.Highest);
}, 5000);
} else {
loadingUri.Remove(uri);
waitingUri.Remove(uri);
loadedUri.Add(uri);
if (waitingUri.Count > 0) { AddUri(waitingUri[0], DownloadPriority.Highest); }
if (OpenReadCompleted != null) { OpenReadCompleted(sender, e); }
}
}
static void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) {
ProgressPercentage = e.ProgressPercentage;
if (DownloadProgressChanged != null) { DownloadProgressChanged(sender, e); }
}
}

/// <summary>
/// 并行資源下載器
/// </summary>
public sealed class ParallelDownloader : DownloadBase {
/// <summary>
/// 資源下載進(jìn)度中觸發(fā)
/// </summary>
public event DownloadProgressChangedEventHandler DownloadProgressChanged;
/// <summary>
/// 資源下載完成
/// </summary>
public event OpenReadCompletedEventHandler OpenReadCompleted;
/// <summary>
/// 當前進(jìn)度百分比
/// </summary>
public static int ProgressPercentage { get; private set; }
readonly static List<string> loadingUri = new List<string>();
readonly static List<string> waitingUri = new List<string>();
/// <summary>
/// 獲取當前正在下載的地址
/// </summary>
public static List<string> LoadingUri { get { return loadingUri; } }
/// <summary>
/// 獲取等待下載地址隊列
/// </summary>
public static List<string> WaitingUri { get { return waitingUri; } }
/// <summary>
/// 下載資源文件
/// </summary>
/// <param name="uri">資源相對地址<</param>
/// <param name="userToken">資源參數</param>
/// <param name="waitingTime">如果正在被下載,等待檢測時(shí)間(單位:毫秒)</param>
public void OpenReadAsync(string uri, object userToken, bool isWaiting, int waitingTime) {
if (loadedUri.Contains(uri)) {
Download(uri, userToken);
} else {
if (loadingUri.Contains(uri)) {
//假如該資源正被下載中,則需要等待,每隔1秒檢測一次是否已下載完成
if (isWaiting) {
DispatcherTimer timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(waitingTime) };
EventHandler handler = null;
timer.Tick += handler = (s, e) => {
if (loadedUri.Contains(uri)) {
timer.Stop();
timer.Tick -= handler;
Download(uri, userToken);
}
};
timer.Start();
}
} else {
if (!waitingUri.Contains(uri)) { waitingUri.Add(uri); }
loadingUri.Add(uri);
Download(uri, userToken);
}
}
}
/// <summary>
/// 開(kāi)始下載
/// </summary>
/// <param name="uri">資源相對地址</param>
/// <param name="userToken">資源參數</param>
void Download(string uri, object userToken) {
OpenReadCompletedEventHandler openReadCompletedHandler = null;
DownloadProgressChangedEventHandler progressChangedHandler = null;
WebClient webClient = new WebClient();
webClient.DownloadProgressChanged += progressChangedHandler = (s, e) => {
ProgressPercentage = e.ProgressPercentage;
if (DownloadProgressChanged != null) { DownloadProgressChanged(this, e); }
};
webClient.OpenReadCompleted += openReadCompletedHandler = (s, e) => {
WebClient wc = s as WebClient;
wc.DownloadProgressChanged -= progressChangedHandler;
wc.OpenReadCompleted -= openReadCompletedHandler;
if (e.Error != null) {
//斷網(wǎng)處理,5秒后重試
Error++;
GlobalMethod.SetTimeout(delegate {
Download(uri, userToken);
}, 5000);
} else {
waitingUri.Remove(uri);
loadingUri.Remove(uri);
if (!loadedUri.Contains(uri)) { loadedUri.Add(uri); }
if (OpenReadCompleted != null) { OpenReadCompleted(this, e); }
}
};
webClient.OpenReadAsync(new Uri(uri, UriKind.Relative), userToken);
}
}
經(jīng)過(guò)測試,它們的運行非常穩健,當然這也少不了WebClient的功勞,尤其體現在其中的抗網(wǎng)速阻塞、抗斷網(wǎng)處理機制(5秒重試)。大家不妨在游戲資源下載過(guò)程中打開(kāi)迅雷并全速全線(xiàn)程下載上傳一堆東西,或者直接將網(wǎng)卡禁用,此時(shí)觀(guān)察游戲中資源下載狀況;然后關(guān)閉迅雷并啟用網(wǎng)卡再觀(guān)察所有的一切下載均立刻恢復正常。同時(shí)也證明了,規劃合理的串行和并行如同設計一條完美的高速公路,它是成就高性能Silverlight網(wǎng)游引擎所不可或缺的功能模塊之一。
沒(méi)錯,Silverlight在網(wǎng)游開(kāi)發(fā)中擁有如此優(yōu)越的特性,不久的將來(lái)大家將看到更多的網(wǎng)游奇跡因Silverlight而誕生,一起見(jiàn)證。