[Teaching] [Silverlight] 用 Silverlight 一起開發Kuso小遊戲《捏氣泡》- 程式人員篇
楔子
接下來,我們會帶領大家一起製作一款如下圖的《捏氣泡》遊戲:
我們採用的技術平台及工具如下:
1. Silverlight 4
2. Visual Studio 2010
3. Blend 4
《圖說:捏氣泡最終完成圖》
本文是針對程式開發人員所撰寫。
我和Bear合作的方式,是由我們先共同討論出設計的大方向,接著由我先透過Visualstudio製作出骨架,再交給Bear使用Blend套上介面。
在本文中,程式人員會學到下列遊戲開發上的相關技巧
- 迴圈的建立&用法
- 圖型顯示的方法
- 播放音效的方法
- 相依屬性的建立
- 封裝屬性的建立
- 外部儲存區使用
- OOB的使用
事前準備
整款《捏氣泡》遊戲架構如下,共有4頁以及一個氣泡的Usercontrol,另外需要準備3款氣泡的圖,以及一個氣泡破裂的音效。
StartPage.xaml:遊戲開始頁。按一下會開始遊戲。
PlayPage.xaml:遊戲進行頁。
InstallPage.xaml:遊戲安裝頁。可安裝《捏氣泡》到本機桌面執行。
EndPage.xaml:遊戲結束頁。可統計分數,返回首頁或重新遊戲。
BubbleControl.xaml:氣泡的Usercontrol。
Step 1. 設定換頁
由於整款遊戲有4頁,會依照不同狀態進行切換,所以我們先在app.xaml加入一些程式碼,可以提供方便的換頁功能,這並不是SL3.0的導覽機制,只是單純將頁面當成Usercontrol加入,以達到換頁的效果。
app.xam.cs
//換頁 Grid navigationUI = new Grid(); public static void NavigateToPage(UserControl page) { App application = (App)Application.Current; application.navigationUI.Children.Clear(); application.navigationUI.Children.Add(page); } private void Application_Startup(object sender, StartupEventArgs e) { //this.RootVisual = new PlayPage(); this.RootVisual = navigationUI; navigationUI.Children.Add(new InstallPage()); }
這樣未來在page裡,就可以用下面非常簡單的code進行換頁的動作
app.xam.cs
App.NavigateToPage(new PlayPage());
另外我們建立兩個變數,一個拿來存放每一局的消耗時間,另一個拿來存放目前已經累積捏破的氣泡
app.xam.cs
//計時 public static TimeSpan UseTime { set; get; } //計數量 public static int TotalBroken { set; get; }
Step 2. 設定外部儲存區
由於我們希望氣泡數量可以持續累積,而不是在遊戲關閉後,紀錄就消失,因此我們會用到Silverlight中的IsolatedStorage。讓應用程式在執行前,讀出先前紀錄,關閉前,再將記錄存回去。
app.xam.cs
private void Application_Startup(object sender, StartupEventArgs e) { //this.RootVisual = new PlayPage(); this.RootVisual = navigationUI; navigationUI.Children.Add(new InstallPage()); //讀取LuIsoSeetings ReadIsoSeetings("TotalBroken"); } private void Application_Exit(object sender, EventArgs e) { //儲x存sIsoSeetings SaveIsoSeetings("TotalBroken"); } private void ReadIsoSeetings(String Key) { //讀取LuIsolatedStorageSettings IsolatedStorageSettings IsoStorageSetting = IsolatedStorageSettings.ApplicationSettings; string stringIsoStorageSet = null; bool IsIsoStorageSetError = false; try { stringIsoStorageSet = IsoStorageSetting[Key].ToString(); } catch (System.Collections.Generic.KeyNotFoundException ex) { IsIsoStorageSetError = true; } if (IsIsoStorageSetError) { IsoStorageSetting.Add(Key, TotalBroken); IsoStorageSetting.Save(); } else { TotalBroken = int.Parse(stringIsoStorageSet); } } private static void SaveIsoSeetings(String Key) { //存入LuIsolatedStorageSettings IsolatedStorageSettings IsoStorageSetting = IsolatedStorageSettings.ApplicationSettings; IsoStorageSetting[Key] = TotalBroken.ToString(); IsoStorageSetting.Save(); }
製作氣泡的UserControl
《捏氣泡》遊戲中,我們會製做一個氣泡的UserControl,並且將封裝的屬性和相依屬性寫在這個UserControl內。
氣泡的功能如下:
1. 本身有一個持續放大縮小的視覺特效,可以讓設計人員自行決定是否要開啟
2. 可以讓設計人員自行設定氣泡顏色
3. 按下氣泡會發出聲音
4. 氣泡破掉有兩種效果
Step 1. 設定xaml:
3段Storyboard,一段是播放特效,2段是2種破裂的狀態。
效果之後會交給設計人員處理。
<UserControl.Resources> <Storyboard x:Name="sb_Effect" /> <Storyboard x:Name="sb_BrokenOne"/> <Storyboard x:Name="sb_BrokenTwo"> </UserControl.Resources> 3個控制項,一張氣泡正常的圖片,
2張氣泡破裂的圖片,一個撥放音效的控制項。
控制項內容之後會交給設計人員處理。
<Grid x:Name="LayoutRoot" > <Image x:Name="Image_Normal" /> <Image x:Name="Image_BrokenTwo" /> <Image x:Name="Image_BrokenOne" /> <MediaElement x:Name="mediaElement" /> </Grid>
Step 2. 設定變數:(從2種破裂效果中,隨機抽出1種)
BubbleControl.xaml.cs
private Random _rnd = new Random((int)DateTime.Now.Ticks);
Step 3. 設定屬性:
BubbleControl.xaml.cs
private bool _isEffect; //是否要開啟放大縮小特效 private bool _isBroken; //氣泡是否已經破裂 public int EffectDelayTime { set; get; } //放大縮小特效的延遲時間 public bool IsBroken { set { if (!IsBroken) { _isBroken = value; SetBroken(value); } } get { return _isBroken; } } public bool IsEffect { set { _isEffect = value; SetEffect(value); } get { return _isEffect; } }
Step 4. 設定氣泡破掉:
這段程式碼將會從2段Storyboard中,抽出一段來撥放。
也就是一段Storyboard是其中一種破裂特效,另一段則是另一種破裂特效。
在這裡,其實是透過Storyboard切換3張圖片的Visibility
BubbleControl.xaml.cs
private void SetBroken(bool value) { if (value) { if (_rnd.Next(2) == 0) { sb_BrokenOne.Begin(); } else { sb_BrokenTwo.Begin(); } SetEffect(false); PlaySound(); AddTotalBroken(); } else { sb_BrokenOne.Stop(); sb_BrokenTwo.Stop(); } }
Step 5. 設定特效:
為了讓特效看起來更自然,每顆氣泡有閃動的效果,也避免特效的觸發時間都一樣,所以加入了延遲時間,可以讓設計人員自行設定。
BubbleControl.xaml.cs
private void SetEffect(bool value) { if (value) { sb_Effect.BeginTime = TimeSpan.FromSeconds(EffectDelayTime); sb_Effect.Begin(); } else { sb_Effect.Stop(); } }
Step 6. 氣泡破掉時播放音效
BubbleControl.xaml.cs
public void PlaySound() { mediaElement.Stop(); mediaElement.Play(); }
Step 7. 增加破裂氣泡數
只要氣泡破裂後,就做一次記錄,將氣泡加入累積總數。
BubbleControl.xaml.cs
private void AddTotalBroken() { App.TotalBroken++; }
Step 8. 設定相依屬性
設定相依屬性的主要目的是讓UserControl的屬性,未來在PlayPage中也可以進行設定。
因此,我們將設定ImageSource相依屬性,好讓設計人員未來可以自行更換圖片。
因為有3款圖片(正常、破裂1、破裂2),因此我們將設定3款相依屬性,方便設計人員未來自行使用。
BubbleControl.xaml.cs
//相依屬性 public static readonly DependencyProperty ImageSourceNormalProperty = DependencyProperty.Register("ImageSourceNormal", typeof(ImageSource), typeof(BubbleControl), null); public static readonly DependencyProperty ImageSourceBrokenOneProperty = DependencyProperty.Register("ImageSourceBrokenOne", typeof(ImageSource), typeof(BubbleControl), null); public static readonly DependencyProperty ImageSourceBrokenTwoProperty = DependencyProperty.Register("ImageSourceBrokenTwo", typeof(ImageSource), typeof(BubbleControl), null); public ImageSource ImageSourceNormal { get { return (ImageSource)this.GetValue(ImageSourceNormalProperty); } set { this.SetValue(ImageSourceNormalProperty, value); } } public ImageSource ImageSourceBrokenOne { get { return (ImageSource)this.GetValue(ImageSourceBrokenOneProperty); } set { this.SetValue(ImageSourceBrokenOneProperty, value); } } public ImageSource ImageSourceBrokenTwo { get { return (ImageSource)this.GetValue(ImageSourceBrokenTwoProperty); } set { this.SetValue(ImageSourceBrokenTwoProperty, value); } }
製作InstallPage
我們希望這款遊戲,是從瀏覽器安裝到桌面後,才可以運作,因此我們會製作一個InstallPage,作為起始頁。當判斷玩家是運作在桌面後,才導到StartPage。
Step 1. 設定OOB
《圖說:在專案上按右鍵,選擇屬性》
《圖說:開啟OOB並進行相關設定》
Step 2. 建立判斷
InstallPage.xaml
<TextBlock x:Name="tb_Install"></TextBlock> InstallPage.xaml.cs public InstallPage() { InitializeComponent(); this.Loaded += new RoutedEventHandler(InstallPage_Loaded); Application.Current.InstallStateChanged += new EventHandler(Current_InstallStateChanged); } void Current_InstallStateChanged(object sender, EventArgs e) { //throw new NotImplementedException(); if (Application.Current.InstallState == InstallState.Installed) { tb_Install.Text = "Installed"; } } void InstallPage_Loaded(object sender, RoutedEventArgs e) { //throw new NotImplementedException(); if (Application.Current.IsRunningOutOfBrowser) { App.NavigateToPage(new StartPage()); } if (Application.Current.InstallState == InstallState.Installed) { tb_Install.Text = "Installed"; } } private void tb_Install_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { try { Application.Current.Install(); } catch { MessageBox.Show("Game has been installed!"); } }
製作StartPage
Step 1. 建立觸發事件
StartPage非常簡單,只是一個引導頁,當按下開始的文字後,就會導到PlayPage
StartPage.xaml
<TextBlock x:Name="tb_Start" MouseLeftButtonDown="tb_Start_MouseLeftButtonDown" Text="Start" /> StartPage.xaml.cs private void tb_Start_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { App.NavigateToPage(new PlayPage()); }
製作PlayPage
Step 1. 建立迴圈
建立迴圈的方式可參考下列文章。
建立迴圈的目的,是可以方便我們進行一些偵測,在《捏氣泡》遊戲中,迴圈有兩個目的。
1. 更新遊戲時間
2. 偵測氣泡是不是已經全部被捏破
所以遊戲的迴圈,是會建立在PlayPage.xaml這一個頁面
使用StoryBoard可以建立一個每0.033秒會偵測一次的迴圈,如果以遊戲開發的術語來說,就是每秒30格的遊戲。
PlayPage.xaml
<UserControl.Resources> <Storyboard x:Name="sb_Timer" Duration="0:0:0.033" Completed="sb_Timer_Completed"/> </UserControl.Resources> PlayPage.xaml.cs bool IsGameLoopRunning = true; private void sb_Timer_Completed(object sender, EventArgs e) { //GameLoop ShowUseTime(); //更新遊戲時間 CheckUcState(); //偵測氣泡是不是已經全部被捏破 if (IsGameLoopRunning) { sb_Timer.Begin(); } }
有時候在CheckUcState()後,遊戲迴圈就可以停止了。因此,另外會宣告一個變數IsGameLoopRunning,用來判斷遊戲迴圈是不是必須持續運作。
Step 2. 迴圈的工作-更新遊戲時間
《捏氣泡》會判斷玩家捏完一輪氣泡的時間,而此時間,也會同步在畫面上顯示。
因此,我將這項工作放在迴圈裡,在迴圈運作時,一併處理。
PlayPage.xaml.cs
<TextBlock x:Name="tb_UseTime" Text="00:00:00"/> PlayPage.xaml.cs //宣告變數-計時 DateTime time_start = DateTime.Now; DateTime time_end; TimeSpan ts; private void ShowUseTime() { time_end = DateTime.Now; ts = (TimeSpan)(time_end - time_start); DateTime dt = new DateTime(ts.Ticks); //顯示時間 tb_UseTime.Text = dt.ToString("mm:ss:ff"); }
Step 3. 迴圈的工作-偵測氣泡是不是已經全部被捏破
《捏氣泡》還會判斷玩家是否已經捏完所有氣泡,因此,我也將這項工作放在迴圈裡,在迴圈運作時,一併處理。
private void CheckUcState() { int j = 0; for (int i = 1; i <= 24; i++) { BubbleControl bubble = this.LayoutRoot.FindName("BubbleControl_" + i) as BubbleControl; if (bubble.IsBroken) { j++; } } if (j >= 24) { //全部捏破後,紀錄目前的消耗時間 App.UseTime = ts; IsGameLoopRunning = false; NavigateToEndPage(); } }
Step 4. 迴圈初始化
在每一次跳到PlayPage的同時,要進行一次初始化動作,進行3件事情。
1. 設定迴圈變數。(設定迴圈正在運作)
2. 將遊戲時間歸零
3. 將氣泡的狀態恢復成初始的狀態
public PlayPage() { InitializeComponent(); GameStart(); } public void GameStart() { //初始化 IsGameLoopRunning = true; //時間歸零 App.UseTime = new TimeSpan(); //氣泡狀態賦歸 for (int i = 1; i <= 24; i++) { BubbleControl bubble = this.LayoutRoot.FindName("BubbleControl_" + i) as BubbleControl; bubble.IsBroken = false; } //循環開始 sb_Timer.Begin(); }
Step 5. 遊戲結束後
遊戲結束後,將會跳到EndPage,計算分數,以及消耗的時間。
//換頁 private void NavigateToEndPage() { App.NavigateToPage(new EndPage()); }
製作EndPage
EndPage的目的是顯示使用的時間,以及顯示目前累積的總氣泡數。另外,在按下Again文字後,可以立刻重新開始。按下Menu會回到Start頁面。
Step 1. 遊戲結束後
public EndPage() { InitializeComponent(); //顯示時間! ShowUseTime(); //顯示總氣泡數 ShowTotalBroken(); } private void ShowTotalBroken() { //throw new NotImplementedException(); tb_TotalBroken.Text = App.TotalBroken.ToString(); } private void ShowUseTime() { DateTime dt = new DateTime(App.UseTime.Ticks); tb_UseTime.Text = dt.ToString("mm:ss:ff"); } private void tb_Again_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { App.NavigateToPage(new PlayPage()); } private void tb_Menu_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { App.NavigateToPage(new StartPage()); }
小結
到目前為止,已經將程式的骨架設定完成,程式人員的工作已經告一段落。
可以將專案檔,交給設計人員進行介面的設計,設計人員的工作包括以下項目。
- 版面配置
- 封裝屬性的使用
- 相依屬性的使用
- 動畫腳本的建立
- 行為的使用
接下來,我們將會一步步的,帶領設計人員一起完成這款遊戲。
Copyright © 2010 比比触控游戏工作室WWW.BPLUSGAME.COM