设为首页 收藏本站
查看: 854|回复: 0

Windows Phone 7范例游戏Platformer实战8——精灵动画的绘制实现

[复制链接]
YunVN网友  发表于 2015-5-9 15:25:19 |阅读模式
  
  Windows Phone 7范例游戏Platformer实战1——5大平台支持
Windows Phone 7范例游戏Platformer实战2——游戏设计初步
Windows Phone 7范例游戏Platformer实战3——游戏资源和内容管道
Windows Phone 7范例游戏Platformer实战4——冲突检测的实现
Windows Phone 7范例游戏Platformer实战5——多点触控编程
  Windows Phone 7范例游戏Platformer实战6——加速度传感器解读
  Windows Phone 7范例游戏Platformer实战7——简单动画的绘制实现原理

  
  本文参考了木木二进制翻译的Learning XNA 3.0文章,以及CSDN上的 XNA基础一文。非常感谢他们做出的杰出贡献。
  
  在上一小节介绍了宝石的简单动画编程后,现在我们开始学习如何实现一个真正的动画,也就是如何将一连串的图片形成连贯的动作。下面是僵尸怪的精灵图片,我们可以看到它包含10个动作,在XNA中正是将这些动作连贯起来才形成我们看到的僵尸鬼的跑动。本小节我们就是围绕如何将该图片实现为僵尸怪的跑动展开的。
  
DSC0000.jpg
  
上面的10个小图片表现了僵尸怪跑动的所有动作,在实际制作时,我们通常将这些图片按顺序制作在一张大图中,并且保证大图中每个小图的尺寸是完全一样的。我们称这样的大图为精灵帧序列图Sprite Sheets。
  
  上节我们提过,我们是使用循环的方式实现游戏界面的绘制,因此我们可以对包含这10个动作的精灵图片进行解析,在用户视角停留范围内绘制出僵尸怪的分解动作就可以了。那么我们如何实现这10个动作的分解呢,这就需要程序来进行解析了。
  
首先我们需要将僵尸怪精灵图片加载到内容管道中,因此一个Texture2D纹理对象是必不可少的。此外,为了让僵尸怪跑动的连贯,因此每帧显示的时间也是要考虑的,这个值要选择的符合游戏的呈现效果,太小的话动画效果好像一闪而过,太大有让用户觉得僵尸怪被卡死。
  
我们在僵尸怪精灵图片看到一共有10个动作,而这个精灵图片的大小为640*64像素,也就是说每个动作的宽度为64,高度也为64像素。
  
我们知道动画存在几种可能,一种就是当动画解析到最后一个动作时直接停止。一种就是当最后一个动作解析完毕又从头开始播放,这两种也是我们Platformer游戏中使用的动画呈现方式。这些都清楚了,那么我们就可以很轻松实现一个对精灵图片进行解析的动画类了。下面是动画类Animation的完整代码:
  


1     class Animation
2     {
3         ///
4         /// 动画中的所有帧均按水平排列,僵尸怪的动画一共10帧
5         ///
6         public Texture2D Texture
7         {
8             get { return texture; }
9         }
10         Texture2D texture;
11
12         ///
13         /// 每帧显示的时间,
14         ///
15         public float FrameTime
16         {
17             get { return frameTime; }
18         }
19         float frameTime;
20
21         ///
22         /// 动画解析到最后一帧时,是否从头开始再次播放
23         ///
24         public bool IsLooping
25         {
26             get { return isLooping; }
27         }
28         bool isLooping;
29
30         ///
31         /// 获得帧数,使用僵尸怪精灵图片的宽度/每帧的宽度即可得出,这里为10
32         ///
33         public int FrameCount
34         {
35             get { return Texture.Width / FrameWidth; }
36         }
37
38         ///
39         /// 动画中每帧的宽度
40         ///
41         public int FrameWidth
42         {
43             // Assume square frames.
44             get { return Texture.Height; }
45         }
46
47         ///
48         /// 动画中每帧的高度
49         ///
50         public int FrameHeight
51         {
52             get { return Texture.Height; }
53         }
54
55         ///
56         /// 动画类构造函数
57         ///         
58         public Animation(Texture2D texture, float frameTime, bool isLooping)
59         {
60             this.texture = texture;
61             this.frameTime = frameTime;
62             this.isLooping = isLooping;
63         }
64     }  
  OK,这一切都完成之后,我们还需要对动画进行不同类型的播放控制。比如说游戏程序检测到僵尸怪动画从未播放过,那么好,动画就第一帧开始。如果动画已经在播放,那么动画从上个动作继续进行绘制和解析。下面就是判定动画是否已经播放的代码
  


