海量的美術、龐大而繁雜的人員與資源配備使得網游和端游開發難度系數高居不下;移動開發時代的來臨為游戲設計師們提供了第三條綠色通道,這是一次愈加趨近夢想的迅捷契機。
作為一個專情的人,深愛着C#,毋庸置疑的原因;於是,我也愛上了Windows Phone,愛上了C#在Silverlight.XNA中放盪的游走;因為它的存,使得代碼移植在頁游、端游與手游之間顯得格外暢快淋漓。
今天,打開的不僅是一扇門,更是通往美麗新世界的嶄新道路;握緊了,戰士,你手中無比鋒利的C#,鞭笞吧!XAML,神秘的游戲世界正等待您來探索。
輕輕的,我步入了這個陌生而又激動的新聖域,困惑悄然而生:該如何開啟Windows Phone游戲開發這個潘多拉之盒?
Sprite,精靈,永恆不變的游戲靈魂鑄就者,生命萬象之密匙;從精靈的起源去探究創世之初尤能緬懷我的虔誠。
翻開上帝之書MSDN,古老的文字向人類印示着Windows Phone游戲精靈的兩種主要創生方式:Silverlight的UElement(所有UI控件的基類)和XNA中的Texture2D(2D紋理)。性能方面,后者絕對專業;不過相對於效率而言,前者則更為出色。彷徨中的我恍然大悟,其實一切真想早已被遠古神器Visual Studio 2010暴露得一覽無余,抹去歲月的塵土,赫然印着:基於Silverlight與XNA的無縫集成打造最完美之解決方案:
通過Silverlight(Blend)制作游戲界面,XNA實現游戲對象的繪制,雙管齊下。開發者不僅能夠延續傳統.NET基於事件驅動的低耦合編程模式,同時也能享受到XNA高性能的圖形繪制與渲染;正如MSDN所述,Silverlight與XNA的完美結合帶來的是開發“效率”與“性能”質的飛躍:
由此我們也不難看出,目前的Silverlight不論是作為瀏覽器插件,還是Windows Phone的主要開發工具,其與XNA融合構建.NET開發者最熟悉的事件驅動架構已成為主流趨勢;本節作為系列Demo向Windows Phone平台移植的第一步,我將向大家詳細講解如何搭建游戲的主體框架。
一)配置開發環境
二)新建游戲項目
打開Visual Studio,點擊文件->新建->項目->選擇模板 Silverlight for Windows Phone中的“Windows Phone Silverlight和XNA應用程序”,這里我取名叫SLXnaGame:
三)分析解決方案
在解決方案管理器SLXnaGame項目中第一眼看到.xaml頓時淚流滿面,無比熟悉的App.xaml以及主頁面MainPage.xaml和游戲場景頁面GamePage.xaml讓所有Silverlight游戲開發者倍感親切:
四)核心框架搭建
新項目默認為我們打開了MainPage.xaml的前端部分,除了左邊垂直擺放着一個偌大的Windows Phone模型外,右邊那一長串的xaml再熟悉不過了。由於SLG游戲以水平方向呈現效果更好,因此我們不妨對這個所見即所得的展示窗口進行一些調整,並以一張很炫的圖片作為游戲的封面:
如上圖,xaml代碼中我們可以通過SupportedOrientations="Landscape" Orientation="LandscapeLeft" 設置Windows Phone模擬器橫向顯示;當然,如果不需要觀看預覽(比如后面講到的GamePage),我們也可以在后台cs文件中編寫this.SupportedOrientations = SupportedPageOrientation.Landscape; 實現同樣的效果。另外,游戲中所有Silverlight控件所用到的圖片資源均統一存放在SLXnaGame項目的(新建)Resource文件夾中,這樣我們便可通過如下xaml代碼實現圖片裝載:Source="/SLXnaGame;component/Resource/UI/FrontPage.jpg" 除此之外,為了構建更為靈活的游戲框架,同樣可以仿造之前Silverlight游戲教程的做法,編寫一個名為Global.cs的全局輔助類存放於SLXnaGameLib(控件類庫)項目中:

