一、創建項目
1.創建WPF項目,設置初始化窗口大小(初級難度):高x寬為430x350。
2.添加文件夾Images,並添加相關圖片。
3.xaml中引入圖片資源。
<Window.Resources> <BitmapImage x:Key="ImgSpace" UriSource="/Images/space.bmp"/> <BitmapImage x:Key="ImgMine" UriSource="/Images/mine.png"/> <BitmapImage x:Key="ImgNum1_8" UriSource="/Images/num1_8.png"/> <BitmapImage x:Key="ImgForeground" UriSource="/Images/foreImg.png"/> <BitmapImage x:Key="ImgMineOrTimer" UriSource="/Images/mineAndTimer.png"/> <BitmapImage x:Key="ImgBomb" UriSource="/Images/bomb.png"/> </Window.Resources>
4.添加窗口元素
(1)菜單
<DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="游戲(_G)"> <MenuItem x:Name="MenuGameStart" Header="開始游戲(_S)" Click="MenuGameStart_Click"/> <Separator/> <MenuItem x:Name="MenuGameExit" Header="退出游戲(_x)" Click="MenuGameExit_Click"/> </MenuItem> <MenuItem Header="級別(_L)"> <MenuItem Header="初級(_L)" x:Name="MenuLowLevel" IsChecked="True" Click="MenuLowLevel_Click"/> <MenuItem Header="中級(_M)" x:Name="MenuMiddleLevel" IsChecked="False" Click="MenuMiddleLevel_Click"/> <MenuItem Header="高級(_H)" x:Name="MenuHighLevel" IsChecked="False" Click="MenuHighLevel_Click"/> </MenuItem> <MenuItem Header="選項(_O)"> <MenuItem x:Name="MenuOptionsMusic" Header="音樂音效(_S)" IsCheckable="True" IsChecked="True" Click="MenuOptionsMusic_Click"/> <Separator/> <MenuItem x:Name="MenuShowAllMine" Header="顯示地雷(_O)" Click="MenuShowAllMine_Click"/> <MenuItem x:Name="MenuRestoreMap" Header="繼續游戲(_F)" Click="MenuRestoreMap_Click"/> <Separator /> <MenuItem x:Name="MenuResetData" Header="重置記錄(_R)" Click="MenuResetData_Click" /> </MenuItem> <MenuItem Header="幫助(_H)"> <MenuItem x:Name="MenuHelpAbout" Header="關於(_A)..." Click="MenuHelpAbout_Click"/> </MenuItem> </Menu> </DockPanel>
(2)在菜單之后,</DockPanel>之前添加其他界面元素
<Grid Margin="3" DockPanel.Dock="Top"> <Grid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Background="SkyBlue" VerticalAlignment="Center"> <Grid Margin="2 2 2 2"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" HorizontalAlignment="Right" Margin="0 0 60 0" Orientation="Horizontal"> <Image x:Name="ImgClock" Width="35" Height="35" /> <Border Height="30" Width="60" CornerRadius="6" BorderThickness="1" VerticalAlignment="Center" Background="#333333"> <TextBlock x:Name="textBlockTime" Text="" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="14" Foreground="AliceBlue"/> </Border> </StackPanel> <StackPanel Grid.Column="1" HorizontalAlignment="Left" Orientation="Horizontal" Margin="40 0 0 0"> <Image x:Name="ImgMineNum" Width="35" Height="35" /> <Border Height="30" Width="60" CornerRadius="6" BorderThickness="1" VerticalAlignment="Center" Background="#333333"> <TextBlock x:Name="textBlockMineNum" Text="" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="14" Foreground="AliceBlue"/> </Border> </StackPanel> </Grid> </StackPanel> <StackPanel Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top"> <Canvas x:Name="BackCanvas" Width="315" Height="315" Background="LightCyan" Margin="10" /> </StackPanel> <StackPanel Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top"> <Canvas x:Name="ForeCanvas" Width="315" Height="315" Margin="10" MouseLeftButtonDown="ForeCanvas_MouseLeftButtonDown" MouseRightButtonDown="ForeCanvas_MouseRightButtonDown"/> </StackPanel> </Grid>
其中兩個Image用於顯示時鍾和地雷數圖例,其后兩個TextBlock分別用於顯示讀秒數和剩余地雷數。
兩個重疊的Canvas,分別顯示底層圖塊和前景圖塊。底層圖塊游戲開始后是固定的,其中會顯示隨機分布的地雷及其周邊地雷數值,而前景圖塊可以通過鼠標點擊進行移除或標記為問號或小紅旗。
二、載入圖片資源
1、添加一個靜態圖片幫助類ImageHelper,方便以后的圖片切片操作。
public static class ImageHelper { /// <summary> /// BitmapSource圖片源剪切操作函數 /// </summary> /// <param name="bmpSource">等待剪切的源</param> /// <param name="cut">剪切矩形</param> /// <returns>已剪切的圖片源</returns> public static BitmapSource CutImage(BitmapSource bmpSource, Int32Rect cut) { return new CroppedBitmap(bmpSource, cut); } /// <summary> /// 將BitmapImage轉換為BitmapSource /// </summary> /// <param name="bitmapImage">待轉換的BitmapImage</param> /// <returns>BitmapSource</returns> public static BitmapSource BitmapImageToBitmapSource(BitmapImage bitmapImage) { ImageSource imageSource = bitmapImage; Bitmap bitmap = ImageSourceToBitmap(imageSource); BitmapSource bitmapSource = BitmapToBitmapImage(bitmap);
bitmap.Dispose(); return bitmapSource; } /// <summary> /// 將ImageSource轉為Bitmap /// </summary> /// <param name="imageSource"></param> /// <returns></returns> public static System.Drawing.Bitmap ImageSourceToBitmap(ImageSource imageSource) { BitmapSource m = (BitmapSource)imageSource; Bitmap bmp = new Bitmap(m.PixelWidth, m.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); System.Drawing.Imaging.BitmapData data = bmp.LockBits(new Rectangle(System.Drawing.Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); m.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); bmp.UnlockBits(data); return bmp; } /// <summary> /// 將Bitmap轉為BitmapImage /// </summary> /// <param name="bitmap"></param> /// <returns></returns> public static BitmapImage BitmapToBitmapImage(Bitmap bitmap) { using (MemoryStream stream = new MemoryStream()) { bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png); stream.Position = 0; BitmapImage result = new BitmapImage(); result.BeginInit(); result.CacheOption = BitmapCacheOption.OnLoad; result.StreamSource = stream; result.EndInit(); result.Freeze(); return result; } } }
2、添加幾個字段變量
主程序MainWindow.xaml.cs中添加:
private BitmapSource _bmpSpace, _bmpMine, _bmpNum1_8, _bmpForeground, _bmpMineOrTimer, _bmpBomb; private System.Drawing.Size _cellSize = new System.Drawing.Size(35, 35);
3、獲取圖片資源
private void GetImageResource() { BitmapImage bmpSpace = (BitmapImage)TryFindResource("ImgSpace"); BitmapImage bmpMine = (BitmapImage)TryFindResource("ImgMine"); BitmapImage bmpNum1_8 = (BitmapImage)TryFindResource("ImgNum1_8"); BitmapImage bmpForeground = (BitmapImage)TryFindResource("ImgForeground"); BitmapImage bmpMineOrTimer = (BitmapImage)TryFindResource("ImgMineOrTimer"); BitmapImage bmpBomb = (BitmapImage)TryFindResource("ImgBomb"); _bmpSpace = ImageHelper.BitmapImageToBitmapSource(bmpSpace); _bmpMine = ImageHelper.BitmapImageToBitmapSource(bmpMine); _bmpNum1_8 = ImageHelper.BitmapImageToBitmapSource(bmpNum1_8); _bmpForeground = ImageHelper.BitmapImageToBitmapSource(bmpForeground); _bmpMineOrTimer = ImageHelper.BitmapImageToBitmapSource(bmpMineOrTimer); _bmpBomb = ImageHelper.BitmapImageToBitmapSource(bmpBomb); }
4、將界面中用到的時鍾和地圖數這兩張圖片設置到位。
private void SetTimerAndMineImage() { ImgClock.Source = ImageHelper.CutImage(_bmpMineOrTimer, new Int32Rect(0, 0, _cellSize.Width, _cellSize.Height)); ImgMineNum.Source = ImageHelper.CutImage(_bmpMineOrTimer, new Int32Rect(1 * _cellSize.Width, 0, _cellSize.Width, _cellSize.Height)); }
5、將上述兩個方法添加到構造方法中。
運行程序,如圖1。
三、設置狀態枚舉
添加一個MyEnum.cs類文件,讓主cs文件看起來簡練一點。內容如下:
// 游戲級別 public enum Level { SIMPLE, NORMAL, HARD }; // 前景狀態 public enum ForeState { NONE, // 無 NORMAL, // 正常覆蓋 FLAG, // 紅旗 QUESTION // 問號 }; // 底層狀態 public enum BackState { MINE = -1, // 地雷 BLANK = 0, // 空 }; // 游戲狀態 public enum GameState { NONE, // 未開始 STOP, // 已停止 START, // 已開始 PAUSE // 已暫停 };
四、添加GameLevel類,定義游戲級別相關參數
public class GameLevel { public int _mineNum { get; set; } // 地雷數 public int _rowGrid { get; set; } // 橫格子數 public int _colGrid { get; set; } // 縱格子數 public GameLevel(int currLevel, int mineNum, int rowGrid, int colGrid) { _mineNum = mineNum; _rowGrid = rowGrid; _colGrid = colGrid; } }
五、定義主游戲數據變量
private int[,] _backData = null; // 底部背景數據(雷-1,0為空白,數值(1-8)周圍雷數) private int[,] _foreData = null; // 前景數據(1:正常蓋住;2:紅旗;3:問號) private Image[,] _backImage = null; // 底圖圖片數組 private Image[,] _foreImage = null; // 上方圖片數組 private GameState _gameState = GameState.NONE; private Random rnd = new Random(); // 隨機數 private GameLevel _gameLevel; private Level _level = Level.Simple;
另外還用到幾個計時器,看后面的注釋
// 計時 private System.Diagnostics.Stopwatch _stopWatchGame = new System.Diagnostics.Stopwatch(); // 游戲過程讀秒 private DispatcherTimer _timerSetTimeText = new DispatcherTimer(); // 更新讀秒文本框 private DispatcherTimer _timerWinAnim = new DispatcherTimer(); // 用於勝利時動畫 private DispatcherTimer _timerBomb = new DispatcherTimer(); // 用於踩雷動畫
將計時器初始化放在構造方法中
// 計時器 _timerSetTimeText.Interval = new TimeSpan(0, 0, 1); _timerSetTimeText.Tick += _timerSetTimeText_Tick; // 動畫計時器 _timerWinAnim.Interval = new TimeSpan(0, 0, 0, 0, 300); _timerWinAnim.Tick += _timerWinAnim_Tick; // 用戶踩雷后的動畫 _timerBomb.Interval = new TimeSpan(0, 0, 0, 0, 200); _timerBomb.Tick += _timerBomb_Tick;
六、初始化游戲狀態
private void InitialGameState() { _timerSetTimeText.Stop(); _timerWinAnim.Stop(); _stopWatchGame.Reset(); _stopWatchGame.Stop(); _gameState = GameState.NONE; MenuGamePauseOrContinue.Header = "暫停(_P)"; } private void _timerSetTimeText_Tick(object sender, EventArgs e) { textBlockTime.Text = ((int)_stopWatchGame.Elapsed.TotalSeconds).ToString(); } private void _timerWinAnim_Tick(object sender, EventArgs e) { }
private void _timerBomb_Tick(object sender, EventArgs e)
{
}
后面動畫計時事件方法以后再加入,這里先空着。
七、初始化游戲主數據
private void InitGameData(Level level) { switch (level) { case Level.SIMPLE: _gameLevel = new GameLevel(9, 9, 10); break; case Level.NORMAL: _gameLevel = new GameLevel(16, 16, 40); break; case Level.HARD: _gameLevel = new GameLevel(30, 16, 99); break; } // 設置窗口大小 this.Width = _cellSize.Width * _gameLevel._rowGrid + 40; this.Height = _cellSize.Height * _gameLevel._colGrid + 20 + 100; // 獲取屏幕大小 double screenWidth = SystemParameters.WorkArea.Width; double screenHeight = SystemParameters.WorkArea.Height; // 使窗口居中 this.Left = (screenWidth - this.Width) / 2; this.Top = (screenHeight - this.Height) / 2; // 設置繪圖控件大小 BackCanvas.Width = _gameLevel._colGrid * _cellSize.Width; BackCanvas.Height = _gameLevel._rowGrid * _cellSize.Height; ForeCanvas.Width = BackCanvas.Width; ForeCanvas.Height = BackCanvas.Height; // 初始化前景和背景數據 _backData = new int[_gameLevel._colGrid, _gameLevel._rowGrid]; _foreData = new int[_gameLevel._colGrid, _gameLevel._rowGrid]; for (int y = 0; y<_gameLevel._colGrid; y++) { for (int x=0; x<_gameLevel._rowGrid; x++) { _backData[y, x] = (int)BackState.BLANK; _foreData[y, x] = (int)ForeState.NORMAL; } } // 初始化前景和背景圖片數組 _backImage = new Image[_gameLevel._colGrid, _gameLevel._rowGrid]; _foreImage = new Image[_gameLevel._colGrid, _gameLevel._rowGrid]; // 清理繪制區 BackCanvas.Children.Clear(); ForeCanvas.Children.Clear(); }