[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.xam.cs
public static TimeSpan UseTime { set; get; }
//計數量
public static int TotalBroken { set; get; }
Step 2. 設定外部儲存區
由於我們希望氣泡數量可以持續累積,而不是在遊戲關閉後,紀錄就消失,因此我們會用到Silverlight中的IsolatedStorage。讓應用程式在執行前,讀出先前紀錄,關閉前,再將記錄存回去。
app.xam.cs
{
//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種破裂的狀態。
效果之後會交給設計人員處理。
<Storyboard x:Name="sb_Effect" />
<Storyboard x:Name="sb_BrokenOne"/>
<Storyboard x:Name="sb_BrokenTwo">
</UserControl.Resources>
3個控制項,一張氣泡正常的圖片,
2張氣泡破裂的圖片,一個撥放音效的控制項。
控制項內容之後會交給設計人員處理。
<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
BubbleControl.xaml.cs
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
{
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
{
if (value)
{
sb_Effect.BeginTime = TimeSpan.FromSeconds(EffectDelayTime);
sb_Effect.Begin();
}
else
{
sb_Effect.Stop();
}
}
Step 6. 氣泡破掉時播放音效
BubbleControl.xaml.cs
{
mediaElement.Stop();
mediaElement.Play();
}
Step 7. 增加破裂氣泡數
只要氣泡破裂後,就做一次記錄,將氣泡加入累積總數。
BubbleControl.xaml.cs
{
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
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
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
<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
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. 迴圈的工作-偵測氣泡是不是已經全部被捏破
《捏氣泡》還會判斷玩家是否已經捏完所有氣泡,因此,我也將這項工作放在迴圈裡,在迴圈運作時,一併處理。
{
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. 將氣泡的狀態恢復成初始的狀態
{
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. 遊戲結束後
{
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());
}
小結
到目前為止,已經將程式的骨架設定完成,程式人員的工作已經告一段落。
可以將專案檔,交給設計人員進行介面的設計,設計人員的工作包括以下項目。
- 版面配置
- 封裝屬性的使用
- 相依屬性的使用
- 動畫腳本的建立
- 行為的使用
接下來,我們將會一步步的,帶領設計人員一起完成這款遊戲。