【译】针对初学者的XNA示例游戏

发表于2016-03-06
评论0 3k浏览
  原文地址:http://www.codeproject.com/Articles/1072080/XNA-Sample-Game-for-Beginners
  版权:作者版权声明为http://www.codeproject.com/info/cpol10.aspx,可以翻译

一、简介
  C#.NET/XNA 是一项使用日渐减少但是至少在XBOX 360平台上依旧支持的游戏
  文中说到的示例游戏Annie's Laundry是由我用XNA在几个月前开发的,仅仅是为了让我加深对游戏开发基础的认识,并且这种认识也对大家有用。

二、背景
  在这篇文章里,我提供了游戏的源代码并且详细描述了物体运动、碰撞、计分、用户控制以及开始结束游戏。这篇文章提供了代码以及上述目标的详细描述。
  Annie'sLaundry 是一个用XNA开发的单人2D游戏。游戏的目的是通过捕获掉落的衣服来尽可能的多得分。掉落衣服的种类是手帕和衬衫。玩家需要提防沿着绳子掉落的粪便以及敌人扔过来的炸弹。所以,玩家在捕获衣服的时候要小心避开粪便以及炸弹。篮子越重就会也难移动,任务衣服掉落下来没有抓住都会损害安妮的健康。玩家需要保持安妮的健康,同时尽量避免粪便和炸弹以及得尽可能高的分数。

三、代码解释
  XNA游戏是从main方法的Program.cs  which开始执行的。Run()方法用来初始化游戏。
using System;
namespace WindowsGame2
{
#if WINDOWS || XBOX
    static class Program
    {
        static void Main(string[] args)
        {
            using (AnnieLaundary game = new AnnieLaundary())
            {
                game.Run();
            }
        }
    }
#endif
}
  AnnieLaundary这个类继承自Microsoft.Xna.Framework.Gam,必要的资源,比如声音、声音对象实例、精灵字体、2D纹理以及对象的初始化,都是在这个类里面。
public class AnnieLaundary : Microsoft.Xna.Framework.Game
 {
   SoundEffect background_sound;
   SoundEffectInstanceintro_sound_instance;          
   SpriteFont font;       
   Bucket bucketObj;       
   Rectangle screenRectangle;       
   Texture2D shirt; // shortened code as for the example
}
  LoadContent()方法把WindowsGame2Content目录下的声音和纹理资源加载进来。这是一段非常重要的代码,你需要小心不要把资源的名字拼错。
protected override void LoadContent()
 {
     bomb_sound =Content.Load("Audio\Waves\bomb");           
     bomb_sound_instance =bomb_sound.CreateInstance();
     mainMenu =Content.Load("mainbkg");
     help =Content.Load("selectamapwithbckgrnd");
     shirt =Content.Load("shirt");
     poo =Content.Load("poo");
     Mybombimg =Content.Load("bomb");
     monkey =Content.Load("monkey");
     shirtObj = new Shirt(shirt,screenRectangle);
     bombObj = new Bomb(Mybombimg, screenRectangle);
     pooObj = new Poo(poo, screenRectangle);
     monkeyObj = new Monkey(monkey, screenRectangle, this.Content);
     pheObj = new PlayHelpExitBtns
     (playNorm, playNeon, helpNorm,helpNeon,
           exitNorm, exitNeon, help,mainMenu, screenRectangle, this.Content);
     spriteBatch = new SpriteBatch(GraphicsDevice);
     font =Content.Load("SpriteFont1");
     serviet =Content.Load("serviet"); //shortened code as for example
}
  主类里面的Update方法是非常重要的,因为它在持续地更新游戏的逻辑。上次update到现在的时间是作为参数传递进来的。
  屏幕状态枚举有两个常量分别是controllerDetect和title。初始是controllerDetect。    updateControllerDetectScreen函数在检测到玩家输入Enter以后会把状态从controllerDetect切为title。
enum screenState
{
    controllerDetect,
    title
}
screenState currentScreenState;
private void updateControllerDetectScreen()
{
    if (Keyboard.GetState().IsKeyDown(Keys.Enter) == true)
        currentScreenState =screenState.title;
}


