XNA游戏开发之2D游戏

摘要:以XNA为基础的游戏可以利用3D模型为游戏加入动画效果,也可以利用简单的程序技巧将2维图片显示成动画。虽然2维动画相对3维动画来说简单一些,但是制作出来的游戏其趣味性和挑战性也绝不逊色。今天我们就一块学习一下在使用XNA Framework开发2D游戏时的一些基础知识和注意事项。

主要内容:

1.2D游戏动画的基本原理

2.动画素材的准备

3.一个简单的2D动画

一、2D游戏动画的基本原理

在XNA中制作2D动画的过程很像翻卡通小人书,首先绘制好各种角色造型,然后以固定的时间间隔来显示不同的图片,当时间间隔小到一定程度时人眼就难以区分,从而形成一种动画效果。如果做过flash或者使用fireworks做过gif的朋友可能更容易理解,它就像flash中的逐帧动画一样,每帧显示一个图片,然后连起来就形成了动画效果。由于在XNA中有SpriteBatch可以绘制图片精灵,这样一来我们只需事先做好不同的角色造型的图片,每次在执行Update方法的时候改变绘制的精灵图片就可以制作出动画效果。

二、动画素材的准备

XNA开发2D动画的原理比较容易理解,我也就不过多的赘余了,现在我们就开始准备游戏素材。XNA的SpriteBatch的绘制方法有点类似于样式表CSS的background属性,可以通过设置参数来显示一个图片的其中一部分,这样一来我们为了方便管理游戏角色就只需要将同一角色的不同造型放到同一张图片中,每次更新时显示图片的不同部分就可以了。例如下面这幅图片就是如此:

精灵图片

在这幅图片中我们将22个不同造型的角色放到同一幅图片中,事先规划好每个小图片的大小,保证各个图片连接起来播放时能够形成一组连贯的动作。当然,为了让整个效果看起来更生动一些我们再来准备一张背景图片:

游戏背景

三、一个简单的2D动画

有了上面的游戏素材,我们就来简单模拟一下植物大战僵尸中的一个场景。在下面的Demo中表现了两种运动:其一就是我们要控制22个小图片之间的切换,以此产生角色行动的动画(在原地);其二就是控制图片切换的同时我们需要移动图片的位置从而形成角色在水平方向上行走动作。具体代码如下:

复制代码
 
   
1 using System;
2   using System.Collections.Generic;
3 using System.Linq;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Audio;
6 using Microsoft.Xna.Framework.Content;
7 using Microsoft.Xna.Framework.GamerServices;
8 using Microsoft.Xna.Framework.Graphics;
9 using Microsoft.Xna.Framework.Input;
10 using Microsoft.Xna.Framework.Input.Touch;
11 using Microsoft.Xna.Framework.Media;
12 namespace _2DAnimation
13 {
14 public class MyGame : Microsoft.Xna.Framework.Game
15 {
16 GraphicsDeviceManager graphics;
17 SpriteBatch spriteBatch;
18 private Texture2D corpse; // 图片资源
19 private Point frameSize; // 每个小图片大小
20 private Point sheetSize; // 小图片个数
21 private Point currentFrame; // 当前小图片位置
22 private Vector2 postion; // 角色所在位置
23 private float speed; // 角色行走速度
24 private Texture2D background;
25 public MyGame()
26 {
27 graphics = new GraphicsDeviceManager( this );
28 Content.RootDirectory = " Content " ;
29 graphics.PreferredBackBufferWidth = 800 ; // 设置游戏视窗大小
30 graphics.PreferredBackBufferHeight = 480 ;
31 TargetElapsedTime = TimeSpan.FromTicks( 333333 * 2 );
32 }
33 protected override void Initialize()
34 {
35 frameSize = new Point( 100 , 125 );
36 sheetSize = new Point( 11 , 2 );
37 currentFrame = new Point( 0 , 0 );
38 postion = new Vector2(graphics.PreferredBackBufferWidth - 100 ,graphics.PreferredBackBufferHeight - 125 - 150 );
39 speed = 3 ;
40 base .Initialize();
41 }
42 protected override void LoadContent()
43 {
44 spriteBatch = new SpriteBatch(GraphicsDevice);
45 background = this .Content.Load < Texture2D > ( " background " ); // 加载游戏背景
46 corpse = this .Content.Load < Texture2D > ( " transparentCorpse " ); // 加载整张大图片(透明背景)
47 }
48 protected override void UnloadContent()
49 {
50 }
51 protected override void Update(GameTime gameTime)
52 {
53 if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
54 this .Exit();
55 ++ currentFrame.X;
56 if (currentFrame.X >= sheetSize.X)
57 {
58 currentFrame.X = 0 ;
59 ++ currentFrame.Y;
60 if (currentFrame.Y >= sheetSize.Y)
61 {
62 currentFrame.Y = 0 ;
63 }
64 }
65 postion.X -= speed;
66 if (postion.X < 20 )
67 {
68 this .Exit(); // 角色进入房屋后退出游戏
69 }
70 base .Update(gameTime);
71 }
72 protected override void Draw(GameTime gameTime)
73 {
74 GraphicsDevice.Clear(Color.CornflowerBlue);
75 spriteBatch.Begin(SpriteSortMode.BackToFront,BlendState.NonPremultiplied); // 所有的绘制工作均在sprite的begin和end中间进行
76 spriteBatch.Draw(
77 corpse, // 要绘制的精灵
78 postion, // 绘制的位置
79 new Rectangle(currentFrame.X * frameSize.X, currentFrame.Y * frameSize.Y, frameSize.X, frameSize.Y), // 绘制精灵的哪一部分
80 Color.White, // 颜色
81 0 , // 旋转角度
82 Vector2.Zero, // 旋转参考点
83 1 , // 缩放倍数
84 SpriteEffects.None, // 翻转
85 0 // 层深
86 );
87 spriteBatch.Draw(background, Vector2.Zero, Color.White);
88 spriteBatch.End();
89 base .Draw(gameTime);
90 }
91 }
92 }
复制代码

