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


                                                         第 4 章XNA里的2D动画

    4.1 播放游戏精灵动画

    第二章里我们提到了如何把2D的纹理用SpriteBatch显示。对于游戏而言,一个精灵(sprite,在游戏编程里指一个对象的原型,比如一个战士,一个怪物)不可能不做动作,就拿简单的走动来说,手脚一定会是要动的,那么如何真实得展现一个人物在屏幕上走动呢?在2D游戏编程里,我们通常就是把人物的动作拆成一帧帧图片,通过连续播放来欺骗人眼产生动画的效果,其实胶片电影也是这个原理。

    如下图4-1,是一个小兵的行走动画,可以看到是由12张图片组成,那么如何能连续循环播放呢

                                                      图4-1

    一想到循环,我们就很容易想到for循环,如果我们把这12张图片的纹理放到一个Texture2D[] 数组里那么,通过for循环不就可以实现了么?

    for(int i=0;i<SoldierTextures.Count;i++) //伪代码

    {

    spriteBatch.Draw(SoldierTextures[i]);

    }

    当然,这个循环只能循环一遍,如果做到从头到尾不停的循环呢?

    前面我们提到XNA里的Update函数,它就是不停执行的一个函数,执行的时间间隔是一个固定值.我们就可以把这个函数当做是一个特殊的for循环.

    我们需要用上一个全局的计数器FrameCount.

    int FrameCount=0;

    void Update() //在update函数里改变计数器

    {

    FrameCount++;

    if(FrameCount>SoilderTextures.Length-1)//如果播完最后一帧

    {

    FrameCount=0; //就回到第一帧

    }

    }

    void Draw() //在Draw函数里绘制出纹理

    {

    spriteBatch.Draw(SoilderTextures[FrameCount]);

    }

    以上代码都是伪代码,便于大家理解,实际编程中的代码比这里要复杂点。

    1.在GameMainScreen类里写上构造函数:

        Texture2D[] soilderTextures;

           public GameMainScreen()

           {

               soilderTextures=new Texture2D[12];//初始化士兵纹理数组

           }

    2.把12张士兵跑动图片放到Content项目下的一个Enemy文件夹的子文件夹Run中,如果4-2:

    3.用for循环加载这12张图片纹理,如下:

     public override void LoadContent()

     {

         base.LoadContent();

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

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

         {

     soilderTextures[i] =ScreenManager.Game.Content.Load<Texture2D>("Enemy/Run/"+(i+1));

         }

     }

    4.在Draw里绘制出当前帧:

     public override void Draw(GameTime gameTime)

     {

         ScreenManager.SpriteBatch.Begin();

         ScreenManager.SpriteBatch.Draw(soilderTextures[FrameCount],new Vector2(100,200), Color.White);

         ScreenManager.SpriteBatch.End();

      }

     

            图4-2

    5.在Update里更新当前帧的位置,依次向后播放:

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

      {

          base.Update(gameTime, otherScreenHasFocus,coveredByOtherScreen);

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

          FrameCount++;

          if(FrameCount > soilderTextures.Length - 1)//如果播完最后一帧

          {

               FrameCount = 0; //就回到第一帧

          }

      }

    6.在模拟器最后运行效果如图4-3:

    我们执行上面写好的代码后,就在模拟器里发现人物就循环播放动画了,不过出现了新问题:士兵的跑动动作频率不够合理,像一个超人一样飞速得在奔跑。

    这是为什么呢?

    因为人物动画帧播放的时间间隔其实是要远大于update函数执行的时间间隔的,比如说人物动画是1秒播放一帧,而update函数是1/30秒(0.3333秒)播放一帧。那么我们如何处理这个问题呢?

    在回到上面的代码里,其实只要控制 FrameCount++执行的时间间隔就可以了。如何控制?那么我们需要用到另外一个计数器updateCount;

    updateCount++;

    if(updateCount>TimeSpan) //伪代码,TimeSpan为时间间隔量

    {

    FrameCount++;

    updateCount=0;

    }

    这样我们通过修改TimeSpan的值就能控制FrameCount++执行的时间间隔了。比如TimeSpan=30, 那么update函数要执行30次,FrameCount++才执行一次。也就说FrameCount++执行的时间间隔为 30*0.33333秒=1秒。

    经过合理调整,我们发现TimeSpan=2时,士兵的动作频率最协调,代码如下:

          int updateCount=0,timeSpan=2;

           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为时间间隔量

               {

                  FrameCount++;

                  updateCount = 0;

               }

               if(FrameCount > soilderTextures.Length - 1)//如果播完最后一帧

               {

                   FrameCount = 0; //就回到第一帧

               }

           }

    写完以上代码,我们就能看到精灵动画能够自如的播放了。不过这个小兵精灵并不能移动,所以有些别扭。那么我们下面一节就要研究如何让精灵移动起来。

    4.2游戏精灵的矢量移动

    我们在前面第二章讲过可以指定得把一个2D纹理绘制在特定的坐标位置上。在WP7的XNA里,坐标系如下,原点的位置是左上角,X轴向右延伸,Y轴向下延伸。

    在XNA里,2维坐标用Vector2对象来表示:

    Vector2 position = new Vector2(X,Y);

    了解了XNA里2维坐标概念后,我们还需要知道一个概念就是运动矢量,什么叫运动矢量呢?比如我向右走,也就是笔直沿X轴增大的方向走,这样我行走的方向就确定了,但是我行走的时候可快可慢,这就涉及到速度的问题。运动矢量就包含了方向和速度两个概念。在2维坐标系里,运动矢量也用Vector2 对象来表示。我用20单位的速度向右走就可以表示为 new Vector2(20,0); 很容易理解此时在X轴方向上以20单位的速度在增大(向右运动),在Y轴上没有变化。

    那么随着时间流逝,我们就能得到运动中的精灵在当前时刻所在坐标位置,用矢量计算公式:

    Vector2 speed = new Vector2(20,0);

    Vector2 EndPosition = position + speed*elipsetime;

    计算出EndPosition后,我们就在Draw方法里

    spriteBatch.Draw(Texture2D,EndPosition);

    这样我们的精灵就能移动了,改变speed运动矢量,我们就能控制精灵的运动速度和方向。当然,要让精灵停下来也很简单,speed = new Vector2(0,0)就可以了。

    改进后的代码如下:

           int FrameCount= 0;

           Vector2 endPosition = new Vector2(0,200);

           public override void Draw(GameTime gameTime)

           {

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

               float totalTime = (float)gameTime.TotalGameTime.TotalSeconds;

               ScreenManager.SpriteBatch.Begin();

               ScreenManager.SpriteBatch.Draw(soilderTextures[FrameCount], endPosition,Color.White);

               ScreenManager.SpriteBatch.End();

           }

             int updateCount=0,timeSpan=2;

           Vector2 speed=new Vector2(100,0);

           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为时间间隔量

               {

                   FrameCount++;

                   updateCount = 0;

               }

               if(FrameCount > soilderTextures.Length - 1)//如果播完最后一帧

               {

                   FrameCount = 0; //就回到第一帧

               }

               endPosition += elapsedTime * speed;

               if(endPosition.X>800)

               {

                   endPosition = new Vector2(0,200);

               }

           }

     按F5运行模拟器后,我们会看到一个不停奔跑的士兵。

    4.3制作可控的游戏精灵

          在手机游戏里,主角一般都是方向可控的,如下图4-4是一个带十字方向键的iphone游戏界面:


                                                       图4-4                       
                                
     

           结合我们第3章讲的WP7的触控操作,我们也能实现十字方向键控制的游戏精灵。

    1.     首先找到一个十字键图片,把它绘制到手机左下角,如图4-5:


     

                  图4-5


     

    2.     设定4个方向点的矩形范围。

            Rectangle topRect = newRectangle

           {

               Location = newPoint { X = 40, Y = 380 },

               Width = 30,

               Height = 30,

           };

           Rectangle bottomRect = new Rectangle

           {

               Location = new Point { X = 40, Y = 460 },

               Width = 30,

               Height = 30,

           };

           Rectangle leftRect = new Rectangle

          {

               Location = new Point { X = 2, Y = 410 },

               Width = 30,

              Height = 30,

          };

          Rectangle rightRect = new Rectangle

          {

               Location = new Point { X = 80, Y = 410 },

               Width = 30,

               Height = 30,

           };

     

    2.     重写HandleInput函数,接受触控操作:

           public override void HandleInput(InputHelperinput)

           {

             TouchCollection touchState = TouchPanel.GetState();

               foreach(TouchLocation tl in touchState)

               {        

                  if(topRect.Contains(new Point{ X = (int)tl.Position.X, Y = (int)tl.Position.Y }) && tl.State == TouchLocationState.Pressed)

                   {

                       speed = new Vector2(0,-100);

                   }

                 if(bottomRect.Contains(new Point { X = (int)tl.Position.X,Y = (int)tl.Position.Y }) &&tl.State==TouchLocationState.Pressed)

                   {

                       speed = new Vector2(0,100);

                   }

                   if(leftRect.Contains(new Point{ X = (int)tl.Position.X, Y = (int)tl.Position.Y }) && tl.State == TouchLocationState.Pressed)

                   {

                       speed = new Vector2(-100,0);

                   }

                   if(rightRect.Contains(new Point { X = (int)tl.Position.X,Y = (int)tl.Position.Y }) && tl.State== TouchLocationState.Pressed)

                  {

                       speed = new Vector2(100,0);

                   }

                   if(tl.State==TouchLocationState.Released)

                   {

                      speed = new Vector2(0,0);

                   }

               }

          }

    3.     写好以上代码后,按F5调试,在游戏主界面,我们就看到如图4-6的效果:




                                 4-6


          当然这只是个简单的DEMO,士兵后退,向上,向下的动画都不对,不过原理和士兵前进的动画一样。更复杂的动画,我们会在下一章来讲解。



       
       本文版权属于王传炜所有,首发http://www.cnblogs.com/,转载请注明出处。
  • 相关阅读:
    HDU1041
    HDU1005
    HDU1231
    MYSQL入门总结
    oracle性能问题排查~记一个单实例的问题
    mysql案例~关于mysql的配置文件个人见解
    数据恢复系列~恢复方案制定
    mysql架构解读~mysql的多源复制
    mysql 案例~select引起的性能问题
    遭遇Bad version number in .class file
  • 原文地址:https://www.cnblogs.com/wangergo/p/2265106.html
Copyright © 2020-2023  润新知