您已经准备好了一切,现在就让我们开始编码吧!本节您将在XNA Studio模板的帮助下创建一个简单的游戏项目,然后在Update和Draw方法中添加少量代码以实现一些小功能。在下一小节学习了SpriteBatch类之后,您将创建您的第一个游戏。
打开XNA Game Studio Express,在菜单栏中选择“文件→新建项目”,在弹出的新窗口中选择“Windows Game”模板,然后在项目名称处输入新项目的名称,比如“HelloWorld”或者就使用默认的名称“WindowsGame1”,在位置处输入新项目的保存位置,最后点击“确定”按钮。另外,在该窗口中还可以创建基于初学者工具包的游戏项目,比如“Spacewar Windows Starter Kit”。如下图1-10所示:
在新创建的项目中包含两个类文件:Game1.cs和Program.cs。其中Program.cs文件包含了该应用程序的入口点Main方法,如下所示:
using (Game1 game = new Game1()) { ..game.Run(); } // using
Game1.cs文件包含了类Game1(继承自类Microsoft.Xna.Framework.Game),它包含了之前讲过的三个方法:Initialize、Update和Draw。Initialize方法此时不执行任何操作,只是调用基类的Initialize方法进行基本的初始化操作。Update方法主要包含下面的几行代码,用来检查Gamepad上的“Back”按钮是否被按下,如果按下则退出游戏,否则调用基类的Update方法:
// Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
Draw方法主要执行一个操作,将游戏窗口的背景色设置成一个特定的颜色,如下所示:
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
现在按下键盘上的F5键,或者选择菜单栏上的“调试→启动调试”命令运行游戏,此时将弹出如图1-11-(1)所示的窗口,它的了蓝色背景就是在Draw方法中设置的。
您也可以把背景色改为绿色,比如Color.Green,然后再按F5运行程序,可以得到如图1-11-(2)所示的窗口,代码如下:
graphics.GraphicsDevice.Clear(ClearOptions.DepthBuffer, Color.Green, 1, 0);
参数ClearOptions的默认值是ClearOptions.Target | ClearOptions.DepthBuffer,意思是背景色和Depth缓存都被清除。
现在,您可以想一想如何修改您的代码来实现一些操作。比如,按下键盘上的Escape键可以退出游戏。之前说过,XNA默认的会在Update方法中检查Xbox 360控制器上的Back按钮是否被按下来决定是否退出游戏,代码如下:
// Allows the default game to exit on Xbox 360 and Windows if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit();
在“第三章-辅助类”中您将学习到Input类的使用,不过现在您可以使用一种快速的方法来操作键盘,下面的代码演示了当您按下Escape键时也能退出程序:
// Get current gamepad and keyboard states GamePadState gamePad = GamePad.GetState(PlayerIndex.One); KeyboardState keyboard = Keyboard.GetState(); // Back or Escape exits our game on Xbox 360 and Windows if (gamePad.Buttons.Back == ButtonState.Pressed || keyboard.IsKeyDown(Keys.Escape)) this.Exit();
第二章将详细讨论Sprites的知识,不过现在您可以稍微提前一点来学习在游戏中加载图像:给游戏加上一个背景图,然后操作键盘或者GamePad来移动该背景图,就像在一个竞速游戏或者Tile引擎中的一样,这些在很多2D的角色扮演游戏中经常会用到。对于更加复杂的Tile引擎,您还会需要石头、草、水、泥土等texture,甚至在草和水之间加一些过渡纹理(Transition Textures)。这将需要更多的texture和自定义代码,不过这也不是很难,如果您真的对Tile引擎感兴趣,您在网络上可以找到很多相关的信息。
要把这个texture(CityGroundSmall.jpg)加入到您的项目中,只需要在解决方案资源管理器(Solution Explorer)中把它拖拽到您的项目中即可,然后右键点击该文件查看“属性”,如图1-13所示:
通常您可能只会看到一些“生成操作(Build Action)”和“复制到输出目录”的选项(比如,您可能想把一个.dll或者.xml文件包含进来)。但如果XNA Studio检测到受支持的内容文件格式,您还会看到一些高级属性,此时会有另外三个重要设置:资源名称(Asset Name)、内容导入器(Content Importer)、内容处理器(Content Processor)。其中资源名称在将来选择加载资源时使用,而且内容文件的资源名称必须是唯一的,不能有重复。对于内容导入器,您可以像图1-13-(1)所示的情况中选择“Texture-XNA Framework”,或者为.x类型的文件选择“模型导入器(Model Importer)”,或者为.fx类型的文件选择“特效导入器(Effect Importer)”,如图1-13-(2)所示。
内容处理器则包含更多的选项,比如,为这里的texture(CityGroundSmall.jpg)可以选择“Texture (Sprite, 32bpp) - XNA Framework”或者“Texture (Model, DXT, mipmapped) - XNA Framework”。DXT是一种压缩格式,在dds类型的文件中也经常使用,而且在游戏中这种格式用于texture效果非常好,因为它的压缩比高达1:6(如果包含透明像素则是1:4),这意味着在硬盘和显卡内存中的相同大小的空间上您可以存放多达 6倍数量的texture。对于2D sprite通常最好不要进行压缩,因为查看它们实际尺寸的时候使用32bpp(bits per pixel)可以保证最好的质量。关于内容管道的内容在本部分随后将会讲到。
现在如果您按下F5或者F6(生成解决方案)上述添加的texture将被处理,并在项目的输出文件夹中产生一个新的文件“CityGroundSmall.xnb”,而且在项目的输出窗口中会显示下面的信息:
Building CityGroundSmall.jpg -> bin\x86\Debug\CityGroundSmall.xnb
现在您要做的最后一件事就是加载已经导入的纹理文件,在Initialize方法中进行操作并且在类中添加一个变量backgroundTexture,并使用之前您指定的资源名称(Asset Name)(资源名称默认的是您添加的内容文件的名称)。要渲染您的texture到输出屏幕上,您需要下一章要讨论的SpriteBatch类,它先设置Alpha Blending,然后把texture画到sprite中,最后把所有东西都画到屏幕上,代码如下:
Texture2D backgroundTexture; SpriteBatch sprites; protected override void Initialize() { backgroundTexture = content.Load<Texture2D>("CityGroundSmall"); sprites = new SpriteBatch(graphics.GraphicsDevice); base.Initialize(); } // Initialize()
要显示背景您必须在Draw方法中启动SpriteBatch,并把texture渲染到sprites中:
protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.Green); sprites.Begin(); sprites.Draw(backgroundTexture, Vector2.Zero, Color.White); sprites.End(); base.Draw(gameTime); } // Draw(gameTime)
上述代码将在您的输出屏幕的(0,0)坐标处显示该背景,SpriteBatch.Draw方法中的Color参数也可以重新设置,不过这里暂时并不重要。按下F5您将看到图1-14所示的结果:
为您的项目做的最后一件事就是添加使用键盘或者GamePad来滚动背景的功能,这样您就可以把可滚动的Tile渲染到整个背景。像下面这样修改Update方法您将可以捕获键盘或者GamePad输入:
float scrollPosition = 0; protected override void Update(GameTime gameTime) { // Get current gamepad and keyboard states GamePadState gamePad = GamePad.GetState(PlayerIndex.One); KeyboardState keyboard = Keyboard.GetState(); // Back or Escape exits our game on Xbox 360 and Windows if (gamePad.Buttons.Back == ButtonState.Pressed || keyboard.IsKeyDown(Keys.Escape)) this.Exit(); // Move 400 pixels each second float moveFactorPerSecond = 400 * (float)gameTime.ElapsedRealTime.TotalMilliseconds / 1000.0f; // Move up and down if we press the cursor or gamepad keys. if (gamePad.DPad.Up == ButtonState.Pressed || keyboard.IsKeyDown(Keys.Up)) scrollPosition += moveFactorPerSecond; if (gamePad.DPad.Down == ButtonState.Pressed || keyboard.IsKeyDown(Keys.Down)) scrollPosition -= moveFactorPerSecond; base.Update(gameTime); } // Update(gameTime)
前面几行代码和之前的例子是相同的。接下来要计算每一帧要移动多少像素,如果一秒钟只画一帧,那么变量moveFactorPerSecond的值就是400;如果一秒钟画60帧,那么一帧需要1/60秒,此时该变量的值就是400/60(其中gameTime.ElapsedRealTime表示相邻两帧之间的时间间隔)。
每当用户按下Up或者Down键的时候变量scrollPosition的值都会发生变化,此时在Draw方法中把scrollPosition的值加到坐标y上,就可以把背景来回上下移动了,代码如下:
protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.Green); sprites.Begin(); int resolutionWidth = graphics.GraphicsDevice.Viewport.Width; int resolutionHeight = graphics.GraphicsDevice.Viewport.Height; for (int x = 0; x <= resolutionWidth / backgroundTexture.Width; x++) for (int y = -1; y <= resolutionHeight / backgroundTexture.Height; y++) { Vector2 position = new Vector2( x * backgroundTexture.Width, y * backgroundTexture.Height + ((int)scrollPosition) % backgroundTexture.Height); sprites.Draw(backgroundTexture, position, Color.White); } // for for sprites.End(); base.Draw(gameTime); } // Draw(gameTime)
现在运行您的游戏就可以上下来回移动,这对于您的第一个小应用是不是很酷?