[翻译]Oreilly.Learning.XNA.3.0之seven
PS:自己翻译的,转载请著明出处
第十五章 包装你的3D游戏
这里我们准备去做-在你第一个3D游戏开发项目的最后的事情。它上看去不错,声音也不错,玩的也很好,剩下要做的是添加一些游戏逻辑包装它。首先第一件事情,你需要一个过度画面,当你的游戏开始时。除此,你需要去提供给玩家一些不同的指示器当她达到另外一个水平(关卡)在这个游戏中。为什么不使用一个过度画面在关卡之间呢?最后,你需要添加一个场景,它显示最后的分数当游戏结束时。听起来是一个不错的解决方案,这些问题可能会创建一个splash screen
游戏部分,它将让你显示文本在屏幕上。这样,你可以重新使用相同的类所所有三个意图,就象提到的-让我们来面对它,无论何时,你可以重新使用代码,这样你可以节省时间不会头痛。
本章弥补了14章留下的问题。打开工程,你准备从14章代码后面开始工作。
添加一个启动游戏画面的组件
在我们跳转到之前,先编写你的游戏启动画面组件,让我们返回看看这些如何去工作的。你的Game1类准备要管理不同的游戏状态。几个可能的游戏状态:开始游戏,玩这个游戏,暂停游戏在两个关卡之间,在游戏的最后显示游戏结束。
为了帮助你管理这些状态,创建一个枚举在你的Game1类中,这样你将使用并跟踪在游戏过程中状态与状态之间的改变。添加下面的代码行在Game1类的类层次:
2 GameState currentGameState = GameState.START;
你同样需要去添加一个方法为起始画面的游戏组件,这个模型管理游戏组件去通报Game1类当一个改变在游戏状态被占用。为此,添加下面的方法到Game1类:
2 {
3 currentGameState = state;
4 }
添加一个启动画面游戏组件,你首先需要创建一个空白游戏组件类。添加一个游戏组件到你的项目中并且调用文件的SplashScreen.cs.
请记住,默认情况下一个游戏组件没有一个Draw方法,因此不能绘制任何东西。但是,如果一个启动画面不能用来绘制一段文字,那么它有什么好的?改变新游戏组件的基类从GameComponent到DrawableGameComponent,它将让你的游戏组件tie into游戏的Draw循环的次序。
接下来,添加下面的类别变量到你的启动画面游戏组件:
2 string secondaryTextToDraw;
3 SpriteFont spriteFont;
4 SpriteFont secondarySpriteFont;
5 SpriteBatch spriteBatch;
6 Game1.GameState currentGameState;
可能你已经猜到你添加的SpriteFont变量,你现在需要去添加几个spritefonts到你的项目中。右击Content节点在解决方案中,并且选择Add-New Folder.命名这个新文件夹Fonts.然后右击这个新Fonts文件夹,并选择Add-New Item...选择Sprite Font模板在右边,并命名你的字体Arial.spritefont,如图15-1所显示的那样。
接下来,添加另外的spritefont(跟着同样的步骤),调用这一个Arial Black.spritefont.
你准备去使用这个Arial Black字体为这个窗口的大标题文本。打开文件,你会看见基于XML的内容。字体的第二个元素是<Size>元素。改变你的Arial Blank字体的大小到16通过修改这个元素,如下:
2 {
3 // Load fonts
4 spriteFont = Game.Content.Load<SpriteFont>(@"fonts\Arial Black");
5 secondarySpriteFont = Game.Content.Load<SpriteFont>(@"fonts\Arial");
6 // Create sprite batch
7 spriteBatch = new SpriteBatch(Game.GraphicsDevice);
8 base.LoadContent( );
9 }
对于本书的目的,我们准备使屏幕转换通过按下了Enter键。为了执行这个,你需要捕捉Enter键的压下在你的SplashScreen类的Update方法中。如果一个Enter键压下被检测到,你要么通知Game1类,在游戏状态需要被改变,要么完全退出游戏。两个中的哪一个依赖于当前的游戏状态(是splashScreen组成部分,当前显示一个星星或者关卡场景,或者一个游戏结束场景?)。
改变你的SplashScreen类的Update方法如下:
2 {
3 // Did the player hit Enter?
4 if (Keyboard.GetState( ).IsKeyDown(Keys.Enter))
5 {
6 // If we're not in end game, move to play state
7 if (currentGameState == Game1.GameState.LEVEL_CHANGE || currentGameState == Game1.GameState.START)
8 ((Game1)Game).ChangeGameState(Game1.GameState.PLAY, 0);
9 // If we are in end game, exit
10 else if (currentGameState == Game1.GameState.END)
11 Game.Exit( );
12 }
13 base.Update(gameTime);
14 }
接下来,你需要添加代码,它将实际绘制文本。当然,这会在Draw方法中完成,您目前没有。你需要去创建一个Draw方法的重写版本,并且添加代码去绘制一个大标题的文本,和小标题文本:
2 {
3 spriteBatch.Begin( );
4 // Get size of string
5 Vector2 TitleSize = spriteFont.MeasureString(textToDraw);
6 // Draw main text
7 spriteBatch.DrawString(spriteFont, textToDraw,new Vector2(Game.Window.ClientBounds.Width / 2- TitleSize.X / 2,Game.Window.ClientBounds.Height / 2),Color.Gold);
8 // Draw subtext
9 spriteBatch.DrawString(secondarySpriteFont,secondaryTextToDraw,new Vector2(Game.Window.ClientBounds.Width / 2- secondarySpriteFont.MeasureString(secondaryTextToDraw).X / 2,Game.Window.ClientBounds.Height / 2 +TitleSize.Y + 10),Color.Gold);
10 spriteBatch.End( );
11 base.Draw(gameTime);
12 }
SplashScreen类的最后一部分是一个方法,它将会使Game1类去设置文本,它需要被显示,并且去设置当前的游戏状态,添加这个方法到SplashScreen类中:
2 {
3 textToDraw = main;
4 this.currentGameState = currGameState;
5 switch (currentGameState)
6 {
7 case Game1.GameState.START:
8 case Game1.GameState.LEVEL_CHANGE:
9 secondaryTextToDraw = "Press ENTER to begin";
10 break;
11 case Game1.GameState.END:
12 secondaryTextToDraw = "Press ENTER to quit";
13 break;
14 }
15 }
2 int score = 0;
2 {
3 camera = new Camera(this, new Vector3(0, 0, 50),Vector3.Zero, Vector3.Up);
4 Components.Add(camera);
5 modelManager = new ModelManager(this);
6 Components.Add(modelManager);
7 base.Initialize( );
8 }
2 {
3 camera = new Camera(this, new Vector3(0, 0, 50),Vector3.Zero, Vector3.Up);
4 Components.Add(camera);
5 modelManager = new ModelManager(this);
6 Components.Add(modelManager);
7 modelManager.Enabled = false;
8 modelManager.Visible = false;
9 // Splash screen component
10 splashScreen = new SplashScreen(this);
11 Components.Add(splashScreen);
12 splashScreen.SetData("Welcome to Space Defender!",currentGameState);
13 base.Initialize( );
14 }
接下来你就需要考虑的是更新的方法。当前,每一次Update被调用,你检查键盘为一个空格键被压下,如果发生,发射一个子弹。现在你正好准备想去做这个,如果当前游戏状态是设置成PLAY.
当前的你的Game1类的Update方法应该看上去象这样:
2 {
3 // Allows the game to exit
4 if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==ButtonState.Pressed)
5 this.Exit( );
6 // See if the player has fired a shot
7 FireShots(gameTime);
8 base.Update(gameTime);
9 }
2 if (currentGameState == GameState.PLAY)
3 {
4 // See if the player has fired a shot
5 FireShots(gameTime);
6 }
2 {
3 GraphicsDevice.Clear(Color.Black);
4 // TODO: Add your drawing code here
5 base.Draw(gameTime);
6 // Only draw crosshair if in play game state
7 if (currentGameState == GameState.PLAY)
8 {
9 // Draw the crosshair
10 spriteBatch.Begin( );
11 spriteBatch.Draw(crosshairTexture,new Vector2((Window.ClientBounds.Width / 2)- (crosshairTexture.Width / 2),(Window.ClientBounds.Height / 2)- (crosshairTexture.Height / 2)),Color.White);
12 spriteBatch.End( );
13 }
14 }
2 {
3 currentGameState = state;
4 switch (currentGameState)
5 {
6 case GameState.LEVEL_CHANGE:
7 splashScreen.SetData("Level " + (level + 1),GameState.LEVEL_CHANGE);
8 modelManager.Enabled = false;
9 modelManager.Visible = false;
10 splashScreen.Enabled = true;
11 splashScreen.Visible = true;
12 // Stop the soundtrack loop
13 trackCue.Stop(AudioStopOptions.Immediate);
14 break;
15 case GameState.PLAY:
16 modelManager.Enabled = true;
17 modelManager.Visible = true;
18 splashScreen.Enabled = false;
19 splashScreen.Visible = false;
20 if (trackCue.IsPlaying)
21 trackCue.Stop(AudioStopOptions.Immediate);
22 // To play a stopped cue, get the cue from the soundbank again
23 trackCue = soundBank.GetCue("Tracks");
24 trackCue.Play();
25 break;
26 case GameState.END:
27 splashScreen.SetData("Game Over.\nLevel: " + (level + 1) +"\nScore: " + score, GameState.END);
28 modelManager.Enabled = false;
29 modelManager.Visible = false;
30 splashScreen.Enabled = true;
31 splashScreen.Visible = true;
32 // Stop the soundtrack loop
33 trackCue.Stop(AudioStopOptions.Immediate);
34 break;
35 }
36 }
首先,让我们了解下PLAY-LEVEL_CHANGE的转变。记住你编写这个游戏去产生X数量的敌人飞船每个关卡。转换到一个新关卡应该发生当最终的飞船已经飞过相机或者被摧毁。为了使这个游戏更流畅一点,让我同样去添加约束,所有的爆炸应该同样完成。这样,当你破坏了最后的飞船,这个游戏不会立即到过度画面,而是让你看到爆炸后,然后在开始转换。
在你的ModelManager类的CheckToSpawnEnemy方法,你有一个if声明,它检查看敌人的数量产生在这个关卡是否比在这关允许的敌人数量低(if(enemiesThisLevel<levelInfoList[currentLevel].numberEnemies))。如果这条件是true,这里有很多的敌人被产生在这关,并且你要检查它,看看是否是产生敌人的时间。但是,如果它是false,这是你第一个声明,该移到一个新的关卡。添加到if声明中,下面的代码到else的块,它将检查去看是否所有的爆炸已经结束了,如果是这样,转换到一个新的关卡(整个方法清晰的显示在这)。
2 {
3 // Time to spawn a new enemy?
4 if (enemiesThisLevel <levelInfoList[currentLevel].numberEnemies)
5 {
6 timeSinceLastSpawn += gameTime.ElapsedGameTime.Milliseconds;
7 if (timeSinceLastSpawn > nextSpawnTime)
8 {
9 SpawnEnemy( );
10 }
11 }
12 else
13 {
14 if (explosions.Count == 0 && models.Count == 0)
15 {
16 // ALL EXPLOSIONS AND SHIPS ARE REMOVED AND THE LEVEL IS OVER
17 ++currentLevel;
18 enemiesThisLevel = 0;
19 missedThisLevel = 0;
20 ((Game1)Game).ChangeGameState(Game1.GameState.LEVEL_CHANGE,currentLevel);
21 }
22 }
23 }
这就是这么简单。现在,让我们添加一个转变从PLAY到END游戏状态。
在ModelManager类的UpdateModels方法,你有两个位置,你删除飞船从这个飞船表单中使用models.RemoveAt。其中之一的情况是一个玩家射击一个飞船,另外的情况是一个飞船飞过了相机并且逃走了。这中情况下游戏将会结束,当很多飞船逃离每关允许逃离的数量。你有一个变量设立去跟踪已经逃离飞船的数量(missedThisLevel),但是你不用对它做任何事。当前的UpdateModels方法看起来象这样:
2 {
3 // Loop through all models and call Update
4 for (int i = 0; i < models.Count; ++i)
5 {
6 // Update each model
7 models[i].Update( );
8 // Remove models that are out of bounds
9 if (models[i].GetWorld( ).Translation.Z >((Game1)Game).camera.cameraPosition.Z + 100)
10 {
11 models.RemoveAt(i);
12 --i;
13 }
14 }
15 }
2 {
3 // Loop through all models and call Update
4 for (int i = 0; i < models.Count; ++i)
5 {
6 // Update each model
7 models[i].Update( );
8 // Remove models that are out of bounds
9 if (models[i].GetWorld( ).Translation.Z >((Game1)Game).camera.cameraPosition.Z + 100)
10 {
11 // If player has missed more than allowed, game over
12 ++missedThisLevel;
13 if (missedThisLevel >levelInfoList[currentLevel].missesAllowed)
14 {
15 ((Game1)Game).ChangeGameState(Game1.GameState.END, currentLevel);
16 }
17 models.RemoveAt(i);
18 --i;
19 }
20 }
21 }
基本上,这里发生的是,每一次一个飞船逃离,你增加missedThisLevel变量。当这个变量超越允许错过的数量,这个游戏结束并且Game1类也被通知了。
好,让我们给它一弹。编译运行你的游戏,并且你应该添加一个很漂亮友好的启动画面(图15-2)。你现在同样可以播放游戏,并且它应该从这个关卡到另外一个关卡的转换,显示另外一个过渡画面当游戏结束。
由于你的过渡画面是一个游戏的组成部分,你可以自定义它正象你编程时,添加一个奇特的背景图象,添加声音....作任何你想去使它更令人兴奋和有趣的事情。
Keeping Score
现在你有关卡和结束游戏的逻辑,但是有趣的事你的分数还是0?在这节,你将会充实游戏的记分。你已经准备显示分数在游戏结束的时候,但是你想让玩家看见他们的分数当他们正在玩的时候。为了做到这个,添加一个类别SpriteFont变量到你的Game1类,用它绘制分数:
是的,没有错...接下来的事情,你需要做的是添加一个新的spritefont到你的项目中。右击Content\Fonts文件夹在解决方案中,选择Add-New Item....选择Sprite Font模板在右边的并命名为Tahoma.spritefont,如图15-3所示
结束游戏的逻辑
请注意,这里只有一个方法能结束这个游戏,就是失败。这里没有逻辑去"赢"这个游戏。为什么呢?什么是模型的利和弊呢?
这实际上是一个普通的模型为一个共同的游戏。一个想法是使这个游戏很难,很难直到它不可能让任何人能到下一关。这个方法的一个好处是玩家总是有一个原因去玩-这里没有结束,唯一的目标是击败之前的高分。
这个方法的一个缺点是你相信没有人可以通过游戏的这关,你认为"不可能"。例如,在游戏中你现在开发的,产生敌人的时间非常短,更多的飞船被产生,每一关有允许很少的飞船飞离游戏。这种情况持续下去,直到最后一关,48只飞船被产生,平均一只0-200毫秒,同时0只允许错过。这接近于不可能。然而,重要的是注意你的游戏看起来真傻如果如果真有人打破这个关。从本质上讲,你的游戏可能崩溃,如果它是一个专业开发的游戏,将是所有的论坛上的一个热门话题在互联网上。
这种阶段中一个游戏打破了由于玩家已经达到这个点,它没有更多的逻辑支持继续游戏被称为"杀死屏幕"。一些著名杀死屏幕的包括Pac-Man当玩家达到256级崩溃,DonkeyKong崩溃当玩家达到22关或者第117幕,Duck Hunt崩溃当玩家达到100级。查找这些在因特网上,你会看到很多当一个玩家发现如何打破一个视频游戏被产生。这并不是什么新闻,你要你的游戏去接受,这是一个好想法或者添加逻辑到你的游戏让玩家最终能赢,或者确保它是根本不可能的,任何人都不能达到游戏的结束的逻辑。
为了使你得分字体突出一点,打开Tahoma.spritefont文件和找到<Style>元素。这个元素让你调整属性如设置文本用粗体和斜体等等。改变spritefont去使用一个粗体字体通过改变<Style>标签如下:
注意:在<Style>标记项是区分大小写,正如它说的在实际的spritefont文件。确保你使用这个文本Bold代替bold或者BOLD.
接下来,在你的Game1类的LoadContent方法中,添加下面的代码加载字体:
要做到这一点,你需要添加一个方式为Game1类去检查多少错过还剩下(这个数据被保存在你的ModelManager类中)。添加下面公开访问到ModelManager类:
2 {
3 get { returnlevelInfoList[currentLevel].missesAllowed- missedThisLevel; }
4 }
现在,添加下面的代码在SpriteBatch.Begin和SPriteBatch.End调用之间在你的Game1类的Draw方法中:
2 string scoreText = "Score: " + score;
3 spriteBatch.DrawString(spriteFont, scoreText,new Vector2(10, 10), Color.Red);
4 // Let the player know how many misses he has left
5 spriteBatch.DrawString(spriteFont, "Misses Left: " +modelManager.missesLeft,new Vector2(10, spriteFont.MeasureString(scoreText).Y + 20),Color.Red);
为什么它如此大的处理显示还剩多少可以错过的数字呢?
当开发任何的游戏,更多的你可以做的是让玩家尽量减少麻烦和担心,这可以让他们集中在游戏,休闲享受的经验。它是并不罕见在更复杂的游戏为屏幕更复杂的游戏通知的指标和方法,千疮百孔,使游戏体验更好。
因此,这里有一个问题:如果你添加一个文本指示器在屏幕上显示还剩多少能错过,是否足够呢?这是个人的观点。这是游戏开发, 它是一个创造性的过程。但是考虑到同样有什么能被做。如果你添加一个声音效果,无论何时当错过发生时播放?然后一个玩家可以告诉某些失误(译者:指的是错过飞船)而不用四处看。当玩家还剩三个或者几个可以错过的时候,一个警报音频被播放效果如何呢?也许文本可以闪烁在那时。这有一点改变并且很容易执行,但是他们可以显着提高游戏性,并且可以考虑在你的整体设计中考虑。
最后,这里必须有一种方法去调整分数。这个score变量是你的Game1类的一部分,但是它的意思是检测当一个变化在分数应该发生变化时(当一个子弹撞击一个飞船时)摆放在ModelManager类中。你需要添加一个方法到你的Game1类,它将让你的ModelManager调整这个分数:
2 {
3 score += points;
4 }
现在,所有剩下的是添加记分机制到ModelManager类它自己。首先,让我们计算出每次杀死值得多少个分。是另一个领域你可以有创意,想出一个公式,为你工作。一对普通的方法既不使用一个标志系统(所有飞船总是X点)也不使用一个增加的收益的方法(飞船值更多分做为游戏的发展)。为了这个游戏,你准备执行后面这种策略。但是首先,你需要一个开始的分,添加下面的类别变量到你的ModelManager类中:
你需要添加一些代码,它将更新分数当一个子弹撞击到了一个飞船。在UpdateShots方法的结束,这里调用到models.RemoveAt,它删除飞船,接着通过调用一个shots.RemoveAt,它删除子弹,它撞击到了一个飞船。在这些之前立即调用RemoveAt,添加下面的代码行,它将调整游戏的分数:
得分一定要这么简单?
绝对不是。这是一个非常有创造性的过程,这个方法为了计算这个分数可以是任何你希望它是。你能想到一些东西去添加进去使分数更有趣?
当你射击不成功,减去一点你的分数如何?这可能使游戏更有趣,因为它可能阻碍玩家简单的按下空格键不放。
但是,请记住,更复杂的得分,就需要更多的解释。现在我们使用一个相当简单的方法,它是被玩家期望的,所以不需要解释-你射击那个飞船,你得分。但是想象一下玩家会有多惊奇如果分数由于什么原因开始,没有任何的解释!如果你不想要你的玩家感到非常愤怒,他们需要完全理解游戏的规则,因为这个规则变的太复杂了,就需要更多的解释。
你有了它-现在所有剩下要做的是播放你的游戏,与你朋友挑战去击败你的分数。游戏结束的画面如图15-4。
Adding a Power-Up
你在这里做得很好,你创建第一个3D游戏,包括分数和日益增加的难度,并充满了乐趣!在我们结束这章之前,让我们做更多的事情。倒不是游戏是无聊的,但是任何事情它打破单调的游戏规则,采取主动的朝着使一个游戏有更多的令人兴奋和入迷的方向。
在本节中,你添加一个power-up(译者:威力)因素,它将会被授予当一个玩家得到三次连续的击杀。这个power-up将让玩家射击在速射的模式有10秒钟。我知道....这个声音使人兴奋,让我们来实现它吧。
首先,您要添加声音效果,当速射power-up被授予时它会播放。这章的源代码,在3D Game\Content\Audio文件夹,你会发现一个声音效果调用RapidFire.wav。复制这个文件到你的项目的Content\Audio目录在窗口浏览器中。记住不要添加这文件到你的项目中,因为你将会添加它到你的XACT工程文件。
打开你的XACT工程文件从XACT里面,添加RapidFire.wav声音到wave bank,并且创建一个声音
cue为这个声音。然后,保存XACT工程文件,并且关闭XACT(参看第5章如果你需要更多关于编辑XACT工程文件)。
除此之外包括一个声音效果当玩家接收这个power-up,它可能会是一个好的想法去包括一个文本指示器。这将帮助你解决任何的困惑,为什么会突然之间玩家可以射的那么快。为了实现这个,添加一个新的spritefont到你的项目中在Visual Studio通过右击这个Content\Fonts文件夹并选择Add-New Item....选择这个SpriteFont模板在右边的,并命名这个文件为Cooper Blank.spritefont,如图15-5所示。
打开Cooper Black.spritefont和改变字体的大小通过修改<Size>元素如下:
2 public enum PowerUps { RAPID_FIRE }
3 int shotDelayRapidFire = 100;
4 int rapidFireTime = 10000;
5 int powerUpCountdown = 0;
6 string powerUpText = "";
7 int powerUpTextTimer = 0;
8 SpriteFont powerUpFont;
originalShotDelay
当power-up开始,你修改shotDelay变量去给这个速射功能。在10秒之后,你需要去设置它返回它的初始值。这个变量简单保留初始值,这样你可以重置它,当这个power-up周期满。
PowerUps enum
这里列举出所有可能power-up,稍后在这种情况下你想去添加更多。
shotDelayRapiedFire
这代表射击的延迟在速射模式中。正常情况下,它是300,所以你会是射击速度的3倍。
rapidFireTime
这是power-up将会持续的时间(用豪秒计算)。
powerUpCountdown
这是计算跟踪power-up已经持续了这个效果多久的时间。
powerUpText
这是当一个power-up被激活文本被显示
powerUpTextTimer
这是时间计时器,它跟踪power-up文本已经在屏幕上多久了。
powerUpFont
这指定的字体,用以绘制power-up的文本。
接下来,你需要加载spritefont你刚刚创建在你的powerUpFont变量中的。添加下面的代码行到你的Game1类的LoadCotent方法中:
2 {
3 shotDelay = originalShotDelay;
4 }
2 {
3 if (powerUpCountdown > 0)
4 {
5 powerUpCountdown -= gameTime.ElapsedGameTime.Milliseconds;
6 if (powerUpCountdown <= 0)
7 {
8 CancelPowerUps( );
9 powerUpCountdown = 0;
10 }
11 }
12 }
2 UpdatePowerUp(gameTime);
2 {
3 switch (powerUp)
4 {
5 case PowerUps.RAPID_FIRE:
6 shotDelay = shotDelayRapidFire;
7 powerUpCountdown = rapidFireTime;
8 powerUpText = "Rapid Fire Mode!";
9 powerUpTextTimer = 1000;
10 soundBank.PlayCue("RapidFire");
11 break;
12 }
13 }
由于shotDelay变量已经被使用去检测延迟在子弹之间,这里没有更多要做的在速射这个功能上。但是,你想绘制power-up文本在屏幕上同时powerUpTextTimer的值大于0。为了实现这个,添加下面的代码到Game1类的Draw方法中,在调用spriteBatch.End之前;
2 if (powerUpTextTimer > 0)
3 {
4 powerUpTextTimer -= gameTime.ElapsedGameTime.Milliseconds;
5 Vector2 textSize = powerUpFont.MeasureString(powerUpText);
6 spriteBatch.DrawString(powerUpFont,powerUpText,new Vector2((Window.ClientBounds.Width / 2) -(textSize.X / 2),(Window.ClientBounds.Height / 2) -(textSize.Y / 2)),Color.Goldenrod);
7 }
最后一件事你需要做的在Game1类中,取消任何power-ups,当一个关卡结束。例如,你不想某些人得到一个power-up在关卡2的结尾,然后把这个power-up持续用到关卡3的开头。
注意:为什么不能把power-up延续到下一关?好,象在游戏开发中很多事情,这是一个人的决定。也许你更喜欢把它延续下去,也许你不希望这样。在我看来,最好是取消它在两个关卡之间,这样就是我们要在这本中要做的。虽然,随意地去创建你想要的方式,你可以添加不同的你自己的或者用户自定义的power-ups。这是你的世界,你可以做任何你想要做的。
添加一个调用到CancelPowerUps方法在你的Game1类的ChangeGameState方法上部分:
现在,让我们移动到ModelManager类中。这个类没有什么要做的在这个特定的power-up因为power-up只影响射击,这主要处理在Game1类中。但是,你仍然需要跟踪当开始一个power-up.因为这个power-up是基于不断的击杀,而击杀在ModelManager中被处理, 它的意义是把这个逻辑驻留在这个类中。
添加下面的类别变量到你的Modelmanager类中:
2 int rapidFireKillRequirement = 3;
当你删除飞船从飞船表单中,是在一个飞船逃离或者一个被击落。在这两种情况下,你将会修改consecutiveKills的值的变量(当一个飞船逃离,consecutiveKills变成0,或者当一个飞船被击落,consecutiveKills被增加)
首先,找到models.RemoveAt调用在UpdateModels方法的结尾,它表明一个飞船已经逃离了。在同样的代码块,你设置游戏状态END与Game1.ChangeGameState方法,如果游戏结束了。
立即添加代码行在调用models.RemoveAt之前,在UpdateModel方法的结尾:
2 consecutiveKills = 0;
2 // and start power-up if requirement met
3 ++consecutiveKills;
4 if (consecutiveKills == rapidFireKillRequirement)
5 {
6 ((Game1)Game).StartPowerUp(Game1.PowerUps.RAPID_FIRE);
7 }
噢!您准备测试了。编译并运行该游戏。如果你的目标是足够的,你将会看到power-up被授予在你连续撞击到第三个飞船之后,如图15-6所示。
不错。游戏已完成!自由的添加别的power-ups,通知指标器,声音效果,同样别的想要的你想使这个游戏,使它按你想要的方式进行。
源代码:http://shiba.hpe.cn/jiaoyanzu/WULI/soft/xna.aspx?classId=4
(完)