您的位置:首页 > 其它

使用XNA为Windows phone 7开发简单拼图游戏

2012-07-14 23:00 435 查看
使用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
希望你能喜欢它!


博文来源

本博文转载自:
http://studentguru.gr/b/dt008/archive/2010/09/14/simple-puzzle-game-for-windows-phone-7-using-xna.aspx
这是一个自由的技术博客!
感谢作者Δημήτρης Γκανάτσιος
翻译:石拓
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