使用XNA為Windows phone 7開發簡單拼圖遊戲
轉自:http://blog.csdn.net/fsafs168/article/details/7747744
使用XNA為Windows phone 7開發簡單拼圖遊戲
引言
這裡是天幕的部落格,今天我們要簡單的學習一下使用XNA4.0平臺開發Windows Phone 7 的拼圖遊戲。這個遊戲把一張圖片分成15個小圖片(每個小圖片擁有一樣的大小),玩家一個一個的移動小圖片,最終還原成原圖。下面是開始遊戲時的畫面:
如果玩家點選螢幕,遊戲就會開始,圖片會被分成15個部分。
在螢幕右下角你能看到一個缺口,這個缺口就是代表玩家可以移動缺口旁邊的圖片到缺口,一個一個的移動,直到解決這個遊戲!
Game Components
如果你開啟我們的解決方案,你會發現有一個GameManager類 (就在GameManager.cs程式碼檔案裡)你在這個檔案裡不會看到除了XNA自定義程式碼以外太多的程式碼,裡面的方法都是XNA自定義的!那麼為什麼這麼做?我們遊戲的程式碼在哪裡?你再開啟解決方案管理器會發現裡面有一個PuzzleGame 類. 如果你開啟這個類,你會看到PuzzleGame這個類繼承了DrawableGameComponent. (需要自行繪製時通知遊戲的元件),那麼什麼是(Drawable)GameComponent類?
為了使你遊戲的程式碼具有更好的獨立性,XNA框架為你提供了GameComponent 類。你能通過這個類重寫覆蓋其父類的方法,來獲得最初的Game類的方法:Initialize、 Update、 LoadContent 等等。它並不包括Draw方法,然而你可以通過簡單的繼承DrawableGameComponent 類(GameComponent的子類)來實現它。
為game類加入一個組建只需要簡單的呼叫 Game.AddComponent 例項方法。然後,在遊戲初始化和進行遊戲迴圈的時候,也就是在進入遊戲主函式之前,XNA框架會呼叫GameComponent 類相關的方法,比如說我們的遊戲,它首先呼叫在PuzzleGame類中的Update方法,然後在呼叫GameManager類的Update方法。更重要的是,從GameComponent繼承的類可以實現它的屬性,它可以使用或者不使用GameComponent,也能相應的修改它,使它參與或者不參與遊戲的迴圈!!
現在讓我們詳細的看一下組成我們遊戲的類。
Tile類
Tile類是負責維護每一個小圖片資訊的,它的程式碼如下:
1: class Tile
2: {
3: public Vector2 TextureLocationInSpriteSheet;
4: /// <summary>
5: /// 這裡包含一個數字,數字從1-15它代表了小圖片所在
6: /// 的位置。如果它排列有序了的話,就代表玩家勝出了
7: /// </summary>
8: public int OriginalLocationInPuzzle;
9: }
第二個變數, OriginalLocationInPuzzle, 會維護小圖片的位置 ( 比如說小圖片在第一行就是數字1,2,3, 第二行就是4,5,6啊等等),就是為了維護小圖片在遊戲中的位置,最終決定玩家是不是勝出了。第一個變數, TextureLocationInSpriteSheet,會使用一個包含用於顯示本地小圖片的 spritesheet(將一個大圖片分割顯示). 你並不知道 spritesheet是什麼?那就接著往下看.
Spritesheet
我們從磁碟載入一個檔案需要一定時間,載入更多的檔案的話需要更多的時間。在我們的遊戲裡,我們需要顯示一個完整的圖片(拼圖遊戲的原圖),然後又要將它分割成15個小圖片進行顯示(遊戲進行時)。所以我們就要在我們的遊戲裡顯示16(!)個圖片,然而他需要一定的時間。於是我們選擇實現一個分割圖片用的spritesheet!spritesheet是一個非常有用的技術,當我們需要一個大圖片的許多小圖片時(子圖形),它能減少載入時間!(這樣我們也不用去學什麼PS來把圖片一點一點的分開… …)這個技術在一些遊戲裡非常的有用!在我們的遊戲裡,我們將用Texture2D物件來引用這個圖片。那麼我們應該用什麼去畫這個圖片?我們只需要覆蓋SpriteBatch.Draw方法。更多的內容在後面,現在我們只需要記住 TextureLocationInSpriteSheet能夠分割一個圖片就可以了。
PuzzleGame類
讓我們看一下PuzzleGame類中的私有變數
1: class PuzzleGame : DrawableGameComponent
2: {
3: GameState gameState;
4:
5: SpriteFont font;
6: Texture2D fullPicture;
7: SpriteBatch spriteBatch;
8:
9: const int Columns = 3;
10: const int Rows = 5;
11:
12: const int TileWidth = 160;
13: const int TileHeight = 160;
14:
15: Tile[,] tilesArray;
16: Tile[,] tilesArrayTemp;
17: Random random = new Random();
18: public PuzzleGame(Game game) : base(game) { }
gameState儲存了遊戲的狀態, 它來自於GameState列舉型別(StartScreen,Playing, Winner),開始、遊戲、勝利三個狀態. 其它的變數主要是為了描述遊戲中3x5個小圖片,它是一個2維陣列儲存的。
LoadTilesToTheirInitialLocation這個方法初始化了小圖片陣列。
1: private void LoadTilesToTheirInitialLocation()
2: {
3: tilesArray = new Tile[Columns,Rows];
4: for (int column = 0; column <Columns; column++)
5: for (int row = 0; row <Rows; row++)
6: {
7: Tile t = new Tile();
8: t.TextureLocationInSpriteSheet.X = TileWidth * column;
9: t.TextureLocationInSpriteSheet.Y = TileHeight * row;
10: t.OriginalLocationInPuzzle= row * Columns + column + 1;
11: tilesArray[column, row] =t;
12: }
13: }
首先我們例項化小圖片陣列tilesArray,然後經過一個二重for迴圈例項化每一個小圖片Tile,並給它的spritesheet賦值 (之前提到的那個),也就是 OriginalLocationInPuzzle 變數.
1: public override void Update(GameTime gameTime)
2: {
3: TouchLocation? tl = null;
4: TouchCollection tc =TouchPanel.GetState();
5: if (tc.Count > 0 &&tc[0].State == TouchLocationState.Released)
6: {
7: tl = tc[0];
8: }
9:
10: switch (gameState)
11: {
12: case GameState.StartScreen:
13: if (tl != null)
14: {
15: LoadTilesIntoRandomLocations();
16: gameState =GameState.Playing;
17: }
18: break;
19: case GameState.Playing:
20: if (tl != null)
21: {
22: int hitTileColumn =(int)(tl.Value.Position.X / TileWidth);
23: int hitTileRow =(int)(tl.Value.Position.Y / TileHeight);
24: CheckAndSwap(hitTileColumn, hitTileRow);
25: if(CheckIfPlayerWins())
26: gameState =GameState.Winner;
27: }
28: else if (tl == null)//user has not touched the screen, so don't bother drawing
29: Game.SuppressDraw();
30: break;
31: case GameState.Winner:
32: if (tl != null)
33: gameState =GameState.StartScreen;
34: break;
35: }
36:
37: base.Update(gameTime);
38: }
在Update 方法裡, 我們首先檢查玩家是不是點選了螢幕。之後我們檢查遊戲的狀態也就是gameState 變數。
- 如果我們在開始介面,我們會給每個小圖片隨即一個位置,然後開始遊戲。
- 如果我們在遊戲介面,我們需要獲得玩家點選螢幕的點的座標,如果玩家點選的是空圖片旁邊的小圖片的話,就移動它。如果這個時候玩家解決了這個拼圖遊戲,我們就跳到勝利介面(進入勝利狀態)。
- 其中比較有趣的是Game.SuppressDraw 方法。如果使用者沒有點選螢幕,我們會防止呼叫Draw方法,直到下一次呼叫Update方法。所以這個時候螢幕不會更新(直到下次Draw方法呼叫前,螢幕會一直以當前狀態顯示)而且這樣做會省電!(驚喜吧,哈哈)
- 最後一點,如果在勝利介面使用者點選了螢幕的話,我們會讓遊戲重新開始(回到最初顯示完整圖片的介面)。
1: private void LoadTilesIntoRandomLocations()
2: {
3: tilesArrayTemp = newTile[Columns, Rows];
4: for (int column = 0; column <Columns; column++)
5: for (int row = 0; row <Rows; row++)
6: {
7: if (column == Columns - 1&& row == Rows - 1) break;
8:
9: int newColumn; // =random.Next(0, Columns);
10: int newRow; // =random.Next(0, Rows);
11: do
12: {
13: newColumn =random.Next(0, Columns);
14: newRow =random.Next(0, Rows);
15: } while(tilesArrayTemp[newColumn, newRow] != null || (newColumn == Columns - 1&& newRow == Rows - 1));
16: tilesArrayTemp[newColumn,newRow] = tilesArray[column, row];
17: }
18: tilesArray = tilesArrayTemp;
19: }
為了隨即載入我們的小圖片Tile,我們使用了一個臨時的二維陣列(tilesArrayTemp) 並隨機產生數字給所有的小圖片,賦值它們的位置(右下角除外)。此外,我們在右下角留下了一個‘空的’小圖片。這個演算法為每個小圖片賦值了一個新的位置(行、列)。如果這個位置已經被佔了(被剛初始化的小圖片),那麼這個演算法會再次嘗試分配一個位置(這是靠do..while實現的)。注意:這個演算法並不效率,實話說我寧願使用類似於“隨即分配一些數字給小圖片,然後依靠數字大小來確定小圖片的位置”一類的演算法。
1: private void CheckAndSwap(int column, int row)
2: {
3: if (row > 0 &&tilesArray[column, row - 1] == null)
4: {
5: tilesArray[column, row - 1] =tilesArray[column, row];
6: tilesArray[column, row] =null;
7: }
8: else if (column > 0&& tilesArray[column - 1, row] == null)
9: {
10: tilesArray[column - 1, row] =tilesArray[column, row];
11: tilesArray[column, row] =null;
12: }
13: else if (row < Rows - 1&& tilesArray[column, row + 1] == null)
14: {
15: tilesArray[column, row + 1] =tilesArray[column, row];
16: tilesArray[column, row] =null;
17: }
18: else if (column < Columns - 1&& tilesArray[column + 1, row] == null)
19: {
20: tilesArray[column + 1, row] =tilesArray[column, row];
21: tilesArray[column, row] =null;
22: }
23: }
CheckAndSwap 方法的引數column(列)和 row(行)是使用者現在點選的小圖片的位置。在裡面使用了if-else語句判斷了越界比如說點選的是“空”小圖片(空小圖片的值是null),然後確定點選的是不是空圖片的旁邊的圖片,如果是的話交換它們(這也是遊戲規則)。
1: private bool CheckIfPlayerWins()
2: {
3: bool playerWins = true;
4:
5: for (int column = 0; column <Columns; column++)
6: for (int row = 0; row <Rows; row++)
7: {
8: if (tilesArray[column,row] == null) continue; //if we are at the empty tile, just continue
9: if (tilesArray[column,row].OriginalLocationInPuzzle != row * Columns + column + 1)
10: {
11: playerWins = false;
12: break;
13: }
14: }
15:
16: return playerWins;
17: }
CheckIfPlayerWins 方法是為了確定玩家是不是勝利了,它只是簡單的比較每一張小圖片的LocationInPuzzle屬性,來確定小圖片是不是在最初的位置,如果全部都是那麼返回true,玩家就勝利了。
1: private void DrawTiles()
2: {
3: for (int column = 0; column <Columns; column++)
4: {
5: for (int row = 0; row <Rows; row++)
6: {
7: if (tilesArray[column,row] == null) continue;
8:
9: spriteBatch.Draw(fullPicture,
10: new Vector2(column *TileWidth, row * TileHeight),
11: newRectangle((int)tilesArray[column, row].TextureLocationInSpriteSheet.X,
12: (int)tilesArray[column, row].TextureLocationInSpriteSheet.Y,
13: TileWidth,TileHeight),
14: Color.White,
15: 0f, //rotation
16: Vector2.Zero,//origin
17: 1,//scale
18: SpriteEffects.None,
19: 0);
20: }
21: }
22: }
你還記得我們說的spritesheet 嗎?上面這個方法就使用了它,SpriteBatch.Draw方法的第三個引數使用了 Rectangle 來表示我們想要畫出的小圖片的位置。很簡單吧?嘻嘻
因為它們都比較簡單,所以我就不再這裡列出其它的繪圖方法了。在我結束這篇文章之前,我們還需要討論一下其它的內容。
SpriteBatch.DrawLine
在XNA框架裡並沒有方法SpriteBatch.DrawLine。那麼,你需要XNA畫線呢?回到文章剛開始的地方,你能看到第二張圖片裡有白色的線。怎麼畫出來的呢?我只是匯入了一個1x1的圖片在我的工程裡(pixel.png)。為了達到畫線的目的,我們調整SpriteBatch.Draw 方法的第二個引數Rectangle 並使用了vertical2和4根水平線,來畫出我們的子圖形(白色的這個,也就是白線的實現)。
1: private void DrawLines()
2: {
3: // 畫第一條垂直線
4: spriteBatch.DrawLine(newVector2(TileWidth - 1, 0), 2, Game.GraphicsDevice.Viewport.Height,
5: Color.White, 100, 0,SpriteEffects.None, 0);
6: // 畫第二條垂直線
7: spriteBatch.DrawLine(newVector2(2 * TileWidth - 1, 0), 2, Game.GraphicsDevice.Viewport.Height,
8: Color.White, 100, 0,SpriteEffects.None, 0);
9: // 畫第一條水平線
10: spriteBatch.DrawLine(newVector2(0,TileHeight - 1), Game.GraphicsDevice.Viewport.Width, 2,
11: Color.White, 100, 0,SpriteEffects.None, 0);
12: // 畫第二條水平線
13: spriteBatch.DrawLine(newVector2(0, 2* TileHeight - 1), Game.GraphicsDevice.Viewport.Width, 2,
14: Color.White, 100, 0,SpriteEffects.None, 0);
15: // 畫第三條水平線
16: spriteBatch.DrawLine(newVector2(0, 3 * TileHeight - 1), Game.GraphicsDevice.Viewport.Width, 2,
17: Color.White, 100, 0,SpriteEffects.None, 0);
18: // 畫第四條水平線
19: spriteBatch.DrawLine(newVector2(0, 4 * TileHeight - 1), Game.GraphicsDevice.Viewport.Width, 2,
20: Color.White, 100, 0,SpriteEffects.None, 0);
21: }
但是我只是說在SpriBatch裡沒有DrawLine方法。在哪裡能找到它?答案是C#裡面一個神奇的特性繼承方法( extensionmethods)。我不會在這裡詳細的列出C#的特性,但會簡要的提一下這些非常好的特性。如果你想在現有類新增更多的功能,並沒有打破封裝哦(這點很重要)。讓我們看一下繼承實現DrawLine 方法的類:
1: static class Extensions
2: {
3:
4: public static Texture2D LineTexture { get; set; }
5:
6: public static void DrawLine(this SpriteBatch spriteBatch, Vector2position, int width, int height,
7: Color color, byte opacity, floatrotation, SpriteEffects effects, float layerDepth)
8: {
9: color.A = opacity;
10: spriteBatch.Draw(LineTexture,
11: newRectangle((int)position.X,(int)position.Y,width,height),
12: null,
13: color,
14: rotation,
15: Vector2.Zero,
16: effects,
17: layerDepth);
18: }
19:
20:
21: }
這裡建立的LineTexture 靜態引用請參考PuzzleGame類中的 LoadContent 方法。這個 DrawLine 方法只是簡單的呼叫了SpriBatch.Draw 方法, 第二個引數被用來壓縮我們要畫出的物件的空間。
更新: Simon Jackson 提出了一個觀點, 我們並不需要那個1x1的pixel檔案,我們也許會建立一個新的Texture2D物件,用它來畫出線!樣例程式碼:
BlankTexture =new Texture2D(GraphicsDevice, 1, 1);
BlankTexture.SetData(newColor[] { Color.White });
Source code
這個程式碼依舊是開源的! https://skydrive.live.com/?sc=documents#cid=10E568ADBB498DC8&id=10E568ADBB498DC8%211600&sc=documents
希望你能喜歡它!
博文來源
這是一個自由的技術部落格!
翻譯:石拓
相關文章
- 簡單XNA_Windows Phone 7 3D動畫Windows3D動畫
- 《Windows Phone 7入門經典之使用Silverlight和XNA開發Windows Phone應用》書評Windows
- 遊戲開發者:8.8%的開發者為Windows Phone開發遊戲遊戲開發Windows開發遊戲
- 用 JavaScript 實現簡單拼圖遊戲JavaScript遊戲
- Windows Phone 7解析圖片格式Windows
- 開發微軟XBox遊戲-XNA開發平臺簡介(轉)微軟遊戲
- 一起學Windows Phone7開發(十四.一 Phone Task)Windows
- Windows Phone7開發系列視訊地址Windows
- Windows Phone 7 開發 31 日談——第21日:Silverlight Toolkit for Windows PhoneWindows
- Windows Phone 7 開發 31 日談——第22日:應用?還是 遊戲?Windows遊戲
- Windows Phone 7 開發 31 日談——第20日:地圖控制元件Windows地圖控制元件
- Java 簡單拼圖遊戲(實現音樂播放功能)Java遊戲
- Windows Phone 7 點陣圖程式設計Windows程式設計
- Windows Phone 7 開發 31 日談——第7日:啟動器Windows
- Windows phone 應用開發[9]-單元測試Windows
- 【使用Unity開發Windows Phone上的2D遊戲】(2)初識工具UnityWindows遊戲
- Windows Phone 7 開發 31 日談——第25日:外部APIWindowsAPI
- Windows Phone 7 開發 31 日談——第24日:嵌入字型Windows
- Windows Phone 7 開發 31 日談——第19日:推送通知Windows
- Windows Phone 7 開發 31 日談——第3日:返回鍵Windows
- Windows Phone 8開發連結Windows
- windows phone資料庫開發Windows資料庫
- Windows Phone 8 開發筆記Windows筆記
- 探索Windows Phone 7的單元測試(翻譯)Windows
- Windows Phone 7 開發 31 日談——第16日:全景檢視Windows
- Windows Phone 7 開發 31 日談——第13日:位置服務Windows
- Windows Phone 7 開發 31 日談——第8日:選擇器Windows
- Windows Phone 7 開發 31 日談——第4日:裝置方向Windows
- Windows Phone 7 開發 31 日談——第1日:專案模板Windows
- 開發WP7專案的好工具: Windows Phone CommandsWindows
- Windows Phone 7 墓碑機制Windows
- Windows Phone 7程式設計Windows程式設計
- 使用Python開發windows桌面程式【超簡單】PythonWindows
- Windows Phone開發之輸入範圍InputScope的使用Windows
- XNA開發一個2D的小遊戲遊戲
- Windows Phone 7 開發 31 日談——第18日:WebBrowser控制元件WindowsWeb控制元件
- Windows Phone 7 開發 31 日談——第15日:獨立儲存Windows
- Windows Phone 7 開發 31 日談——第11日:加速感應器Windows