如何在WP7上用XNA写2D游戏(六)

第 5 章XNA里的2D动画进阶

5.1 游戏精灵的动画切换

上一章,我们做出了可控制移动的精灵。精灵是行走状态,实际游戏里一个游戏人物可跳可走,可跑,收到攻击会受伤,还会死亡。那么这些都是人物的状态在改变,相应的我们要改变精灵播放的动画帧。

1.先定义一个枚举

public enum PersonState

{

   ran,

   walk,

   beattacked,

   dead

}

2.我们给精灵加一个状态属性:

PersonState State{get;set;}

3.然后我们修改update函数里的代码:

if(sprite.State==PersonState.ran)

{

sprite.Textures= RunTextures;

} else
if(sprite.State==PersonState.walk)

{

   sprite.Textures= WalkTextures;

}

......

在游戏运行时,只要改变精灵对象sprite的State属性就可以控制精灵的动画切换了。

//以下为伪代码

if(sprite.beattacked)
//
如果精灵收到攻击

{

    sprite.state=PersonState.beattacked;

}

if(sprite.isDead)

{

sprite.state=PersonState.dead;

}

4.在Draw函数里改变响应的Texture2D数组,来改变动画帧。

      按照这个思路,我们开始完善我们的游戏代码吧:

1. 定义Sprite类:

    /// <summary>

    /// 游戏实体对象,相当于精灵实体

    /// </summary>

   public class Sprite

    {

        /// <summary>

        /// 所在位置

        /// </summary>

        public Vector2 Position;

       /// <summary>

       /// 目标位置

      /// </summary>

       public Vector2 EndPosition;

   

   /// <summary>

   /// 运动矢量

   /// </summary>

        public Vector2 Velocity;

 

        /// <summary>

       /// 奔跑动画帧数

        /// </summary>

        public int RunFrameCount;

 

        /// <summary>

       /// 死亡动画帧数

        /// </summary>

      public int DeadFrameCount;

 

        /// <summary>

       /// 被攻击动画帧数

      /// </summary>

        public int BeAttackFrameCount;

 

       /// <summary>

      /// 攻击动画帧数

     /// </summary>

    public int AttackFrameCount;

 

     /// <summary>

    /// 贴图

    /// </summary>

        public Texture2D Texture;

 

        /// <summary>

       /// 跑动动画贴图

        /// </summary>

        public Texture2D[] RunTextures;

 

        /// <summary>

        /// 死亡动画贴图

        /// </summary>

        public Texture2D[] DeadTextures;

 

       /// <summary>

       /// 被攻击动画贴图

       /// </summary>

        public Texture2D[] BeattackTextures;

 

       /// <summary>

       /// 攻击动画贴图

     /// </summary>

      public Texture2D[] AttackTextures;

 

     /// <summary>

     /// 生命值

     /// </summary>

     public float Life;

   

     /// <summary>

    /// 是否死亡

    /// </summary>

      public bool IsAlive;

 

        /// <summary>

       /// 精灵状态

      /// </summary>

       public PersonState
State;

}

2. 定义精灵状态枚举

    public enum PersonState
   {

       Ran,

       Attack,

       Beattacked,

       Dead

   }

3. 添加一个“A”键贴图,并且输入到屏幕右下角,如图5-1-1:



                                                       图5-1-1 带攻击键的游戏界面

4.修改LoadContent函数为:

 public override void LoadContent()

{

   base.LoadContent();

   playerTexture = ScreenManager.Game.Content.Load("Player/1");       

    scrossTexure = ScreenManager.Game.Content.Load("UI/scross");       

    aTexture = ScreenManager.Game.Content.Load("UI/a");       

    solider.RunFrameCount = 12;       

    solider.AttackFrameCount = 10;
    solider.State = PersonState.Ran;       

     solider.RunTextures = new Texture2D[12];       

     solider.AttackTextures = new Texture2D[10];       

     for (int i = 0; i < 12;i++ )

    {           

        solider.RunTextures[i] = creenManager.Game.Content.Load("Enemy/Run/"+(i+1));

     }       

     for (int j= 0;j< 10; j++)

     {    

         solider.AttackTextures[j] = ScreenManager.Game.Content.Load("Enemy/Attack/" + (j + 1));    

     }    

     solider.Texture=solider.RunTextures[0];//预先给士兵贴图一个初始值  

   }

 

  5.修改Update函数里的代码,当用户触碰A键的时候,改变士兵的状态为攻击状态。      

    public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)       

  {           

      base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);           

      float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;           

      updateCount++;           

       if (updateCount > timeSpan) //TimeSpan为时间间隔量           

       {               

            if (solider.State == PersonState.Ran)               

            {                   

                  solider.RunFrameCount += 1;                   

                  if (solider.RunFrameCount > solider.RunTextures.Length - 1)//如果播完最后一帧                   {                       

                         solider.RunFrameCount = 0; //就回到第一帧                   

                   }                   

                   solider.Texture = solider.RunTextures[solider.RunFrameCount];               

            }               

        if (solider.State == PersonState.Attack)               

        {                   

             solider.AttackFrameCount += 1;                  

             if (solider.AttackFrameCount > solider.AttackTextures.Length - 1)//如果播完最后一帧              {                       

                  solider.AttackFrameCount = 0; //就回到第一帧                  

              }                   

              solider.Texture = solider.AttackTextures[solider.AttackFrameCount];               

          }

         updateCount = 0;           

    }           

    endPosition += elapsedTime * speed;           

    if (endPosition.X>800)           

     {               

         endPosition = new Vector2(0,200);           

      }       

   }