/// <summary>
/// 全局(數據和方法)
/// </summary>
public static class Global {
/// <summary>
/// 主項目名
/// </summary>
static string ProjectName = Application.Current.GetType().Assembly.FullName.Split( ' , ')[ 0];
/// <summary>
/// 項目Resource資源路徑
/// </summary>
public static string ProjectPath( string uri) {
return string.Format( " /{0};component/Resource/{1} ", ProjectName, uri);
}
/// <summary>
/// 獲取項目Resource中的位圖
/// </summary>
/// <param name="uri"> 路徑 </param>
/// <returns></returns>
public static BitmapImage GetImage( string uri) {
return new BitmapImage( new Uri(ProjectPath(uri), UriKind.Relative));
}
}
}
注意,這里需要添加對System.Windows.dll動態鏈接庫的引用:右鍵點擊SLXnaGameLib項目中的引用->添加引用->選擇System.Windows
到此為止,游戲初始界面制作完畢,按F5調試運行;正常情況下我們將看到前面精心設計好的游戲初始畫面,此時細心的朋友肯定會注意到一個特殊的警告提示:
由於Silverlight與XNA的兼容模式是從7.1開始才有的新模板,其本質由7.0衍生而來。編譯后會發現警告提示“無法引用項目‘SLXnaGameLib’”,但實際上SLXnaGame項目還是能夠使用的,如果出現由於兼容問題導致可能出現的無法找到命名空間或類名,可刪除對這個SLXnaGameLib的引用后重新再引用一次即可永久解決問題。此處稍作說明提醒大家無需緊張,框架之間的協調問題在后續版本中將進一步完善。
回到正題,接下來我們點擊“點擊開始”這個閃爍的按鈕便會跳轉到項目默認自帶的第二個頁面:GamePage.xaml。對於新手來說,MainPage.xaml是如何通過點擊Button實現跳轉的呢?機關就在MainPage.xaml左邊的小箭頭上:
熟悉Silverlight的朋友都清楚,Silverlight中的用戶控件(頁面)都是以兩個文件partial的形式存在:前台(.xaml)和后台(.cs)。常規的做法是通過在前台注冊Button的Click="Button_Click"事件,並於后台編寫相應代碼實現頁面之間的點擊跳轉功能。
private void Button_Click( object sender, RoutedEventArgs e) {
NavigationService.Navigate( new Uri( " /GamePage.xaml ", UriKind.Relative));
}
接下來我們將目標轉向第二個頁面,首先打開GamePage.xaml,赫然寫着“不需要XAML內容……”,其實我想說:…哥還是留個Canvas吧,哈哈。
繼續打開GamePage.cs,終於來到了我們游戲框架的核心部分。默認的代碼有些凌亂,稍做調整后我們不妨先對比一下它與標准的XNA游戲中的Game1.cs有什么區別:
做過XNA開發的朋友是否有種豁然開朗的感覺 (新手朋友們可以參考一下XNA的游戲開發機制)。把Silverlight.XNA(以下簡稱SL.XNA)中的OnNavigatedTo()和OnNavigatedFrom()分別看做是純XNA中的LoadContent()和UnloadContent(),兩者相似度幾乎一模一樣,只是SL.XNA模式通過一個timer實現了游戲的主循環;注意了,這個timer可是XNA線程框架中的GameTimer,因此我們無需擔憂其性能方面的問題:
至於繪圖方面,SL.XNA和純XNA在代碼方面幾乎是無縫移植。比如我們希望繪制字體,完全可以一字不差的照搬現有的XNA教程中的字體示例;而音樂和音效的播放則同樣,將mp3或wav等音頻資源加入到SLXnaGameLibContent資源項目中,然后編寫一樣的代碼實現一模一樣的功能(注意,mp3和wav的資源存放形式不同,播放方式亦不同):