protected override void Update(GameTimegameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
    {
        this.Exit();
    }
    switch (currentScreenState)
    {
        case screenState.controllerDetect:
        {
           updateControllerDetectScreen();
            break;
        }
        case screenState.title:
        {
            if (PlayHelpExitBtns.selectedKeyState == "play" && playState == false)
            {
                StartGame();
                playState = true;
               intro_sound_instance.Stop();
                /* stops the intro sound play when theplaye begins */
            }
            if (PlayHelpExitBtns.selectedKeyState == "play" && !Serviet.gameover)
            {
               intro_sound_instance.Stop();
                /* stops the intro sound play when theplaye begins */
               background_sound_instance.Play();
                /* starts background music */
                if (Serviet.level >= 2) /* level static variable is declared inserviet */
                {
                   shirtObj.Updateshirt();
                }
                if (Serviet.level >= 3)
                {
                   secondbucketObj.UpdateSecondBucket();
                }
                if (Serviet.level >= 4)
                {
                   pooObj.UpdatePoo();
                }
                if (Serviet.level >= 5)
                {
                   bombObj.UpdateBomb();
                }
               servietObj.UpdateServiet();
               monkeyObj.UpdateMonkey();
               bucketObj.UpdateBucket();
            }
            else
            {
                pheObj.update();
                if (pheObj.exitGame())
                {
                    this.Exit();
                }
            }
            break;
        }
    }
    /* serviet collision with bucket */
    if (servietObj.Collision(bucketObj.GetBounds()))
    {
        if (clothfall_sound_instance.State == SoundState.Stopped)
        {
           clothfall_sound_instance.Volume = 0.75f; /* Play sounds */
           clothfall_sound_instance.Play();
        }
    }
    /* second bucket comes after level 3 */
    if (Serviet.level >= 3)
    {
        if (servietObj.Collision(secondbucketObj.GetBounds()))
        {
            if (clothfall_sound_instance.State == SoundState.Stopped)
            {
               clothfall_sound_instance.Volume = 0.75f /* Play Sounds */
               clothfall_sound_instance.Play();
            }
        }
    }
    if (shirtObj.Collision(bucketObj.GetBounds()))
    {
        if (clothfall_sound_instance.State == SoundState.Stopped)
        {
           clothfall_sound_instance.Volume = 0.75f; /* Play Sounds */
           clothfall_sound_instance.Play();
        }
    }
    if (Serviet.level >= 3) // second bucket comes after level 3
    {
        if (shirtObj.Collision(secondbucketObj.GetBounds()))
        {
            if (clothfall_sound_instance.State == SoundState.Stopped)
            {
               clothfall_sound_instance.Volume = 0.75f;
               clothfall_sound_instance.Play();
            }
        }
    }
    /* second bucket comes after level 3  but poop comes after 4 */
    if (Serviet.level >= 4)
    {
        if (pooObj.Collision(bucketObj.GetBounds())) /* collision with first bucket*/
        {
            if (poop_sound_instance.State == SoundState.Stopped)
            {
               poop_sound_instance.Volume = 0.75f;
               poop_sound_instance.Play();
            }
        }
        if (pooObj.Collision(secondbucketObj.GetBounds())) /* collision with second */
        {
            if (poop_sound_instance.State == SoundState.Stopped)
            {
               poop_sound_instance.Volume = 0.75f;
               poop_sound_instance.Play();
            }
        }
    }
    if (Serviet.level >= 5) // second bucket comes after level 3  but bomb comes after 5
    {
        /* checks the collision of first bucket withbomb */
        if (bombObj.Collision(bucketObj.GetBounds()))
        {
            if (bomb_sound_instance.State == SoundState.Stopped)
            {
               bomb_sound_instance.Volume = 0.75f;
               bomb_sound_instance.Play();
               crying_sound_instance.Play();
            }
        }
       /* checks the collision of second bucket withbomb */
       if (bombObj.Collision(secondbucketObj.GetBounds()))
       {
            if (bomb_sound_instance.State == SoundState.Stopped)
            {
               bomb_sound_instance.Volume = 0.50f;
                bomb_sound_instance.Play();
               crying_sound_instance.Play();
            }
        }
    }
    base.Update(gameTime);
}
  关卡在Serviet里被声明为静态变量,并且会用在update()函数里。
/* second bucket comes after level 3 but bomb comes after 5 */
if (Serviet.level>= 5)
{
     // collision conditions
}
  Collision() 方法用来检测物体之间是否发生碰撞。
/* checks the collision of second bucket with bomb */
if (bombObj.Collision(secondbucketObj.GetBounds()))
{
   // sounds files
}
  Draw()方法把所有的精灵作为spritebatches渲染出来。精灵是渲染物体的通用表达方式。
  PlayHelpExitBtns类提供了游戏里的帮助菜单,玩家可以选择继续玩、帮助或者退出。