public void PlayAnimation(Animation animation)
{
   //如果动画已经在运行,无需重新开始
   if (Animation == animation)
   return;
   // 开始新的动画
   this.animation = animation;
   this.frameIndex = 0;
   this.time = 0.0f;
}  
  前面我提到过帧速率的概念,这里再次说明一下:帧速率表示一秒钟游戏重绘场景的次数。在WP7的XNA中,帧速率默认为30fps。一般来说达到了30fps就可以在WP7获得较好的画面流畅度了。
  
  还有一种不同类型的帧速率,和单独的动画相关,这种帧速率(通常称为动画速度)反映了给定动画帧序列绘制一次的速度,或者说一秒钟绘制的动画帧数。
  
  有几种方法可以改变僵尸怪动画的速度。XNA的Game类有一个叫做TargetElapsedTime的属性用来告诉XNA在每次Game.Update调用之间要等待多久。本质上这个属性表示每个帧之间的时间间隔。WP7上默认情况下这个值被设为1/30秒,也就是帧速度为30fps。
  
  要改变您程序的帧速率,添加以下代码到PlatformerGame类构造函数的末尾:
  


TargetElapsedTime = new TimeSpan(0, 0, 0, 0, 50);
  
  这个告诉XNA每50微秒调用一次Game.Update,相当于帧速率20fps。编译游戏并运行它,您会发现动画以低得多的速度运行。在TimeSpan构造函数中尝试不同的值(比如说,1毫秒)来看看动画循环的速度。
  
  理想的情况是你应该保持帧速率在30fps左右,就是说可以不用管默认帧数。为什么30fps是个标准呢?这是让手机屏幕不会让人眼察觉到闪烁的最低刷新率。如果您将帧速率调得太高,XNA不保证您能获得期望的性能。GPU的速度,处理器的速度,您消耗的资源和代码的效率决定了您的游戏是否能达到最好的性能。
  
  幸运的是,XNA提供了一种方法来检测您的游戏是否存在性能问题。Update和Draw方法都有的GameTime对象参数,有一个叫做IsRunningSlow的布尔类型的属性。您能在任何时候在这两个方法中检查IsRunningSlow的值。如果值为true,XNA不能跟上您指定的帧速率。在这种情况下,XNA会进行跳帧尽力达到您期望的速率。这也许不是您愿意在任何游戏中看到的结果。所以如果出现这样的情况,您或许应该提醒用户,他的机器配置在运行游戏时非常困难,应该释放其它的程序占用的资源以便获得最佳的性能。
  
  
  调整动画速度
  
尽管调整游戏本身的帧速率可以影响动画的速度,但是这样做并不是理想的方法。为什么呢?当您改变了游戏的帧速率,将会影响到所有精灵的动画速度,比如英雄和僵尸怪的移动速度会变得非常不自然。如果您希望一个动画的速度为30fps而另一个为20fps,您就不应该通过改变整个游戏的帧速率来实现。所以说实现一个动画并非难事,真正困难的地方在于如何控制每个动画可以有自己不同的刷新速度,如何使同一个动画在不同配置的机器上表现相同。
  
  移除之前修改TargetElapsedTime的代码,让我们试试其他的途径。
  
  当机器的配置不够时,XNA会自动跳过某些次绘制——即不调用Draw()方法。我们可以通过GameTime(你还记得Update和Draw方法都有一个该类型的参数)的IsRunningSlowly属性来检测实际的帧率是否比我们设定的要小。通过修改TargetElapsedTime属性来设置帧率,会使所有的动画都受到影响,因为它实际修改的是调用Update()和Draw()的频率。
  
  那么如何使一个动画以自己恒定的速度刷新了?包括这个动画的刷新速度不受主帧率(即TargetElapsedTime设定的值)和机器配置的影响,当然,前提条件是我们动画的刷新率不能大于主帧率,也不能超出WP7配置允许的最大帧率。
  
  我们可以用类似下面的代码来控制每个动画以自己的刷新率运行:
  


1 ///
2 /// 根据时间的推进,在合适的位置绘制动画中帧
3 ///
4 public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Vector2 position, SpriteEffects spriteEffects)
5 {
6    if (Animation == null)
7        throw new NotSupportedException("No animation is currently playing.");
8
9    //查看是否满足跳帧的条件,是的话,则直接绘制下一帧
10    time += (float)gameTime.ElapsedGameTime.TotalSeconds;
11    while (time > Animation.FrameTime)
12    {
13        time -= Animation.FrameTime;
14
15         //查看动画是否支持循环
16          //是的话,动画在最后帧结束后从头开始
17        if (Animation.IsLooping)
18         {
19            frameIndex = (frameIndex + 1) % Animation.FrameCount;
20         }
21         else
22         {
23            frameIndex = Math.Min(frameIndex + 1, Animation.FrameCount - 1);
24         }
25    }
26
27    // 计算当前帧在精灵图片中的位置,比如说僵尸怪第一个动作的矩形区域为(0,0,64,64)
28    Rectangle source = new Rectangle(FrameIndex * Animation.Texture.Height, 0,
29    Animation.Texture.Height, Animation.Texture.Height);
30
31    // 绘制当前帧
32    spriteBatch.Draw(Animation.Texture, position, source, Color.White, 0.0f, Origin, 1.0f,
33    spriteEffects, 0.0f);
34 }  
  通过上述代码,我们就可以控制目标Sprite的动画速率为Animation.FrameTime的设定值,比如说20fps,如果两次Update()之间的绘制时间超过了Animation.FrameTime的设定值,那么游戏会发生跳帧的现象,也就是直接跳到原本应该绘制帧的下一帧。在实际的应用中,我们可以将上述控制帧率的代码放到Sprite的基类中,这样就可以控制不同的Sprite以各自的速率运行了。
  
  我们来看前面Draw()方法中28-33行代码的含义:
  