代码总体来说还是比较简单,主要通过在Update方法中动态修改currentFrame来形成图片切换,当然之所以这样是因为Draw方法的第三个参数,通过它我们才能做到一张图片的局部显示(具体来说就是在Rectangle中指定corpse精灵的起始坐标和要显示的图片宽度和高度)。图片的水平移动主要通过在Update中动态修改position变量的值,当然这里需要注意图片超出游戏视窗的情况。下面是运行效果:

截图效果:

游戏截图效果

动画效果:

游戏动画效果

值得一提的是,我们这里牵扯到了图片显示的层深问题,如果注意看代码的朋友会发现我们并没有首先绘制游戏背景,而是先绘制了游戏角色(当然我是为了说明层深问题而故意这么做的)。默认情况下载XNA会按照程序调用顺序绘制精灵,这样我们就看不到角色了,因为它被背景图片遮住了。如果说遇到了这种情况(事实上游戏开发中经常遇到)我们就需要设置图片的层深,也就是我们上面代码中Draw方法的最后一个参数。它是float类型,其取值范围是0-1。具体0是代表最先绘制还是最后绘制则完全取决于SpriteBatch的Begin方法中的第一个参数,这个参数是SpriteSortMode,他是一个枚举类型,有五种模式:

取值

描述

Deferred

这是默认模式,此模式下只有在SpriteBatch调用End方法时才会被绘制,如果绘制多个精灵则按照调用次序依次进行。

Texture

同Deferred类似,在绘制之前会按照纹理排序,对于相同深度而不互相重叠的情况下效果较好。

BackToFront

在End方法执行后绘制精灵,绘制精灵的顺序按照层深度由前往后排序(先绘制层深值较小的)。

FrontToBack

在End方法执行后绘制精灵,绘制精灵的顺序按照层深度由后往前排序(先绘制层深值较大的)。

Immediate

调用Begin方法后就会立即绘制精灵(这也是五种模式中唯一在End方法之前就进行绘制的),同一时间只有一个SpriteBatch被使用。

另一点需要注意的是,我们的角色是透明的,在XNA Game开发中如果想要让图片透明有两种方式:第一就是图片本省就是透明,此时当然需要选用能够存储图像alpha的图片格式来保存图片(例如png格式可以将图像的alpha存储到图片信息中,我们上的例子中就是这么做的。事实上GIF也可以,但是在XNA中默认不支持GIF);第二种方式就是将图片需要显示成透明的部分设置为洋红色(RGB:255,0,255),这样XNA会自动将洋红色部分渲染成透明。

OK,最后附上代码下载:

Download