protected override void Draw(GameTimegameTime)
{
   GraphicsDevice.Clear(Color.Black);
    spriteBatch.Begin();
    if (currentScreenState == screenState.controllerDetect)
    {
       spriteBatch.Draw(splashScreen, Vector2.Zero, Color.White);
    }
    if (PlayHelpExitBtns.selectedKeyState == "play")
    {
        // if the game overs with the annies lowhealth the condition is passed here
        if (Serviet.gameover && Serviet.anniesmiles == 0)
        {
           spriteBatch.Draw(Gameover, gameoverposition, Color.White);
           spriteBatch.Draw(levelimage, leveluppossition, Color.White);
            spriteBatch.DrawString
            (font, "Score : " + Serviet.final_score,
                  new Vector2(1050, 20),Color.Chocolate);
           crying_sound_instance.Play();
           bomb_sound_instance.Stop();
           background_sound_instance.Stop();
           clothfall_sound_instance.Stop();
           poop_sound_instance.Stop();
        }
        if (Serviet.gameover)
        {
            // if the game overs with the hitting of abomb then it is passed here
           spriteBatch.Draw(Gameover, gameoverposition, Color.White);
           spriteBatch.Draw(levelimage, leveluppossition, Color.White);
            spriteBatch.DrawString
            (font, "Score : " + Serviet.final_score,
                  new Vector2(1050, 20),Color.Chocolate);
           poop_sound_instance.Stop();
           clothfall_sound_instance.Stop();
           background_sound_instance.Stop();
           levelup_sound_instance.Stop();
        }
        else
        {
           spriteBatch.Draw(background, backgrounposition, Color.White);
           bucketObj.Draw(spriteBatch);
           servietObj.Draw(spriteBatch);
           monkeyObj.DrawMonkey(spriteBatch);
            DrawText();
           spriteBatch.Draw(myTexture, spritePosition, Color.White);
            spriteBatch.Draw
            (annhealth_icon, new Rectangle
                             (530 , 15, annhealth_icon.Width, 44),
                                      new Rectangle
                                        (0, 0,annhealth_icon.Width, 44),
                                               Color.White); // healthbag icon
            if(Serviet.anniesmiles < 7)
            {
                healthbar_color =Color.Yellow;
            }
            if (Serviet.anniesmiles < 4)
            {
                healthbar_color =Color.Red;
            }
           spriteBatch.Draw(healthbar,
                   new Rectangle(600, 25,
                       (int)(healthbar.Width * ((double)Serviet.anniesmiles/ 10)),
                            44), new Rectangle
                                 (0, 45, healthbar.Width, 44), healthbar_color);
                                 // draw red line indicating health
           spriteBatch.Draw(healthbar,
                     new Rectangle(600, 10, healthbar.Width, 44),
                        new Rectangle(0, 0, healthbar.Width, 44), Color.White);
            if (Serviet.level >= 2) // shirt on and after the level 2
            {
               shirtObj.DrawShirt(spriteBatch);
            }
            if (Serviet.level >= 3)
            {
               secondbucketObj.DrawSecondBucket(spriteBatch);
            }
            if (Serviet.level >= 4)
            {
               pooObj.DrawPoo(spriteBatch);
            }
            if (Serviet.level >= 5)
            {
               bombObj.DrawBomb(spriteBatch);
            }
        }
        if ((Serviet.total >= 100 &&Serviet.total <= 120)
             || (Serviet.total >= 200 && Serviet.total <= 220)
             || (Serviet.total >= 300 && Serviet.total <= 320)
             || (Serviet.total >= 400 && Serviet.total <= 420)
             || (Serviet.total >= 500 && Serviet.total <= 520))
        {
            spriteBatch.Draw(Levelup,leveluppossition, Color.White);
            if (Serviet.total == 100
                 || Serviet.total == 200
                 || Serviet.total == 300
                 || Serviet.total == 400
                 || Serviet.total == 500)
            {
               levelup_sound_instance.Play();
            }
        }
    }
    else if (currentScreenState == screenState.title)
    {
        pheObj.draw(spriteBatch);
    }
    spriteBatch.End();
    base.Draw(gameTime);
}
  selectedKeyState是静态字符串,由Draw() 调用判断玩家在帮助菜单的输入。
if (PlayHelpExitBtns.selectedKeyState == "play")
{
        // code
}
  Serviet类里anniesmiles是个静态整数,一开始被初始化为10用来计算安妮的健康值,每有一个衣服掉落到地上,安妮的健康值就会减1
  下面是游戏方面,你可以看到得分、关卡和健康值等信息。


   游戏结束画面会显示得分


  从这里下载代码:https://dl.dropboxusercontent.com/u/83417157/Profiles/Annie%27s%20Laundry.zip

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引