編譯平台:VS2008 + .Net Framework 3.5
語言: C#
使用工具:Expression Design 4
Expression Blend
3、實現游戲用戶界面
盡管程序員可以使用VS編寫XAML代碼的方式來構造用戶界面,但是對於有設計愛好的用戶來說,使用類似Photoshop一樣的Expression套件能將
軟件美工最大化。對於怪物與目標塊的圖形顯示,示例使用了Expression Design來設計圖形,然後將其導入到Expression Blend中進行布局處理。
3、1 使用Expression Design設計圖案
Expression Design 是一個專業的圖表和圖形設計工具。該工具提供了矢量圖形的繪制能力,強大之處可以像PhotoShop一樣設計好用戶界面或是所需要量的圖形,
使用其導出功能導出為XAML資源或代碼。Expression Design主界面如下:
Design 通常是與Blend緊密相關的。美工人員使用Design強大的設計功能來設計界面元素,導出給Blend進行編輯。最後通過VS設計程序代碼。
在示例中,使用Design設計了一個Cell圖案,在其中添加了多個圖層,每一層放置各自不同的圖案,比如箱子、怪物、牆體。
然後使用Design的導出功能將這些圖案導出為資源字典,以便於程序引用這些圖形。導出窗口如下:
然後在App.xaml中的資源字典的定義中使用<ResourceDictionary.MergedDictionaries>指定Cell.xaml作為整個應用程序級別的資源。
3、2 實現用戶主界面
在開始布局游戲區域前,主界面上需要一個Banner來顯示游戲名稱,為游戲主界面添加背景圖片以增加界面的效果。
游戲創建了兩個用戶控件來實現這樣的特效,位於游戲項目的Controls文件夾下。
BackgroundControl.xaml用戶控件實現非常簡單,通過使用Expression Design來設計背景圖片,然後導出為獨立的Xaml文件。
導出設置如下:
圖形將被置於一個Canvas畫布中,在MainWindow.xaml中通過引用這個控件來設置背景色。
Banner.xmal的實現與BackgroundControl.xaml的實現類似。
主界面的布局非常簡單,主要分為四行:
(1) 第一行
主要放了一個Border、一個Rectangle、一個Viewbox(裡面放Banner)
(2) 第二行
在一個Grid中加入一個Rectangle、顯示關卡代碼、關數、按鈕
(3) 第三行
只是一個間隔,沒放東西
(4) 第四行
如上,位於Viewbox的Grid, x:Name為grid_Game在游戲啟動時動態創建行和列定義,並在行列中放置多個Button按鈕來實現游戲的方塊。
在MainWindow.xaml的聲明中,為窗口關聯了Loaded事件,當該事件觸發時,將執行Window_Load代碼。這段代碼在游戲窗口打開時,開始游戲第一關。
Window_Loaded將調用在資源中實例化的Game類,在Window.Resource資源區,首先定義了Game類,代碼如下:
[csharp]
<!-- 用於整個游戲的Game實例. -->
lt;Sokoban:Game x:Key="sokobanGame"/>
<!-- 用於整個游戲的Game實例. -->
<Sokoban:Game x:Key="sokobanGame"/>在MainWindow.xaml.cs中相應地定義了一個Game屬性,該屬性使用在資源中指定的x:Key鍵值查找Game對象實例。Game屬性的聲明如下:
[csharp]
/// <summary>
/// 獲取定義在Window資源中的Game對象的實例
/// </summary>
/// <value>游戲實例.</value>
Game Game
{
get
{
return (Game)TryFindResource("sokobanGame");
}
}
/// <summary>
/// 獲取定義在Window資源中的Game對象的實例
/// </summary>
/// <value>游戲實例.</value>
Game Game
{
get
{
return (Game)TryFindResource("sokobanGame");
}
}Game屬性的get獲取器使用TryFindResource() 方法,傳入指定的查找對象實例,並轉換為Game對象。因為TryFindResource()方法返回object類型的實例。
[csharp]
//
// 摘要:
// 搜索具有指定鍵的資源,如果找到,則返回該資源。
//
// 參數:
// resourceKey:
// 要查找的資源的鍵標識符。
//
// 返回結果:
// 找到的資源;如果未找到具有所提供 key 的資源,則為 null。
public object TryFindResource(object resourceKey);
//
// 摘要:
// 搜索具有指定鍵的資源,如果找到,則返回該資源。
//
// 參數:
// resourceKey:
// 要查找的資源的鍵標識符。
//
// 返回結果:
// 找到的資源;如果未找到具有所提供 key 的資源,則為 null。
public object TryFindResource(object resourceKey);
3、3 程序啟動時加載關卡
Window_Loaded 將使用Game對象加載游戲關卡,並初始化用戶界面。
[csharp]
void Window_Loaded(object sender, RoutedEventArgs e)
{ //當Game的Level屬性發生變更時,會觸發PropertyChanged事件
Game.PropertyChanged += game_PropertyChanged;
try
{
/* 加載並開始第一級游戲,Level屬性變更,
* 觸發Game.PropertyChanged */
Game.Start();
}
catch (Exception ex)
{ //異常處理消息。
MessageBox.Show("加載游戲出現異常. " + ex.Message);
}
}
void Window_Loaded(object sender, RoutedEventArgs e)
{ //當Game的Level屬性發生變更時,會觸發PropertyChanged事件
Game.PropertyChanged += game_PropertyChanged;
try
{
/* 加載並開始第一級游戲,Level屬性變更,
* 觸發Game.PropertyChanged */
Game.Start();
}
catch (Exception ex)
{ //異常處理消息。
MessageBox.Show("加載游戲出現異常. " + ex.Message);
}
}Game.Start將開始游戲的每一關,Start() 方法調用的LoadLevel()方法內部觸發了Game.PropertyChanged事件。
只要游戲的狀態發生變化,game_PropertyChanged事件處理代碼便會執行,該事件中的代碼將開始游戲界面的更新工作。
[csharp]
/// <summary>
/// 通過加載第一關來開始游戲
/// </summary>
public void Start()
{
if (sokobanService != null)
{ //獲取關卡數
LevelCount = sokobanService.LevelCount;
}
else
{ //得到關卡總數,通過獲取關卡文件的個數來得到
string[] files = Directory.GetFiles(levelDirectory, "*.skbn");
LevelCount = files.Length;
}
LoadLevel(0);//加載關卡
}
/// <summary>
/// 通過加載第一關來開始游戲
/// </summary>
public void Start()
{
if (sokobanService != null)
{ //獲取關卡數
LevelCount = sokobanService.LevelCount;
}
else
{ //得到關卡總數,通過獲取關卡文件的個數來得到
string[] files = Directory.GetFiles(levelDirectory, "*.skbn");
LevelCount = files.Length;
}
LoadLevel(0);//加載關卡
}
[csharp]
void game_PropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{ //判斷傳入的屬性名稱
switch (e.PropertyName)
{
case "GameState"://如果為GameState變更
UpdateGameDisplay();//更新游戲的界面顯示
break;
}
}
void game_PropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{ //判斷傳入的屬性名稱
switch (e.PropertyName)
{
case "GameState"://如果為GameState變更
UpdateGameDisplay();//更新游戲的界面顯示
break;
}
}
在以上代碼中,判斷PropertyName是否為GameState,如果為GameState屬性發生變更,則調用UpdateGameDisplay來更新游戲界面的顯示。
3、4 更新游戲界面的顯示
UpdateGameDisplay方法將根據游戲的狀態顯示不同的信息,使玩家理解游戲當前所在的狀態。
如果游戲處於開始運行狀態,將調用Initialiselevel() 方法來初始化游戲的關卡。UpdateGameDisplay的實現如下:
[csharp]
/// <summary>
/// 設置游戲進度狀態顯示,
/// </summary>
void UpdateGameDisplay()
{
switch (Game.GameState)
{
case GameState.Loading://如果游戲處於加載中
FeedbackControl1.Message = //在界面上顯示響應消息
new FeedbackMessage { Message = "正在加載..." };
ContinuePromptVisible = false;
break;
case GameState.GameOver://如果游戲結束狀態
FeedbackControl1.Message =//顯示結束信息
new FeedbackMessage { Message = "游戲結束" };
ContinuePromptVisible = true;
break;
case GameState.Running: //如果游戲處於開始運行狀態
ContinuePromptVisible = false;
FeedbackControl1.Message = new FeedbackMessage();
InitialiseLevel();//初始化游戲關卡界面
break;
case GameState.LevelCompleted: //如果玩家玩過關
FeedbackControl1.Message = //顯示玩過關的消息
new FeedbackMessage { Message = "恭喜您,成功過關!" };
MediaElement_LevelComplete.Position = TimeSpan.MinValue;
MediaElement_LevelComplete.Play();//播放聲音
ContinuePromptVisible = true;
break;
case GameState.GameCompleted://如果玩完了所有的關卡
FeedbackControl1.Message = new FeedbackMessage //顯示最終信息
{ Message = "干得好. \n游戲完成! \n請在Codeproejct聯系游戲作者" };
MediaElement_GameComplete.Position = TimeSpan.MinValue;
MediaElement_GameComplete.Play();//播放完成音樂
break;
}
}
/// <summary>
/// 設置游戲進度狀態顯示,
/// </summary>
void UpdateGameDisplay()
{
switch (Game.GameState)
{
case GameState.Loading://如果游戲處於加載中
FeedbackControl1.Message = //在界面上顯示響應消息
new FeedbackMessage { Message = "正在加載..." };
ContinuePromptVisible = false;
break;
case GameState.GameOver://如果游戲結束狀態
FeedbackControl1.Message =//顯示結束信息
new FeedbackMessage { Message = "游戲結束" };
ContinuePromptVisible = true;
break;
case GameState.Running: //如果游戲處於開始運行狀態
ContinuePromptVisible = false;
FeedbackControl1.Message = new FeedbackMessage();
InitialiseLevel();//初始化游戲關卡界面
break;
case GameState.LevelCompleted: //如果玩家玩過關
FeedbackControl1.Message = //顯示玩過關的消息
new FeedbackMessage { Message = "恭喜您,成功過關!" };
MediaElement_LevelComplete.Position = TimeSpan.MinValue;
MediaElement_LevelComplete.Play();//播放聲音
ContinuePromptVisible = true;
break;
case GameState.GameCompleted://如果玩完了所有的關卡
FeedbackControl1.Message = new FeedbackMessage //顯示最終信息
{ Message = "干得好. \n游戲完成! \n請在Codeproejct聯系游戲作者" };
MediaElement_GameComplete.Position = TimeSpan.MinValue;
MediaElement_GameComplete.Play();//播放完成音樂
break;
}
}
UpdateGameDisplay()方法通過判斷Game類中定義的GameState來向UI界面顯示游戲狀態消息。
FeedbackControl1是一個自定義的用於游戲界面顯示消息的用戶控件,該控件主要用於在用戶控件上顯示一些消息。
[csharp]
<Controls:FeedbackControl Grid.Row="3" x:Name="FeedbackControl1" Margin="10,10,10,10" Click="FeedbackControl1_Click"/>
<Controls:FeedbackControl Grid.Row="3" x:Name="FeedbackControl1" Margin="10,10,10,10" Click="FeedbackControl1_Click"/>
UpdateGameDisplay初始化關卡界面是在Running狀態,即游戲開始運行後,開始初始化關卡,
這是通過 InitialiseLevel();初始化游戲關卡界面。
InitialiseLevel();方法將初始化學Grid的行列定義,並在行列中放置Button控件作為方塊的容器。
由於在調用InitialiseLevel();前,LoadLevel()方法已經加載了關卡數據到Level類中,因此可以將Cell對象與指定的Button控件相關聯。
[csharp]
/// <summary>
/// 使用游戲級別初始化Grid
/// </summary>
void InitialiseLevel()
{
commandManager.Clear();//清除命令集合
//清除Grid子元素集合,以及行列定義集合
grid_Game.Children.Clear();
grid_Game.RowDefinitions.Clear();
grid_Game.ColumnDefinitions.Clear();
//根據關卡中的行數向Grid中添加行定義
for (int i = 0; i < Game.Level.RowCount; i++)
{
grid_Game.RowDefinitions.Add(new RowDefinition());
}
//根據關卡中的列數向Grid中添加列定義
for (int i = 0; i < Game.Level.ColumnCount; i++)
{
grid_Game.ColumnDefinitions.Add(new ColumnDefinition());
}
for (int row = 0; row < Game.Level.RowCount; row++)
{ //循環遍歷行
for (int column = 0; column < Game.Level.ColumnCount; column++)
{ //循環遍歷列
Cell cell = Game.Level[row, column];//得到行列中的Cell對象
cell.PropertyChanged += cell_PropertyChanged;//關聯屬性變更事件
Button button = new Button();//實例化Button控件
button.Focusable = false; //該控件不允許獲取焦點
//將Button的DataContext指定為cell對象,以便在XAML中控制
button.DataContext = cell;
button.Padding = new Thickness(0, 0, 0, 0);//按鈕無邊框
button.Style = (Style)Resources["Cell"];//指定按鈕樣式
button.Click += Cell_Click;//關聯按鈕單擊事件
//通過附加屬性設置按鋸位於Grid的行和列
Grid.SetColumn(button, column);
Grid.SetRow(button, row);
grid_Game.Children.Add(button);//將按鈕添加到Grid控件列表中
}
}
textBox_LevelCode.Text = Game.LevelCode;//顯示關卡號
label_LevelNumber.Content = //顯示當前關卡和總關卡信息
Game.Level.LevelNumber + 1 + "/" + Game.LevelCount;
grid_Main.DataContext = Game.Level;//設置主界面的DataContext為關卡
mediaElement_Intro.Position = TimeSpan.MinValue;//播放介紹音樂
mediaElement_Intro.Play(); //播放音樂
grid_Game.Focus(); //游戲區域得到焦點
}
/// <summary>
/// 使用游戲級別初始化Grid
/// </summary>
void InitialiseLevel()
{
commandManager.Clear();//清除命令集合
//清除Grid子元素集合,以及行列定義集合
grid_Game.Children.Clear();
grid_Game.RowDefinitions.Clear();
grid_Game.ColumnDefinitions.Clear();
//根據關卡中的行數向Grid中添加行定義
for (int i = 0; i < Game.Level.RowCount; i++)
{
grid_Game.RowDefinitions.Add(new RowDefinition());
}
//根據關卡中的列數向Grid中添加列定義
for (int i = 0; i < Game.Level.ColumnCount; i++)
{
grid_Game.ColumnDefinitions.Add(new ColumnDefinition());
}
for (int row = 0; row < Game.Level.RowCount; row++)
{ //循環遍歷行
for (int column = 0; column < Game.Level.ColumnCount; column++)
{ //循環遍歷列
Cell cell = Game.Level[row, column];//得到行列中的Cell對象
cell.PropertyChanged += cell_PropertyChanged;//關聯屬性變更事件
Button button = new Button();//實例化Button控件
button.Focusable = false; //該控件不允許獲取焦點
//將Button的DataContext指定為cell對象,以便在XAML中控制
button.DataContext = cell;
button.Padding = new Thickness(0, 0, 0, 0);//按鈕無邊框
button.Style = (Style)Resources["Cell"];//指定按鈕樣式
button.Click += Cell_Click;//關聯按鈕單擊事件
//通過附加屬性設置按鋸位於Grid的行和列
Grid.SetColumn(button, column);
Grid.SetRow(button, row);
grid_Game.Children.Add(button);//將按鈕添加到Grid控件列表中
}
}
textBox_LevelCode.Text = Game.LevelCode;//顯示關卡號
label_LevelNumber.Content = //顯示當前關卡和總關卡信息
Game.Level.LevelNumber + 1 + "/" + Game.LevelCount;
grid_Main.DataContext = Game.Level;//設置主界面的DataContext為關卡
mediaElement_Intro.Position = TimeSpan.MinValue;//播放介紹音樂
mediaElement_Intro.Play(); //播放音樂
grid_Game.Focus(); //游戲區域得到焦點
}
InitialiseLevel首先清除命令管理器中的命令列表,清除Grid中的行列定義及子內容。
然後根據游戲關卡的行數和列數創建行列定義,循環遍歷行列。先獲取在Game.Level 實例中加載的cell 對象。以便與稍後將要創建的Button控件相關聯。
在獲取了Cell並設置了Cell的PropertyChanged事件為cell_PropertyChanged事件處理代碼後實例化一個Buttons控件。
該Button控件將作為一個游戲方塊,指定其不能得焦點,不能具有邊界,並關聯其Click單擊事件。
最後將該Button添加到Grid中。
cell_PropertyChanged事件主要用於判斷當CellContents屬性發生變更,將根據方塊的內容是一個箱子還是角色來播放不同的音樂。
3、5 處理方塊單擊事件
當用戶單擊某個按鈕時,會執行Cell_Click事件處理代碼。
該事件處理代碼將根據鼠標單擊的位置,讓怪物轉到當前單擊的位置點,如果玩家是按下鍵盤的確Shit鍵並單擊鼠標,將執行PushCommand命令,
否則JumpCommand命令。命令的執行是通過CommandManager這個命令管理器來實現的。Cell_Click事件代碼如下:
[csharp]
void Cell_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)e.Source;//得到當前單擊的Button實例
Cell cell = (Cell)button.DataContext;//獲取DataContext
CommandBase command; //要執行的命令變量
if (Keyboard.IsKeyDown(Key.LeftShift) //如果按下左或右Shift
|| Keyboard.IsKeyDown(Key.RightShift))
{ //實例化一個PushCommand執行推動命令
command = new PushCommand(Game.Level, cell.Location);
}
else //如果沒有按下Shift,則執行跳轉命令
{ //實例化跳轉命令
command = new JumpCommand(Game.Level, cell.Location);
}//使用CommandManager命令管理器執行命令
commandManager.Execute(command);
}
void Cell_Click(object sender, RoutedEventArgs e)
{
Button button = (Button)e.Source;//得到當前單擊的Button實例
Cell cell = (Cell)button.DataContext;//獲取DataContext
CommandBase command; //要執行的命令變量
if (Keyboard.IsKeyDown(Key.LeftShift) //如果按下左或右Shift
|| Keyboard.IsKeyDown(Key.RightShift))
{ //實例化一個PushCommand執行推動命令
command = new PushCommand(Game.Level, cell.Location);
}
else //如果沒有按下Shift,則執行跳轉命令
{ //實例化跳轉命令
command = new JumpCommand(Game.Level, cell.Location);
}//使用CommandManager命令管理器執行命令
commandManager.Execute(command);
}
代碼首先得到當前單擊的Button對象,根據該Button對象獲取到與其相關聯的cell對象。
然後判斷當前是否按下了左或右Shit鍵。
如理按下了Shit鍵,則實例化PushCommand命令,否則實例化JumpCommand命令,要求CommandManager命令
管理器來執行相應的推移或跳轉操作。
3、6 使用Command模式發送命令請求
Command模式是為了讓命令的請求與命令的執行者進行解耦。CommandManager只對抽象的CommandBase進行執行,而不與實際的
命令代碼進行解耦。
在用戶界面上,當用戶執行命令時,傳遞實際的命令給CommandManager 執行,實際上這是利用了面向對象的多態技術。
當用戶界面按下鍵盤上的按鈕時,一系列具體的命令對象產生,然後交給CommandManager命令管理器進行執行。
在MainWindow.xaml的定義中,響應了主窗體的KeyDown事件,將執行Window_KeyDown事件處理代碼。
[csharp]
/// <summary>
/// 處理Window控件的KeyDown事件
/// </summary>
void Window_KeyDown(object sender, KeyEventArgs e)
{
CommandBase command = null;//用於保存命令的變量
Level level = Game.Level; //得到當前關卡對象實例
if (Game != null) //如果己經初始化了Game
{ //判斷當前游戲的狀態是否在運行狀態
if (Game.GameState == GameState.Running)
{
switch (e.Key) //獲取按鍵
{
case Key.Up://如果是向上方向鍵,則向上移動
command = new MoveCommand(level, Direction.Up);
break;
case Key.Down://如果是向下方向鍵,則向下移動
command = new MoveCommand(level, Direction.Down);
break;
case Key.Left://如果是向左方向鍵,則向左移動
command = new MoveCommand(level, Direction.Left);
break;
case Key.Right://如果是向右方向鍵,則向右移動
command = new MoveCommand(level, Direction.Right);
break;
case Key.Z://如果是Ctrl+Z鍵
if (Keyboard.Modifiers == ModifierKeys.Control)
{ //執行Undo操作,將從撤消堆棧中取上一次執行的命令
commandManager.Undo();
}
break;
case Key.Y://如果是Ctrl+Y鍵
if (Keyboard.Modifiers == ModifierKeys.Control)
{ //執行Redo操作,將從命令堆棧中取上一次執行的命令
commandManager.Redo();
}
break;
}
}
else
{
switch (Game.GameState) //根據游戲的不同狀態判斷
{
case GameState.GameOver://如果游戲結束
Game.Start(); //按任意鍵重新開始
break;
case GameState.LevelCompleted://如果關卡玩過關
Game.GotoNextLevel();//按任意鍵開始下一關
break;
}
}
}
if (command != null)//根據己經賦好的命令對象
{ //使用命令管理器的Execute執行命令
commandManager.Execute(command);
}
}
/// <summary>
/// 處理Window控件的KeyDown事件
/// </summary>
void Window_KeyDown(object sender, KeyEventArgs e)
{
CommandBase command = null;//用於保存命令的變量
Level level = Game.Level; //得到當前關卡對象實例
if (Game != null) //如果己經初始化了Game
{ //判斷當前游戲的狀態是否在運行狀態
if (Game.GameState == GameState.Running)
{
switch (e.Key) //獲取按鍵
{
case Key.Up://如果是向上方向鍵,則向上移動
command = new MoveCommand(level, Direction.Up);
break;
case Key.Down://如果是向下方向鍵,則向下移動
command = new MoveCommand(level, Direction.Down);
break;
case Key.Left://如果是向左方向鍵,則向左移動
command = new MoveCommand(level, Direction.Left);
break;
case Key.Right://如果是向右方向鍵,則向右移動
command = new MoveCommand(level, Direction.Right);
break;
case Key.Z://如果是Ctrl+Z鍵
if (Keyboard.Modifiers == ModifierKeys.Control)
{ //執行Undo操作,將從撤消堆棧中取上一次執行的命令
commandManager.Undo();
}
break;
case Key.Y://如果是Ctrl+Y鍵
if (Keyboard.Modifiers == ModifierKeys.Control)
{ //執行Redo操作,將從命令堆棧中取上一次執行的命令
commandManager.Redo();
}
break;
}
}
else
{
switch (Game.GameState) //根據游戲的不同狀態判斷
{
case GameState.GameOver://如果游戲結束
Game.Start(); //按任意鍵重新開始
break;
case GameState.LevelCompleted://如果關卡玩過關
Game.GotoNextLevel();//按任意鍵開始下一關
break;
}
}
}
if (command != null)//根據己經賦好的命令對象
{ //使用命令管理器的Execute執行命令
commandManager.Execute(command);
}
}
代碼中的CommandBase 抽象基類用來保存具體的Command命令,這是利用了多態的原理。
如果游戲全Game 不為null,將根據游戲是否在運行狀態獲取用戶所按下的按鈕。
3、7 使用MultiDataTrigger改變方塊外觀
盡管使用Grid和Button布好了游戲界面,但是默認情況下,所有的Button都使用Cell外觀。
幸好WPF提供了強大的樣式觸發器,可以根據指定的條件變更其外觀。
到目前為止,已經使用了Expression Design創建了用於不同方塊的圖案及角色形狀,並且這些圖形都以畫刷的形式嵌入到了應用程序的資源中。
那麼通過MultiDataTrigger就可以根據特定的Cell名稱來應用不同的按鈕填充,使得按鈕可以顯示不同的外觀。
對於方塊內容樣式,比如方塊中是否有一個箱子,或是當前角色移動到某個方塊的位置,使用MultiDataTrigger多條件觸發器定義以下XAML代碼:
通過WPF提供的強大的樣式觸發器,當移動角色或推動箱子是地,UI端會自動變更顯示內容,不需要編程實現設置圖形的位置。
上面講到Button的Style是Rectangle,這是在InitialiseLevel()方法中,為每一個單元格創建按鈕時,指定了按鈕的默認模式為Cell時指定的。
[csharp]
button.Style = (Style)Resources["Cell"];//指定按鈕樣式
button.Style = (Style)Resources["Cell"];//指定按鈕樣式
Cell樣式指定了按鈕的呈現外觀,因為按鈕是一個內容控件,可以通過指定其Template來改變按鈕的默認呈現。Cell樣式如下:
方塊顯示的Rectangle指定的Style為"CellStyle",它會根據Button所關聯的不同的Cell類型來顯示不同的方塊樣式。如下:
通過這些樣式觸發器的設置,游戲關卡內容一旦回去,就自動使用各自不同的方塊外觀來顯示整個游戲的布局。
當鼠標或鍵盤移動時,會根據CellContents的Name屬性自動設置移動,實現了游戲的運行顯示效果。
作者:chenyujing1234