摘要
上一回我们为大家介绍了如何使用 Visual Studio 2010 或 Visual Studio 2010 Express for Windows Phone 设计以 XNA 为基础的 Windows Phone 7 游戏程序,并了解以 XNA 为基础的游戏程序的基础架构与核心功能。这一回我们将要为大家介绍 XNA Framework 支持游戏程序制作的重要类别,做为开发游戏程序的基础知识。除此之外,我们也将利用本文所介绍的类别实作一个简单的游戏程序,并令其具有能够不断卷动的背景图案。
GraphicsDeviceManager 类别
第一个要介绍给大家认识的是负责管理绘图装置的 GraphicsDeviceManager 类别。以 XNA 为基础的游戏程序必须在初始化的时候建立妥 GraphicsDeviceManager 类别的对象,并设定包括游戏程序窗口高度与宽度在内的必要属性,做为显示游戏内容的基础。有关于游戏程序初始化阶段建立 GraphicsDeviceManager 类别的对象的详细做法可以参考 [设计以 XNA 为基础的 Windows Phone 7 游戏]一文的说明。表1 所示即为 GraphicsDeviceManager 类别常用的属性:
属性名称 | 说明 |
IsFullScreen | 控制游戏程序的窗口是否要以全屏幕的方式显示 |
PreferredBackBufferFormat | 屏幕缓冲区的格式 |
PreferredBackBufferHeight | 屏幕缓冲区的高度 |
PreferredBackBufferWidth | 屏幕缓冲区的宽度 |
GraphicsDeviceManager 类别常用的方法请参考表2 的说明:
方法名称 | 说明 |
ToggleFullScreen | 在窗口模式和全屏幕模式中切换 |
游戏核心类别
以 XNA 为基础的游戏程序的主体是 Game 类别,也是做为游戏程序主体的 Game1 类别的基础类别。游戏程序可以利用 Game1 类别的 Update 方法更新游戏的状态,利用 Draw 方法显示游戏的内容。表3 所示即为 Game 类别的常用属性:
属性名称 | 说明 |
Components | 管理所有 GameComponent 的集合 |
Content | 取得 ContentManager 对象的属性 |
GraphicsDevice | 取得图型装置对象的属性 |
IsActive | 判断游戏程序的窗口目前是否在作用中 |
IsFixedTimeStep | 控制游戏程序要使用固定更新模式或是可变更新模式 |
TargetElapsedTime | 当 IsFixedTimeStep 属性的内容值为 true 时,控制 Update 方法被呼叫的频率的属性 |
Game 类别常用的方法可以参考表4 的说明:
方法名称 | 说明 |
Exit | 结束游戏程序的执行 |
BeginDraw | 宣告绘制图形的动作开始 |
EndDraw | 宣告绘制图形的动作结束 |
Draw | 执行绘制游戏内容的动作 |
LoadContent | 执行加载游戏资源的方法 |
UnloadContent | 执行释放游戏资源的方法 |
Update | 负责更新游戏状态的方法 |
如果程序设计师要以模块化的方式设计游戏程序,可以将游戏的人物制作成 GameComponent 类别或是 DrawableGameComponent 类别,再将 GameComponent 类别的对象或是 DrawableGameComponent 类别的对象加入到 Game 类别的对象的 Conponents 集合中,由 Game 类别的对象统一管理,把复杂的游戏人物的初始化、状态更新、以及更新显示等动作封装在 GameComponent 类别或是 DrawableGameComponent 类别中,可以有效简化 Game 类别的控制逻辑。当游戏人物众多,角色复杂时,适当地利用 GameComponent 类别或是 DrawableGameComponent 类别可以提升游戏的可维护性,利于发展功能进阶,效果复杂的游戏。
XNA Framework 提供的 GameComponent 类别和 DrawableGameComponent 类别的差别在于 DrawableGameComponent 类别管理的是有用户接口的游戏组件,而 GameComponent 类别所管理的则是没有用户接口的游戏组件。换句话说,DrawableGameComponent 类别提供了 GameComponent 类别未支持的 LoadContent 方法和 Draw 方法,以便执行加载所管理的游戏资源和显示游戏的内容的工作。
当建立好 GameComponent 类别的对象或 DrawableGameComponent 类别的对象之后,只要将 GameComponent 类别的对象或 DrawableGameComponent 类别的对象加入到 Game 类别的对象的 Components 集合中,GameComponent 类别的对象或 DrawableGameComponent 类别的对象的 Update 方法就会被持续地呼叫,而 DrawableGameComponent 类别的对象的 Draw 方法也会在适当的时机被呼叫,并将游戏的内容显示到窗口中供用户操作。
欲为游戏程序项目加入 GameComponent 类别或 DrawableGameComponent 类别,可以使用鼠标的右键点中 [Solution Explorer] 窗口中的项目名称,从出现的菜单选择 [Add | New Item] 功能,屏幕上就会出现要求选择欲加入到项目的项目的窗口,如图1 所示:
图1:要求选择欲加入到项目的项目的窗口
请于中间的窗口选择 [Game Component] 项目,于 [Name] 字段输入文件名,然后按下 [Add] 键,就可以为项目加入一个继承自 GameComponent 类别的衍生类别。
[注意]
继承自 GameComponent 类别的衍生类别无法覆写基础类别的 LoadContent 方法和 Draw 方法,适合用来制作没有用户接口的游戏组件。如果需要制作提供用户接口的游戏组件,可以在为项目加入继承自 GameComponent 类别的衍生类别之后,自行将所继承的 GameComponent 基础类别修改成 DrawableGameComponent 类别,就可以覆写基础类别的 LoadContent 方法和 Draw 方法,分别负责执行加载所管理的游戏资源,和绘制游戏内容的工作。
以下就是在继承自 DrawableGameComponent 类别的衍生类别覆写基础类别的 LoadContent 方法和 Draw 方法的范例:
public class GameComponent1 : Microsoft.Xna.Framework.DrawableGameComponent { … protected override void LoadContent() //覆寫 LoadContent 方法 { base.LoadContent(); } public override void Draw(GameTime gameTime) //覆寫 Draw 方法 { base.Draw(gameTime); } }
GameComponent 类别常用的属性可以参考表5 的说明:
属性名称 | 说明 |
Enabled | 控制 GameComponent 类别的对象是否启用的属性。当对象启用时,类别的 Update 方法就会被定时地呼叫 |
UpdateOrder | 控制 Update 方法被呼叫的顺序。内容值小的对象的 Update 方法会优先被呼叫 |
GameComponent 类别常用的方法详见表6 的说明:
方法名称 | 说明 |
Initialize | 负责执行 GameComponent 类别的对象初始化的方法 |
Update | 负责更新 GameComponent 类别的对象的状态的方法 |
DrawableGameComponent 类别的功能和 GameComponent 类别的功能类似,差别在于 DrawableGameComponent 类别可以用来管理具用户接口的游戏组件。DrawableGameComponent 类别常用的属性请参考表7 的说明:
属性名称 | 说明 |
Enabled | 控制 DrawableGameComponent 类别的对象是否启用的属性。当对象启用时,类别的 Update 方法就会被定时地呼叫 |
Visible | 控制 DrawableGameComponent 类别的对象是否隐藏的属性,对象隐藏时,类别的 Draw 方法将不会被呼叫 |
UpdateOrder | 控制 Update 方法被呼叫的顺序。内容值小的对象的 Update 方法会优先被呼叫 |
DrawOrder | 控制 Draw 方法被呼叫的顺序。内容值小的对象的 Draw 方法会优先被呼叫 |
Game | 取得管理 DrawableGameComponent 类别的对象的Game类别对象的属性 |
GraphicsDevice | 取得游戏程序使用的绘图装置的属性 |
DrawableGameComponent 类别常用的方法请参考表8 的说明:
方法名称 | 说明 |
Initialize | 负责执行 GameComponent 类别的对象初始化的方法 |
LoadContent | 负责加载欲使用的游戏资源 |
UnloadContent | 负责释放欲使用的游戏资源 |
Update | 负责更新游戏的状态 |
Draw | 负责显示游戏的内容 |
制作好 GameComponent 类别或是 DrawableGameComponent 类别之后,我们就可以在 Game1 类别的建构函式中建立 GameComponent 类别或是 DrawableGameComponent 类别的对象,再将建立好的对象加入到 Game1 类别的对象的 Components 集合中,游戏程序便会定时地呼叫 GameComponent 类别或是 DrawableGameComponent 类别的Update方法,以及 DrawableGameComponent 类别的 Draw 方法,达到更新游戏状态和显示游戏内容的效果。假设游戏项目中已经制作好继承自 GameComponent 类别的衍生类别,名称为 GameComponent1,以及继承自 DrawableGameComponent 类别的衍生类别,名称为 DrawableGameComponent1,则 Game1 类别的建构函式就可以写成以下的样子:
public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferHeight = 480; graphics.PreferredBackBufferWidth = 800; Components.Add(new GameComponent1(this)); Components.Add(new DrawableGameComponent1 (this)); // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); }
[提示]
XNA Framework 支持的 DrawableGameComponent 类别虽然可以用来管理游戏程序的人物,但是却没有记录游戏人物坐标位置的属性,程序设计师可以自行于继承自 DrawableGameComponent 类别的衍生类别加入负责记载游戏人物坐标的属性,以形成一个完整的游戏组件,利于游戏程序进行定位与移动。
显示 2D 与 3D 图形
以 XNA 为基础的游戏程序可以利用 Texture2D 类别管理 2D 图形资源,利用 Model 类别管理 3D 模型。用来管理 2D 图形资源的 Texture2D 类别常用的属性可以参考表9 的说明,用来管理 3D 模型的 Model 类别的常用属性可以参考表10 的说明:
属性名称 | 说明 |
Bounds | 代表图形资源的大小 |
Format | 代表图形资源的格式 |
GraphicsDevice | 取得游戏程序使用的绘图装置的属性 |
Height | 图形资源的高度(单位:pixel) |
Width | 图形资源的宽度(单位:pixel) |
属性名称 | 说明 |
Bones | 3D 模型中的骨骼数据的集合 |
Meshes | 3D 模型中的网格数据的集合 |
Root | 取得最根源的骨骼数据的属性 |
坐标
当以 XNA 为基础的游戏程序欲将所加载的 2D 或 3D 图形资源显示到游戏的画面时,我们就会需要用到和坐标有关的型态。XNA 支持的常用坐标类别包括定义 2 维空间坐标点的 Vector2 结构,定义 3 维空间坐标点的 Vector3 结构,以及定义齐次坐标系统 (Homogenous Coordinate System) 坐标点的 Vector4 结构。
Vector2 架构的是二维空间的坐标,坐标原点默认在窗口的左上角,如图2 所示:
图2:二维空间的坐标系统
Vector2 结构的数据成员请参考表11 的说明:
数据成员名称 | 说明 |
X | 代表坐标点的X轴的位置 |
Y | 代表坐标点的Y轴的位置 |
Vector2 结构常用的方法请参考表12 的说明:
方法名称 | 说明 |
Add | 对坐标点执行加法运算 |
Clamp | 限制坐标内容值必须落在指定的范围之间 |
Distance | 计算两个坐标点之间的距离 |
DistanceSquared | 计算两个坐标点之间的距离的平方 |
Divide | 对坐标点执行除法运算 |
Equals | 判断坐标点是否等于指定的坐标点 |
Lerp | 计算两个坐标点之间的线性内插 |
Max | 计算坐标点的最大值 |
Min | 计算坐标点的最小值 |
Multiply | 对坐标点执行乘法运算 |
Negate | 对坐标点执行反运算 |
Subtract | 对坐标点执行减法运算 |
Transform | 对坐标点执行转置 (Transform) 运算 |
Vector3 架构的是三维空间的坐标,因为 XNA Framework 支持右手坐标系统 (Right-Handed System),其 Z 轴往使用者的方向递增,如图3 所示:
图3:XNA 支持的右手坐标系统
Vector3 结构的数据成员可以参考表13 的说明:
数据成员名称 | 说明 |
X | 代表坐标点的X轴的位置 |
Y | 代表坐标点的Y轴的位置 |
Z | 代表坐标点的Z轴的位置 |
Vector3 结构常用的方法和 Vector2 结构常用的方法类似,但是有提供能够计算外积 (Cross Product) 的 Cross 方法。
Vector4 架构的是齐次坐标系统 (Homogeneous Coordinate System) 的坐标点,第四个坐标并非用来代表几何空间的位置,而是用来表示坐标轴的远近参数。
Vector4 结构的数据成员可以参考表14 的说明:
数据成员名称 | 说明 |
X | 代表坐标点的 X 轴的位置 |
Y | 代表坐标点的 Y 轴的位置 |
Z | 代表坐标点的 Z 轴的位置 |
W | W 元素。W 元素并不是用来代表几何空间的位置,而是为了定义转置矩阵的设计,利于执行平行、放大、与旋转的处理 |
Vector4 结构常用的方法和 Vector2 结构常用的方法类似,可以直接参考表12 的说明。
卷动游戏程序背景的做法
了解了 XNA Framework 支持游戏设计的常用类别之后,接下来我们就要使用这些类别为游戏程序制作一个可以自动卷动的背景,就像超级玛璃的游戏一样,在游戏进行时不断地卷动。
要制作游戏的卷动背景,可以准备一张以上,组合起来会变成完整的背景图的背景拼图,例如以下就是由三张连续的背景拼图组成的背景图。
图4:由三张连续的图片拼凑而成背景图
[说明]
游戏的背景通常是由多张图片拼凑而成,一张接着一张不断地轮播,周而复始。请注意除非背景图的内容永远不会重复,否则不要使用单一张完整的背景图片当做游戏的背景,就算要制作背景图内容永远不重复的游戏,也会因为单一张完整的图片体积过积而不切实际。
准备好当做游戏背景的背景拼图之后,请启动 Visual Studio 2010 Express for Windows Phone,建立一个 [Windows Phone Game(4.0)] 型态的项目,并把准备好的背景拼图加入到 [Solution Explorer] 窗口中的 Content Pipeline 项目中,以便被游戏程序加载使用。
建立好项目之后请开启 Game1.cs 原始程序档案进行编辑,于 Game1 类别中加入以下的变量宣告,分别用来管理当做游戏背景的拼图,以及记载背景拼图位置的变量:
Texture2D Background1; //管理第一張背景圖片的變數 Texture2D Background2; //管理第二張背景圖片的變數 Texture2D Background3; //管理第三張背景圖片的變數 Vector2 Position1; //第一張背景圖片的顯示座標 Vector2 Position2; //第二張背景圖片的顯示座標 Vector2 Position3; //第三張背景圖片的顯示座標
请注意上述的变量宣告可以处理由三张图片拼凑而成的游戏背景,如果游戏背景是由更多图片组成,可以宣告适量的变量以管理组成背景的图片。
宣告好必要的变量之后,请修改 Game1 类别的建构函式,加入设定游戏窗口大小的程序代码,修改好的建构函式如下:
public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = 480; //設定遊戲視窗的寬度為 480 graphics.PreferredBackBufferHeight = 800; //設定遊戲視窗的寬度為 400 // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); }
设定好游戏窗口的大小之后,请编辑 Game1 类别的 LoadContent 方法,负责加载当做背景图片的图形,并设定妥背景图片的显示位置,编辑好的 LoadContent 方法如下:
protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); // TODO: use this.Content to load your game content here Background1 = Content.Load<Texture2D>("Background01"); //載入第一張背景圖片 Position1 = new Vector2(0, 0); //設定第一張背景圖片的顯示位置 Background2 = Content.Load<Texture2D>("Background02"); //載入第二張背景圖片 Position2 = new Vector2(Position1.X + Background1.Width, 0); //設定第二張背景圖片要顯示在第一張背景圖片的右方 Background3 = Content.Load<Texture2D>("Background03"); //載入第三張背景圖片 Position3 = new Vector2(Position2.X + Background2.Width, 0); //設定第三張背景圖片要顯示在第二張背景圖片的右方 }
如果读者准备的背景图片的文件名和上述的范例程序代码所指定的文件名不同,请依据所准备的图形文件名修改上述指定加载的文件名。
完成加载游戏程序的背景图案之后,接下来我们必须修改 Game1 类别的 Update 方法,更新背景图片显示的位置,创造出背景卷动的效果。
如果要将游戏的背景由右往左卷动,则每一次 Update 方法被呼叫的时候必须递减所显示的背景图片的左上角点的 X 坐标的内容值,令背景图片产生往左移动的效果。反之,如果要将游戏的背景由左往右卷动,则每一次 Update 方法被呼叫的时候必须递增所显示的背景图片的左上角点的X坐标的内容值,就能够令背景图片产生往右移动的效果。请注意如果背景图是由右往左卷动,当最左边背景图卷动到游戏的窗口之外时,必须自动补到最右边的图的右边,背景图才能够周而复始地不断地卷动,反之亦然。图5 所示即为最左边的背景图卷动到游戏窗口的范围之外时,自动接续到最右方的背景图的右边继续显示的示意图:
图5:卷动背景图的做法
了解卷动背景图的做法之后,请将 Game1 类别的 Update 方法修改成以下的样,以由右往左的方式卷动背景图:
protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // TODO: Add your update logic here if (Position1.X < -Background1.Width) //如果第一張背景圖離開遊戲的視窗 { Position1.X = Position3.X + Background3.Width; //令其顯示在第三張背景圖的右方的位置 } if (Position2.X < -Background2.Width) //如果第二張背景圖離開遊戲的視窗 { Position2.X = Position1.X + Background1.Width; //令其顯示在第一張背景圖的右方的位置 } if (Position3.X < -Background3.Width) //如果第三張背景圖離開遊戲的視窗 { Position3.X = Position2.X + Background2.Width; //令其顯示在第二張背景圖的右方的位置 } int aSpeed = 2; //設定每次捲動的單位距離 Position1.X -= aSpeed; //令第一張背景圖往左移動指定的距離 Position2.X -= aSpeed; //令第二張背景圖往左移動指定的距離 Position3.X -= aSpeed; //令第三張背景圖往左移動指定的距離 base.Update(gameTime); }
[提示]
读者只要稍加修改上述的 Update 方法,就可以令背景图由左往右移动。如果要升高或降低背景的卷动速度,可以修改 aSpeed 变量的内容值。
最后我们只要将移动过的背景图显示到游戏的窗口,就可以令游戏程序的背景产生卷动的效果。请修改 Game1 类别的 Draw 方法,将每一张背景图显示在指定的位置,修改妥的 Draw 方法如下:
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // TODO: Add your drawing code here spriteBatch.Begin(); spriteBatch.Draw(Background1, Position1, Color.White); //在 Position1 的位置顯示第一張背景圖 spriteBatch.Draw(Background2, Position2, Color.White); //在 Position2 的位置顯示第二張背景圖 spriteBatch.Draw(Background3, Position3, Color.White); //在 Position3 的位置顯示第三張背景圖 spriteBatch.End(); base.Draw(gameTime); }
做好之后请按下 CTRL+F5 组合键执行项目,所制作好的游戏便会部署到 Windows Phone 7 的仿真器中执行,您将可以看到背景图由右往左卷动的情形,其执行的画面如图6 所示:
图6:背景图由右往左卷动的游戏程序执行的情形
范例下载:ScrollBackgound.zip
[结语]
这一次的文章中我们为大家介绍了 XNA Framework 支持游戏程序制作的重要类别,做为开发游戏程序的基础知识。除此之外,我们也利用本文所介绍的类别实作一个简单的游戏程序,并令其具有能够不断卷动的背景图案。下一回我们将会介绍更多的类别,让读者可以为所制作的游戏程序加入更丰富的游戏效果。