Song song;
// 構造函數
public MainPage() {
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded( object sender, EventArgs e) {
content = (Application.Current as App).Content;
song = content.Load<Song>( " Media/MySong ");
MediaPlayer.Play(song);
}
// 簡單的按鈕單擊事件處理程序可使我們轉至第二頁
private void Button_Click( object sender, RoutedEventArgs e) {
sound = content.Load<SoundEffect>( " Audio/MyAudio ");
sound.Play();
NavigationService.Navigate( new Uri( " /GamePage.xaml ", UriKind.Relative));
}
到此有朋友要問了:僅僅是調用了XNA中的字體和音樂,與純XNA又有何區別?Silverlight控件呈現問題甚至還不需要字體呢,干嘛非得多次一舉給XNA加個Silverlight殼?
別急,接下來便是Silverlight與XNA交互實現的關鍵:UIElementRenderer
就像本文開頭所述那樣,完美的交互必須是Silverlight的UElement和XNA的Texture2D之間的非跨線程交互操作,大家不妨先看看最終的實現代碼:
public GamePage() {
InitializeComponent();
this.SupportedOrientations = SupportedPageOrientation.Landscape;
this.LayoutUpdated += GamePage_LayoutUpdated;
....
}
/// <summary>
/// 允許頁面繪制自身。
/// </summary>
private void Draw( object sender, GameTimerEventArgs e) {
graphicsDevice.Clear(Color.CornflowerBlue);
elementRenderer.Render(); // 通過elementRenderer呈現Silverlight中的UElement
spriteBatch.Begin();
spriteBatch.Draw(elementRenderer.Texture, Vector2.Zero, Color.White); // 通過XNA的形式將elementRenderer整體繪制出來
spriteBatch.End();
}
/// <summary>
/// 創建一個可以被XNA繪制的Silverlight-UI展示器UIElementRenderer
/// </summary>
void GamePage_LayoutUpdated( object sender, EventArgs e) {
if (ActualWidth > 0 && ActualHeight > 0 && elementRenderer == null) {
elementRenderer = new UIElementRenderer( this, ( int)ActualWidth, ( int)ActualHeight);
}
}
其實,UIElementRenderer的原理便是將Silverlight中的UElement對象以XNA的繪制形式在Draw()方法中畫出來,真想大白:不論是Silverlight的東西還是XNA的東西,所有能看得到的對象最終都將以XNA的形式繪制出來,這也是成就SL.XNA模式得以完美兼具“效率”與“性能”的根本原因。
接下來我們也來俗一把,分別用Silverlight的TextBlock和XNA的Font編寫Hello Game:
// 設置圖形設備的共享模式以啟用 XNA 呈現
graphicsDevice.SetSharingMode( true);
// 創建可以用來繪制紋理的新 SpriteBatch。
spriteBatch = new SpriteBatch(graphicsDevice);
// TODO: 使用 this.content 在此處加載游戲內容
textBlock = new TextBlock() { Text = " Hello Game, I,m Silverlight TextBlock " };
LayoutRoot.Children.Add(textBlock); // 將textBlock添加進Canvas畫布中
Canvas.SetLeft(textBlock, 10); Canvas.SetTop(textBlock, 20); // 設置textBlock在畫布中的絕對位置
font = content.Load<SpriteFont>( " Font/MyFont ");
timer.Start();
base.OnNavigatedTo(e);
}
/// <summary>
/// 允許頁面繪制自身。
/// </summary>
private void Draw( object sender, GameTimerEventArgs e) {
graphicsDevice.Clear(Color.CornflowerBlue);
elementRenderer.Render(); // 通過elementRenderer呈現Silverlight中的UElement
spriteBatch.Begin();
spriteBatch.Draw(elementRenderer.Texture, Vector2.Zero, Color.White); // 通過XNA的形式將elementRenderer整體繪制出來
spriteBatch.DrawString(font, " Hello Game, I,m XNA Font ", new Vector2( 20, 55), Color.Yellow); // 第三個參數代表繪制的絕對位置
spriteBatch.End();
}
默認情況下,后Draw的對象顯示在最頂層;當然,如果你想動態更改他們之間的層級深度關系,可以使用Draw方法的其他形態,比如:
值得一提的是,如上面代碼所示UIElementRenderer對象創建於GamePage_LayoutUpdated事件中,它的第一個UIElement類型參數為this,即指整個GamePage頁面(UserControl):
試想一下,如果換成是一個Image或者ListBox等控件呢?高度自由的UIElementRenderer給了我們SL.XNA游戲開發無限遐想空間,不是嗎?
至此,資源布局及代碼結構這些毛坯級也是最核心的框架構建完畢,無論您是單純的Silverlight開發者,或者XNA游戲開發者,亦或者兩者通殺型,這個框架都能為你提供可無限拓展的高性能空間。下一節,我將繼續為大家深入講解SL.XNA中的控件交互,關注哦,^ ^。
本節源碼下載地址:SLXnaGame1.zip
手記小結:本節非常詳細的為大家講解了如何從0開始一步步搭建基於Silverlight.XNA游戲框架;新手、老手,又或者你擅長的是Silverlight、WPF或XNA;對於初出茅廬的Windows Phone開發者來說這都是一篇開卷有益的啟蒙之章,包括后續的更多章節,旨在通過自身的開發經歷讓朋友們高效率的掌握Windows Phone開發中關於C#、xaml、Silverlight、XNA等多方面知識。畢竟,一個人的能力與時間極其有限,卓越而經典的游戲需要更多的開發者參與進來,相信我們的共同努力可以鑄成屬於中國人輝煌的游戲江山!