6.修改Draw函数里代码:  

    public override void Draw(GameTime gameTime)  

    {        

          float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;        

          float totalTime = (float)gameTime.TotalGameTime.TotalSeconds;           ScreenManager.SpriteBatch.Begin();        

          ScreenManager.SpriteBatch.Draw(solider.Texture, endPosition, Color.White);           ScreenManager.SpriteBatch.Draw(scrossTexure, new Vector2(2,380), Color.White);           ScreenManager.SpriteBatch.Draw(aTexture, new Vector2(722, 380), Color.White);           ScreenManager.SpriteBatch.End();  

  }

 

7.在模拟器里运行效果如图5-1-2:

  

 

 

                                                    图5-1-2 操控士兵行走方向

 

5.2游戏精灵的碰撞计算

          在一个战斗游戏里,光有一个主角是不太可能的,在上一章节里,我们给主角精灵加了一个攻击动画。如果让主角对敌人做出攻击动作,敌人会出现被攻击状态,直到死亡。那么如何判断主角进入到能对敌人进行攻击的范围呢,这就需要用到游戏精灵的碰撞计算。

          为了简化理解,我们看到主角有高度,有宽度,因为这是个2D的世界。那么我们就可以把主角在平面坐标里用一个矩形表示,矩形的高度近似等于主角的高度,矩形的宽度也近似等于主角的宽度。对敌人而已也是一样的,这样两个精灵物体的碰撞,我们就能变成2D世界里两个矩形是否相交汇。

          在XNA游戏框架里,Rectangle有一个专门的函数Intersects来判断两个矩形是否相交汇。 这样我们就解决了精灵的碰撞问题,我们来做一个敌人让主角攻击,如果在主角的攻击范围内,也就是两个游戏精灵能进行碰撞。为了完美表现这个过程,我们需要引入精灵的被攻击动画和死亡动画。

          1.由于主角和敌人都是同一个精灵创建而成,我们可以用同一个函数CreatePerson来实现这个过程:

              Sprite CreatePerson(Vector2 Position)

             {

                 Sprite person = new Sprite();            
                 person.Position = Position;

                 person.RunFrameCount = 12;

                 person.AttackFrameCount = 10;

                 person.BeAttackFrameCount = 7;

                 person.DeadFrameCount = 10;

                 person.State = PersonState.Ran;

                 person.RunTextures = new Texture2D[12];

                 person.AttackTextures = new Texture2D[10];

                 person.DeadTextures = new Texture2D[10];

                 person.BeattackTextures = new Texture2D[7];

                 for (int i = 0; i < 12; i++)

                {

                       person.RunTextures[i] = ScreenManager.Game.Content.Load("Enemy/Run/" + (i + 1));           

                 }           

                 for (int i = 0; i < 7; i++)           

                {               

                      person.BeattackTextures[i] = ScreenManager.Game.Content.Load("Enemy/Beattacked/" + (i + 1));           

                 }           

                for (int j = 0; j < 10; j++)           

               {               

                     person.AttackTextures[j] = ScreenManager.Game.Content.Load("Enemy/Attack/" + (j + 1));               

                     person.DeadTextures[j] = ScreenManager.Game.Content.Load("Enemy/Dead/" + (j + 1));

                }           

                person.Texture = person.RunTextures[0];           

                return person;       

          }

         2.创建主角和敌人两个精灵:      

            Sprite solider,enemy;      

            solider= CreatePerson(soliderPosition);      

            enemy = CreatePerson(enemyPosition);  

         3.修改Draw函数如下:       

            public override void Draw(GameTime gameTime)       

           {           

                float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;           

                float totalTime = (float)gameTime.TotalGameTime.TotalSeconds;                 ScreenManager.SpriteBatch.Begin();           

                ScreenManager.SpriteBatch.Draw(solider.Texture, solider.Position, Color.White);                 ScreenManager.SpriteBatch.Draw(enemy.Texture, enemy.Position, Color.White);                 ScreenManager.SpriteBatch.Draw(scrossTexure, new Vector2(2,380), Color.White);                 ScreenManager.SpriteBatch.Draw(aTexture, new Vector2(722, 380), Color.White);                 ScreenManager.SpriteBatch.End(); }

         4.增加一个执行精灵动画的方法DoSpriteAnimation,并修改Update函数,如下:     

           ///

