XNA动画的实现4保存动画数据
- 格式:doc
- 大小:66.00 KB
- 文档页数:12
JS实现持续动画的几种方式JavaScript是一种具有丰富动画效果的编程语言。
要在JavaScript 中实现持续动画,可以采用以下几种方式:1. setInterval(函数: setInterval(函数是JavaScript的内置函数之一,它可以以指定的时间间隔重复执行指定的代码。
通过将动画代码放在setInterval(函数中,可以实现持续动画。
例如:```javascriptsetInterval(functio//动画代码},10);```上述代码将以10毫秒的时间间隔执行动画代码,从而实现持续的动画效果。
2. requestAnimationFrame(函数: requestAnimationFrame(函数是为了实现更流畅、更高质量的动画而引入的。
它会在浏览器下一帧的时候执行指定的代码。
通过递归调用requestAnimationFrame(函数,可以实现持续的动画。
例如:```javascriptfunction animat//动画代码requestAnimationFrame(animate);animate(;```上述代码将不断调用animate(函数,从而实现持续的动画效果。
```css0% { transform: translateX(0px); }100% { transform: translateX(100px); }divanimation: myAnimation 1s infinite;```上述代码定义了一个名为myAnimation的动画,从元素的初始位置开始,在1秒钟内,将元素平移到右边100像素的位置。
通过将myAnimation动画应用到div元素上,并设置infinite属性,可以实现持续的动画效果。
4. Canvas动画: 通过使用Canvas元素和绘图API,可以在网页上绘制各种动画效果。
Canvas可以在元素中绘制和处理图形,如直线、曲线、矩形、圆形等。
Android中的动画(XML方式)实践(逐帧动画与补间动画)属性动画。
逐帧动画:基于单元格的动画,每一帧显示一个不同的drawable。
一帧一帧的顺序播放。
补间动画:补间动画应用于view,通过对其位置,大小,旋转和透明度的改变,让view动起来。
属性动画:属性动画几乎可以让应用程序中任何对象动起来。
所有补间动画的内容,都可以通过属性动画实现。
下面逐一介绍:注:这篇文章偏重于以xml方式实现相应动画效果。
逐帧动画逐帧动画(Frame-by-frame Animations)从字面上理解就是一帧一帧的播放图片,类似卡通动画。
目标:实现如下图效果:loading步骤:1.在res/drawable目录下新建loading_frame.xml文件:loading_frame.xml根节点是animation-list,内部由一到多个<item>节点组成oneshot属性表示是否只播放一次(true:一次;false:循环播放).item节点声明是一个动画帧,其中 android:drawable属性定义要显示的图像,android:druation代表此帧持续的时间,毫秒为单位。
注:在AndroidStudio中强制规定带animation-list节点xml文件必须放在res/drawable文件下(eclipse(ADT)貌似支持任意放res/drawable和res/anim)。
在androidStudio中若放在res/anim下会报错:错误提示:1错误提示:22.新建页面布局activity_frame.xml:如上图布局很简单上面一个imageview,下面两个button,都水平居中(相对于parent)。
3.新建FrameActivityframeActivity主要逻辑代码当然为了避免animationDrawable带来的内存泄露,建议在onDestroy方法中做如下操作:onDestroy注意:帧数比较多的动画不建议用逐帧动画实现,其一会显得卡顿,其二容易引起OOM。
3Dmax渲染输出教程:保存图像和动画的最佳设置3D渲染是计算机图形学中的一个重要概念,它可以将三维模型转化为二维图像或动画。
在使用3D渲染软件3Dmax进行渲染时,选择适当的输出设置非常重要,可以影响最终图像或动画的质量和文件大小。
本文将详细介绍保存图像和动画的最佳设置步骤。
一、保存图像的最佳设置步骤:1. 确定渲染场景中的视图和角度,调整相机位置和方向。
2. 点击菜单栏中的“渲染”按钮,进入渲染设置页面。
3. 在渲染设置页面的“常规”选项卡中,选择输出格式。
常用的图片格式包括JPEG、PNG和BMP等,根据需要选择相应的格式。
4. 设置输出分辨率。
分辨率决定了图像的清晰度,一般可以根据实际需要调整分辨率的大小。
5. 在“帧缓冲区”,选择需要输出的元素,例如只输出RGB通道或者透明通道等。
6. 在“图像输出”选项中,设置输出的文件路径和名称。
可以选择将渲染结果保存到本地电脑或者网络硬盘中的特定文件夹中。
7. 调整图像的质量设置。
一些格式如JPEG支持设置图像压缩比,可根据需要调整图像的质量等级。
8. 选中“启用阻止RGB低点”选项以避免图像中的黑点问题。
9. 点击“渲染”按钮开始渲染过程。
渲染速度会根据计算机硬件和渲染设置的复杂程度而有所不同。
10. 渲染完成后,在指定的输出文件夹中查看保存的图像文件。
二、保存动画的最佳设置步骤:1. 确定动画中的时间范围和帧率。
时间范围决定了动画的起始和结束时间,帧率决定了动画中每秒播放的帧数。
2. 点击菜单栏中的“渲染”按钮进入渲染设置页面。
3. 在渲染设置页面的“常规”选项卡中,选择输出格式为动画序列。
常用的动画序列格式包括AVI、MOV和MP4等。
4. 设置输出分辨率。
根据实际需要,调整分辨率的大小,一般建议使用标准的16:9或4:3比例。
5. 在“帧缓冲区”选项中,选择需要输出的元素,例如只输出RGB通道或者透明通道等。
6. 在“图像输出”选项中,设置输出的文件路径和名称,选择一个适合您的输出文件夹。
11.3 Animated Model Processor现在你需要创建一个新的模型处理器 (model processor) 扩展 XNA 默认的模型处理器。
你将使用这个新模型处理器处理动画模型,提取骨骼和动画,并将它们存储为一个 AnimatedModelData 对象。
要创建一个新模型处理器你应创建一个叫做 AnimatedModelProcessorWin 的新的素材 管道扩展库(Content Pipeline Extension Library)项目。
这个库项目来自于一个新素材处理 器类(content processor class) ,并会自动将素材管道组件(Content Pipeline assembly, Microsoft.Xna. Framework.Content. Pipeline ) 添 加 到 项 目 中 。
因 为 你 将 使 用 AnimatedModelContentWin 库(上一部分建立的)存储动画数据,所以你还需要将这个库添 加到项目中。
代码如下:[ContentProcessor] public class ContentProcessor1 : ContentProcessor<TInput, TOutput> { public override TOutput Process(TInput input, ContentProcessorContext context) { // TODO throw new NotImplementedException(); }默认素材处理器类扩展了 ContentProcessor 类,它作为任何素材管道处理器的基类,用 来处理类型为 Tinput 的对象并输出为类型为 TOutput 的新对象。
因为你对创建一个新的素材 处 理器 不感兴 趣, 只想扩 展某 些功能 ,所以 你应 扩展 一个已 有的素 材处 理器 而不是 ContentProcessor 类。
Android动画机制与使⽤技巧动画效果⼀直是⼈机交互中⾮常重要的部分,与死板、突兀的显⽰效果不同,动画效果的加⼊,让交互变得更加友好,特别是在提⽰、引导类的场景中,合理地使⽤动画能让⽤户获得更加愉悦的使⽤体验⼀、Android View动画框架Animation框架定义了透明度、旋转、缩放、位移等⼏种常见的动画实现原理:每次绘制View时,ViewGroup中的drawChild函数获取该view的Animation的Transformation值,然后调⽤canvas.concat(transformToApply.getMatrix())通过矩阵运算完成帧动画,如果动画没有完成,就继续调⽤invalidate() 函数,启动下次绘制来驱动动画,从⽽完成整个动画的绘制。
⼆、帧动画帧动画就是⼀张张图⽚不同的切换,形成的动画效果。
⼀般⼿机的开机动画,应⽤的等待动画等都是帧动画,因为只需要⼏张图⽚轮播,极其节省资源,如果真的设计成动画,那么是很耗费资源的事。
在res⽬录下新建⼀个drawable⽂件夹并定义xml⽂件,⼦节点为 animation-list,在这⾥定义要显⽰的图⽚和每张图⽚的显⽰时长。
<animation-list xmlns:android="/apk/res/android"android:oneshot="false"><!-- false表⽰循环播放,true表⽰只播放⼀次 --><item android:drawable="@drawable/g1" android:duration="200" /><item android:drawable="@drawable/g2" android:duration="200" /><item android:drawable="@drawable/g3" android:duration="200" /><item android:drawable="@drawable/g4" android:duration="200" /><item android:drawable="@drawable/g5" android:duration="200" /><item android:drawable="@drawable/g6" android:duration="300" /><item android:drawable="@drawable/g7" android:duration="400" /><!-- 慢动作 --><item android:drawable="@drawable/g8" android:duration="500" /><item android:drawable="@drawable/g9" android:duration="200" /><item android:drawable="@drawable/g10" android:duration="200" /><item android:drawable="@drawable/g11" android:duration="200" /></animation-list>在屏幕上播放帧动画,需要布局⽂件有⼀个ImageView来显⽰动画图⽚public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(yout.activity_main);ImageView iv = (ImageView) findViewById(R.id.iv);//把动画⽂件设置为imageView的背景iv.setBackgroundResource(R.drawable.frameanimation);AnimationDrawable ad = (AnimationDrawable) iv.getBackground();//播放动画ad.start();}}三、补间动画(视图动画)组件由原始状态向终极状态转变时,为了让过渡更⾃然,⽽⾃动⽣成的动画叫做补间动画。
MATLAB制作绘图动画保存视频MATLAB制作绘图动画/保存视频getframe函数和movie函数详解:getframe函数可以捕捉动画帧,并保存到矩阵中。
该函数的主要格式有:1、f = getframe,从当前图形框中得到动画帧;2、f = getframe(h),从图形句柄h中得到动画帧;3、f = getframe(h,rect),从图形句柄h的指定区域rect中得到动画帧。
当创建了⼀系列动画帧后,可利⽤movie函数播放这些动画帧。
该函数的主要格式有:1、movie(M),将矩阵M中的动画帧播放⼀次;2、movie(M, n),将矩阵M中的动画帧播放n次3、movie(M, n, fps),将矩阵M中的动画帧以每秒fps帧的速度播放n次。
axis square 当前坐标系图形设置为⽅形,刻度范围不⼀定⼀样,但是⼀定是⽅形的。
axis equal 将横轴纵轴的定标系数设成相同值,即单位长度相同,刻度是等长的,但不⼀定是⽅形的。
axis manual:将坐标轴的范围锁定为当前范围。
如果打开了hold on命令,则后续的图形都使⽤同样的坐标范围。
该函数设置XLimMode、YLimMode和ZLimMode属性为manual值。
⼀.使⽤ animatedlinean = animatedline创建⼀根没有任何数据的动画线条并将其添加到当前坐标区中。
通过使⽤addpoints函数循环向线条中添加点来创建动画。
an = animatedline(x,)创建⼀根包含由x和y定义的初始数据点的动画线条。
an = animatedline(x,y,z)创建⼀根包含由x、y和z定义的初始数据点的动画线条。
an = animatedline(___,Namevalue)使⽤⼀个或多个名称-值对组参数指定动画线条属性。
例如,'Color','r'将线条颜⾊设置为红⾊。
在前⾯语法中的任何输⼊参数组合后使⽤此选项。
第三章 用户输入和碰撞检测虽然看到一组漂亮的旋转圆环由您自己实现的时候是很酷的事情,但您还有漫漫长路要走。
尽管圆环动画看起来很漂亮,但是它没有干任何事情,并且它并不受您的控制。
一个和玩家没有交互的游戏有什么乐趣可言呢?在这一章里,我们会研究用户输入和碰撞检测来使您的游戏除了看起来很漂亮外再干点有意义的事情。
这一章将会用到第2章末尾编写的代码(圆环动画),本章讨论到的知识点将在那个项目的基础上进行实践。
更多的精灵如果您想要有一个受用户控制的物体并且加入和其他物体之间的碰撞检测,您至少还需要一个物体。
让我们加入另一个精灵动画到项目中。
我们为第二个精灵动画使用一个不同的图像。
在随书代码中的Chapter2\AnimatedSpri tes \AnimatedSprites \Content\Images文件夹下您可以找到一个名为skullball.Png(印有头骨图像的球)的图像文件。
像以前一样,添加图片到项目中 (右键点击解决方案管理器中的Content\Image文件夹,选择添加->现有项,然后找到 skullball.Png并添加到项目中)。
接下来,您要添加一些用来绘制头骨图片并产生动画的变量。
这些变量您看起来应该似曾相识了,因为它们和第2章中用来绘制圆环动画的变量很相似。
在Game1类的顶部添加这些成员变量:1 Texture2D skullTexture;23 Point skullFrameSize = new Point(75, 75);45 Point skullCurrentFrame = new Point(0, 0);67 Point skullSheetSize = new Point(6, 8);89int skullTimeSinceLastFrame = 0;1011const int skullMillisecondsPerFrame = 50;头骨图像帧的尺寸是75 X 75像素,在精灵图中有8行6列。
XNA Posing Studio教程XNALara版本升至9.8之后更名为XPS(XNA Posing Studio)。
软件的维护升级似乎也已转由XNAaraL 进行。
XPS 10之后支持动画。
现今DeviantART上已经有人从游戏中提取出AC的角色模型可供这款软件使用,下面掩饰所使用的模型也均为游戏中所提取1.首先你需要先从微软下载Microsoft .NET Framework 3.5(Win7自带可不装)安装,/downloads/details.aspx?familyid=333325FD-AE52-4E35-B531-508D977D32 A6&displaylang=en2.然后是下载Microsoft XNA Framework安装,/downloads/details.aspx?FamilyId=6521D889-5414-49B8-AB32-E3FFF05A4C 50&displaylang=en3.接着下载XPS10,绿色无需安装,已打包前两项/file/e7iwaef8或者官网下载,需注册。
xnalara. /t183-xnalara-xna-posing-studio-10854.下载好所需的角色模型,Ezio,Altair神马的/file/bejl4do25丰富的其它游戏模型(角色,物品,环境)的下载地址(涉及所有主流游戏):/gallery/https:///?cid=4A3E70355112C6C4&id=4A3E70355112C6C4!176/showthread.php?t=151335解压下载好的XPS,打开软件目录后把下载下来的角色模型解压后放到data这这个文件夹,这里我用的是Ezio和Christina 软件界面是英文的,暂时没有汉化,将就点选择file->add model,就可以看见之前放在data下的模型,这里我们选择ezio很酷,是吧,用鼠标左键试着移动一下,各个角度都是可见的,鼠标滚轮可以放大下面我们来说一下软件怎么使用,首先model这项是模型,也就是你导入了多少个模型,这里就分别有多少个模型可选然后pos,这是模型的坐标值,分别对应XYZ轴,试着输入一下感觉不同的位置变化Scale是模型大小右边的滑块区域是调整身体的各个部位的位置用的,分别对应XYZ轴,height是模型的离地高度,默认是零,一般不用调整。
Matlab与adams联合仿真实例本实例以matlab为外部控制程序,使用PID算法控制偏心杆的摆动,使偏心杆平衡到指定位置。
1.在adams/view中建立偏心杆模型图1 偏心杆模型1)新建模型如图所示,将Units设置为MMKS。
设置自己的Working Directory,这里设置为C:\adams\exercise。
点击OK按钮。
图2 新建模型对话框2)创建连杆设置连杆参数为Length=400,Width=20,Depth=20,创建如图所示的连杆。
图3 创建连杆3)创建转动幅在连杆质心MARKER点处创建转动幅,旋转副的参数设置为1Location和Normal To grid将连杆与大地相连。
图4 创建转动幅4)创建球体球体选项设置为Add to part,半径设置为20,单击连杆右侧Marker点,将球体添加到连杆上图5 创建球体5)创建单分量力矩单击Forces>Create a Torque(Single Component)Applied Forces,设置为Space Fixed,Normal to Grid,将Characteristic设置为Constant,勾选Torque并输入0,单击连杆,再点击连杆左侧的Marker点,在连杆上创建一个单分量力矩。
图6 创建单分量力矩2.模型参数设置1)创建状态变量图7 新建状态变量点击图上所示得按钮,弹出创建状态变量对话框,创建输入状态变量Torque,将Name 修改为.MODEL_1.Torque。
图8 新建输入状态变量Torque再分别创建状态变量Angel和Velocity(后面所设计控制系统为角度PID控制,反馈变量为Angel,Velocity为Angel对时间求导,不需要变量Velocity,这里设置Velocity是为了展示多个变量的创建)。
设置Angel的函数AZ(MARKER_3,MARKER_4)*180/PI,Velocity 的函数为WZ(MARKER_3,MARKER_4)*180/PI。
android 动画的实现原理Android动画的实现原理是通过改变视图的属性值来创建视觉效果。
在Android中,动画可以通过三种方式实现:补间动画、帧动画和属性动画。
1. 补间动画:补间动画是指通过指定开始和结束状态,系统会自动完成状态之间的过渡动画。
常用的补间动画包括平移动画、缩放动画、旋转动画和淡入淡出动画。
补间动画是通过对目标视图的transform属性进行变换来实现的,通过对动画的属性值进行插值计算,系统会将中间状态渐变地应用到目标视图上,从而实现动画效果。
2. 帧动画:帧动画是指将连续的图片资源按照一定时间间隔依次播放,从而形成动画效果。
帧动画是通过逐帧显示不同的图片资源来实现的。
在Android中,帧动画可以通过逐个资源文件的方式创建,也可以通过在XML文件中定义动画资源实现。
3. 属性动画:属性动画是Android3.0引入的一种动画机制,相较于补间动画和帧动画,属性动画更加强大和灵活。
属性动画通过修改目标视图的属性值来实现动画效果。
在属性动画中,可以通过指定目标视图的属性名称和起始值以及结束值,系统会自动根据这些信息来计算出中间过渡值,并将其应用到目标视图上。
属性动画可以实现更加复杂和精细的动画效果,例如借助关键帧来定义关键状态,或者通过设置动画插值器来控制动画的速度曲线。
总的来说,Android动画的实现原理是通过改变视图的属性值来实现动画效果。
不同的动画类型有不同的实现方式,包括补间动画、帧动画和属性动画。
补间动画是通过对目标视图的属性进行变换来实现的,帧动画是逐帧显示不同的图片资源,而属性动画是通过修改目标视图的属性值来实现动画效果。
XNA动画的实现4——保存动画数据在上一篇文章中,我们实现了关键帧类实现了复杂的动画,这些复杂动画的数据都是在程序中手动生成的,为了能够重用这些数据,有必要实现下述功能:将动画数据保存为磁盘上的一个文件,使用时只要从磁盘上读取这个文件构成动画数据类,就可以在内存中使用它了。
要做到这一点,只需使用.NET框架的文件IO功能将动画数据文件串行化。
首先是关键帧数据类Keyframe.cs,代码如下:/// <summary>/// 关键帧类,表示一个动画片段(AnimationClip)中一个指定时刻的状态(包含位置、旋转、缩放信息),/// 多个关键帧组成一个动画片段。
/// </summary>public class Keyframe{/// <summary>/// 位置/// </summary>public Vector3 Position{ get;set;}/// <summary>/// 旋转/// </summary>public Vector3 Rotation{ get;set;}/// <summary>/// 缩放/// </summary>public Vector3 Scale{ get;set;}/// <summary>/// 此关键帧离开动画开始时刻的时间,单位为秒/// </summary>public double Time{ get;set;}/// <summary>/// 创建一个关键帧/// </summary>public Keyframe(Vector3 Position, Vector3 Rotation, Vector3 Scale, double Time){this.Position = Position;this.Rotation = Rotation;this.Scale = Scale;this.Time = Time;}// 必须要有一个不带参数的构造函数用于串行化private Keyframe() { }}这个代码与上一篇文章几乎是一样的,但有三点不同:1.各个属性由原来的只读变成可写,这样做破坏了类的封装性,让用户可以设置属性值,从而导致错误。
但是要让这个类可串行化,属性必须可写。
2.Time属性由原来的TimeSpan类型变成了double类型。
这是因为.NET框架无法串行化TimeSpan类型,导出为空值,所以只能换种类型。
下一篇文章的方法可以解决这个问题。
3.必须要有一个不带参数的构造函数,否则串行化过程会报错。
然后是动画数据类AnimationClip.cs,你需要将数据和方法分离,因此,相对于上一篇文章,这个类删除了方法,只保留了数据,变得简单多了。
代码如下:/// <summary>/// 动画片段类,这个类保存一个关键帧集合和动画持续时间。
/// </summary>public class AnimationClip{/// <summary>/// 动画片段的播放长度/// </summary>public double Duration { get; set; }/// <summary>/// 关键帧集合/// </summary>public List<Keyframe> Keyframes { get; set; }/// <summary>/// 创建一个动画片段/// </summary>/// <param name="Keyframes">关键帧集合</param>public AnimationClip(List<Keyframe> Keyframes){this.Keyframes = Keyframes;//对关键帧根据时间先后进行排序Keyframes.Sort(CompareKeyframeTimes);// 动画播放的时间就是最后一个关键帧的Time属性this.Duration = Keyframes[Keyframes.Count - 1].Time;}/// <summary>/// 创建一个动画片段,数据是从一个xml文件加载的/// </summary>public AnimationClip(string fileName){// ----------------------------// 下述代码应该还有改进空间// ---------------------------AnimationClip animationData;// 将xml文件反串行化获取动画数据using (XmlReader reader = XmlReader.Create(fileName)){XmlSerializer serializer = new XmlSerializer(typeof(AnimationClip));animationData = (AnimationClip)serializer.Deserialize(reader);}this.Keyframes = animationData.Keyframes;//对关键帧根据时间先后进行排序Keyframes.Sort(CompareKeyframeTimes);// 动画播放的时间就是最后一个关键帧的Time属性this.Duration = Keyframes[Keyframes.Count - 1].Time;}// 必须要有一个不带参数的构造函数用于串行化private AnimationClip(){}// 按照关键帧的时间属性升序排序的Comparison方法int CompareKeyframeTimes(Keyframe a, Keyframe b){return pareTo(b.Time);}}从代码中我们可以看出,首先我们在AnimationClip中添加了一个表示动画持续时间的Duration属性,它实际上就是关键帧数组中最后一个的时间Time属性,将它提取出来可以简化后面的操作。
而且我们还编写了一个以动画数据名称为参数的构造函数,在这个构造函数中,我们使用了IO功能从磁盘的动画数据xml文件中读取了信息构建了AnimationClip类。
而原来的AnimationClip类中的方法移至一个新的类中,我命名为AnimationPlayer,它负责计算变换矩阵,控制动画的播放。
代码如下:/// <summary>/// 控制动画播放的类,这个类负责计算变换矩阵,还可以调整动画播放的速度/// </summary>public class AnimationPlayer{private AnimationClip animationClip; // 动画片段TimeSpan startTime, endTime; // 动画播放开始时刻和结束时刻TimeSpan elapsedTime; // 动画当前已经播放的时间int currentKeyframeIndex; // 动画当前播放的关键帧索引bool loop; // 动画是否循环float playbackRate = 1.0f; // 动画播放倍率,1表示正常速度播放LerpMode lerpMode; // 动画数据的插值模式Vector3 position, rotation, scale; // 当前时刻的位置、旋转和缩放/// <summary>/// 动画是否已经播放完成/// </summary>public bool Done { get; private set; }/// <summary>/// 动画是否处于暂停状态/// </summary>public bool Paused { get; set; }/// <summary>/// 变换矩阵/// </summary>public Matrix Transform { get; private set; }/// <summary>/// 创建一个动画播放类/// </summary>/// <param name="setAnimationClip">要播放的动画片段</param>public AnimationPlayer(AnimationClip setAnimationClip){this.animationClip = setAnimationClip;}/// <summary>/// 更新动画数据/// </summary>public void Update(GameTime gameTime){if (animationClip == null || Done)return;if (Paused)return;TimeSpan time = gameTime.ElapsedGameTime;// 调整动画播放速度if (playbackRate != 1.0f)time = TimeSpan.FromSeconds(time.TotalSeconds * playbackRate);elapsedTime += time;// 进行插值操作updateTransforms();}void updateTransforms(){// 如果动画播放的时间已经超过指定的时间间隔...while (elapsedTime >= (endTime - startTime)){// 如果是循环动画,则将动画播放时间回退到0if (loop){elapsedTime -= (endTime - startTime);currentKeyframeIndex = 0;}// 否则,将播放时间进行截取else{Done = true;elapsedTime = endTime - startTime;break;}}// 读取关键帧集合IList<Keyframe> keyframes = animationClip.Keyframes;// 首先获取离开整个动画开始时刻的时间,然后根据它获取当前关键帧的索引// 最后获取从当前帧开始的动画播放时间,这个才能根据这个时间进行插值运算double totalTime = (elapsedTime+ startTime).TotalSeconds;while (keyframes[currentKeyframeIndex + 1].Time < totalTime )currentKeyframeIndex++;totalTime -= keyframes[currentKeyframeIndex].Time;// 获取播放到当前关键帧至下一个关键帧之间的位置,介于0和1之间float amt = (float)((totalTime) /(keyframes[currentKeyframeIndex + 1].Time - keyframes[currentKeyframeIndex].Time));// 根据插值模式对位置进行插值运算if (lerpMode == LerpMode.Linear)position = Vector3.Lerp(keyframes[currentKeyframeIndex].Position,keyframes[currentKeyframeIndex + 1].Position, amt);elseposition = catmullRom3D(keyframes[wrap(currentKeyframeIndex - 1, keyframes.Count - 1)].Position,keyframes[wrap(currentKeyframeIndex, keyframes.Count - 1)].Position,keyframes[wrap(currentKeyframeIndex + 1, keyframes.Count - 1)].Position,keyframes[wrap(currentKeyframeIndex + 2, keyframes.Count - 1)].Position, amt);// 曲线运动中的旋转和缩放仍然是线性插值rotation = Vector3.Lerp(keyframes[currentKeyframeIndex].Rotation,keyframes[currentKeyframeIndex + 1].Rotation, amt);scale = Vector3.Lerp(keyframes[currentKeyframeIndex].Scale,keyframes[currentKeyframeIndex + 1].Scale, amt);// 计算变换矩阵Transform = Matrix.CreateScale(scale) *Matrix.CreateFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z) *Matrix.CreateTranslation(position);}Vector3 catmullRom3D(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, float amt){return new Vector3(MathHelper.CatmullRom(v1.X, v2.X, v3.X, v4.X, amt),MathHelper.CatmullRom(v1.Y, v2.Y, v3.Y, v4.Y, amt),MathHelper.CatmullRom(v1.Z, v2.Z, v3.Z, v4.Z, amt));}// 辅助方法,将输入的"value"参数在[0, max]范围内循环int wrap(int value, int max){while (value > max)value -= max;while (value < 0)value += max;return value;}/// <summary>/// 播放完整的动画片段/// </summary>/// <param name="loop">是否循环播放</param>/// <param name="lerpMode">变换插值模式</param>/// <param name="playbackRate">动画播放倍率</param>public void StartClip(bool loop,LerpMode lerpMode,float playbackRate){StartClip(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(animationClip.Duration), loop, lerpMode, playbackRate);}/// <summary>/// 根据给定的关键帧索引播放两关键帧之间的动画片段/// </summary>/// <param name="startFrame">开始时刻的关键帧索引</param>/// <param name="endFrame">结束时刻的关键帧索引</param>/// <param name="loop">是否循环播放</param>/// <param name="lerpMode">变换插值模式</param>/// <param name="playbackRate">动画播放倍率</param>public void StartClip(int startFrame, int endFrame, bool loop,LerpMode lerpMode,float playbackRate){StartClip(TimeSpan.FromSeconds (animationClip.Keyframes[startFrame].Time), TimeSpan.FromSeconds (animationClip.Keyframes[endFrame].Time),loop,lerpMode,playbackRate);}/// <summary>/// 根据给定的开始时刻和结束时刻播放一段时间内的动画片段/// </summary>/// <param name="StartTime">开始时刻</param>/// <param name="EndTime">结束时刻</param>/// <param name="loop">是否循环播放</param>/// <param name="lerpMode">变换插值模式</param>/// <param name="playbackRate">动画播放倍率</param>public void StartClip(TimeSpan StartTime, TimeSpan EndTime, bool loop,LerpMode lerpMode,float playbackRate){this.startTime = StartTime;this.endTime = EndTime;this.loop = loop;this.lerpMode = lerpMode;this.playbackRate =playbackRate;elapsedTime = TimeSpan.FromSeconds(0);currentKeyframeIndex = 0;}/// <summary>/// 暂停播放/// </summary>public void PauseClip(){Paused = true;}/// <summary>/// 继续播放/// </summary>public void ResumeClip(){Paused = false;}}代码较长,但核心代码的原理与上一章是类似的。