Rectangle source = new Rectangle(FrameIndex * Animation.Texture.Height, 0, Animation.Texture.Height, Animation.Texture.Height);
spriteBatch.Draw(Animation.Texture, position, source, Color.White, 0.0f, Origin, 1.0fspriteEffects, 0.0f);

  
  前面说了我们可以将僵尸怪的精灵图元分解为一个个的动作,在僵尸怪精灵序列图片中,一共包含10个分解动作的小图片。每个小图片的大小都为64*64像素,宽和高都是一样的。我们可以将这10个小图片的索引安装顺序定位1-9。这里程序中将索引定位从0开始,而不是1,这一点要注意下。
  

  
  
  这样的话,我们就知道索引为0的图片其矩形区域为(0,0,64,64),索引为1的小图片矩形区域为(64,0,64,64)。如果不发生跳帧的现象,每调用Draw()代码一次图片的索引值就会发生增加1,又或者索引重新变为0(在动画允许循环的情况下)。这样动画每次绘制的僵尸怪动作都不一样,这样就依次读取图片区域并绘制就形成了连贯的动画。因为轩辕不太懂GIF动画图片的实现,下面用了个类似的图片替代僵尸怪的跑动动画:
  
DSC0001.gif
  
  下面是动画播放结构体AnimationPlayer的完整代码:
  

DSC0002.gif DSC0003.gif 代码

  1 #region File Description
  2 //-----------------------------------------------------------------------------
  3 // AnimationPlayer.cs
  4 //
  5 // Microsoft XNA Community Game Platform
  6 // Copyright (C) Microsoft Corporation. All rights reserved.
  7 //-----------------------------------------------------------------------------
  8 #endregion
  9
10 using System;
11 using Microsoft.Xna.Framework;
12 using Microsoft.Xna.Framework.Graphics;
13
14 namespace Platformer
15 {
16     ///
17     /// Controls playback of an Animation.
18     ///
19     struct AnimationPlayer
20     {
21         ///
22         /// Gets the animation which is currently playing.
23         ///
24         public Animation Animation
25         {
26             get { return animation; }
27         }
28         Animation animation;
29
30         ///
31         /// Gets the index of the current frame in the animation.
32         ///
33         public int FrameIndex
34         {
35             get { return frameIndex; }
36         }
37         int frameIndex;
38
39         ///
40         /// The amount of time in seconds that the current frame has been shown for.
41         ///
42         private float time;
43
44         ///
45         /// Gets a texture origin at the bottom center of each frame.
46         ///
47         public Vector2 Origin
48         {
49             get { return new Vector2(Animation.FrameWidth / 2.0f, Animation.FrameHeight); }
50         }
51
52         ///
53         /// Begins or continues playback of an animation.
54         ///
55         public void PlayAnimation(Animation animation)
56         {
57             // If this animation is already running, do not restart it.
58             if (Animation == animation)
59                 return;
60
61             // Start the new animation.
62             this.animation = animation;
63             this.frameIndex = 0;
64             this.time = 0.0f;
65         }
66
67         ///
68         /// Advances the time position and draws the current frame of the animation.
69         ///
70         public void Draw(GameTime gameTime, SpriteBatch spriteBatch, Vector2 position, SpriteEffects spriteEffects)
71         {
72             if (Animation == null)
73                 throw new NotSupportedException("No animation is currently playing.");
74
75             // Process passing time.
76             time += (float)gameTime.ElapsedGameTime.TotalSeconds;
77             while (time > Animation.FrameTime)
78             {
79                 time -= Animation.FrameTime;
80
81                 // Advance the frame index; looping or clamping as appropriate.
82                 if (Animation.IsLooping)
83                 {
84                     frameIndex = (frameIndex + 1) % Animation.FrameCount;
85                 }
86                 else
87                 {
88                     frameIndex = Math.Min(frameIndex + 1, Animation.FrameCount - 1);
89                 }
90             }
91
92             // Calculate the source rectangle of the current frame.
93             Rectangle source = new Rectangle(FrameIndex * Animation.Texture.Height, 0, Animation.Texture.Height, Animation.Texture.Height);
94
95             // Draw the current frame.
96             spriteBatch.Draw(Animation.Texture, position, source, Color.White, 0.0f, Origin, 1.0f, spriteEffects, 0.0f);
97         }
98     }
99 }
100   
  AnimationPlayer结构体和Animation构成了Platformer游戏的动画基础,所有的和僵尸怪、英雄相关的所有动画实现都是构建于它们之上。完成这两个类后,我们实现僵尸怪的跑动效果就可以说是非常简单了。轩辕将在下一节介绍僵尸怪的跑动动画和它本身的特性的所有实现。
  
  喜欢这篇文章的兄弟们点击文章下面的“推荐”支持下。
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-65316-1-1.html 上篇帖子: Windows phone 7之基础控件-属性与事件 下篇帖子: Windows Phone 7 hello world
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表