/// 执行精灵动画- /// ///

          void DoSpriteAnimation(Sprite solider)       

          {           

               if (solider.State == PersonState.Ran)           

              {               

                    solider.RunFrameCount += 1;               

                    if (solider.RunFrameCount > solider.RunTextures.Length - 1)//如播完最后一帧                     {                   

                         solider.RunFrameCount = 0; //就回到第一帧               

                     }               

                     solider.Texture = solider.RunTextures[solider.RunFrameCount];           

              }
              if (solider.State == PersonState.Attack)           

              {               

                     solider.AttackFrameCount += 1;               

                     if (solider.AttackFrameCount > solider.AttackTextures.Length - 1)//如果播完最后一帧               

                     {                   

                          solider.AttackFrameCount = 0; //就回到第一帧               

                     }               

                     solider.Texture = solider.AttackTextures[solider.AttackFrameCount];           

              }           

             if (solider.State == PersonState.Dead)           

             {               

                 solider.DeadFrameCount += 1;               

                 if (solider.DeadFrameCount > solider.DeadTextures.Length - 1)//如果播完最后一帧                 {                   

                      solider.DeadFrameCount = 0; //就回到第一帧               

                 }               

                solider.Texture = solider.DeadTextures[solider.DeadFrameCount];           

              }           

             if (solider.State == PersonState.Beattacked)           

             {               

                 solider.BeAttackFrameCount += 1;               

                  if (solider.BeAttackFrameCount > solider.BeattackTextures.Length - 1)//如果播完最后一帧
                 {                   

                      solider.BeAttackFrameCount = 0; //就回到第一帧               

                  }               

                  solider.Texture = solider.BeattackTextures[solider.BeAttackFrameCount];           

               }       

           }       

 

          public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)       

          {           

              base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);           

              float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;               updateCount++;           

               if (updateCount > timeSpan) //TimeSpan为时间间隔量           

              {               

                   DoSpriteAnimation(solider);               

                   DoSpriteAnimation(enemy);               

                   updateCount = 0;           

               }           

                solider.Position += elapsedTime * speed;           

                if (soliderPosition.X>800)           

                {               

                       soliderPosition = new Vector2(0,200);           

                 }       

           }

     5. 写一个判断精灵碰撞的函数IsSpriteHit:       

    /// <summary>

      /// 两个精灵是否相碰

       /// </summary>

       /// <paramname="solider">主角</param>

       /// <paramname="enemy">敌人</param>

       /// <returns>是或者否</returns>

     bool IsSpriteHit(Sprite solider,Sprite enemy)       

     {           

          Rectangle soliderRect = new Rectangle()           

          {               

               Location = new Point()

               {

                  X = (int)solider.Position.X, Y = (int)solider.Position.Y

               },

               Width = solider.Texture.Width, Height = solider.Texture.Height           

           };           

           Rectangle enemyRect = new Rectangle()           

          {               

               Location = new Point()

                {

                      X = (int)enemy.Position.X, Y = (int)enemy.Position.Y

                },

               Width = enemy.Texture.Width, Height = enemy.Texture.Height           

          };         

        return soliderRect.Intersects(enemyRect);       

}

 

2. 在Update函数增加如下代码:   

    if(IsSpriteHit(solider,enemy)&&solider.State== PersonState.Attack)   

   {               

           enemy.State = PersonState.Beattacked;   

   }

 

3. 在模拟器执行上述代码效果如图5-2-1:

                                                         

                                                           图5-2-1 士兵战斗碰撞判断

              这一章的内容讲到这里就玩了,不过两个精灵战斗必然会有伤亡,这就需要我们加上伤害,防御,血量等数据,以及判断精灵是否死亡,精灵死亡后播放死亡动画,这就留给读者继续完善本章节的这个游戏。

posted @ 2012-07-23 23:39  王传炜  阅读(1316)  评论(2编辑  收藏  举报