跳转到内容

使用 XNA 创建游戏/数学物理/角色动画

来自 Wikibooks,开放世界中的开放书籍

角色动画

[编辑 | 编辑源代码]

在这里,我们必须区分骨骼动画和关键帧动画。重点是展示如何使用 XNA 实现这两种类型的动画。应特别注意 XNA 框架提供的约束(例如,着色器 2.0 模型不允许超过 59 个关节)。

动画仅仅是一种错觉——它是由一系列图像创建的。每个图像都与前一个略有不同。我们只是将这样一组图像感受为变化的场景。
呈现动画最常见的方法是作为一部电影或视频程序,尽管还有其他方法。 [1]
在计算机动画中,它有两种形式:一种更“经典”的,源自翻书本的已知关键帧动画;另一种是骨骼动画,默认情况下,它来自 3D 动画。

关键帧动画

[编辑 | 编辑源代码]
关键帧动画。
http://commons.wikimedia.org/wiki/File:Muybridge_race_horse_animated.gif


关键帧动画是一种动画技术,最初用于经典卡通中。关键帧定义了动画的起点和终点。它们填充了所谓的中间帧或过渡帧。

历史,传统关键帧动画

[编辑 | 编辑源代码]

在传统的关键帧动画中,例如用于手绘动画片,高级艺术家(或关键艺术家)将绘制关键帧。(仅动画的重要图片)在粗略动画测试之后,他将其交给助手,助手进行必要的“中间帧和清理”。

计算机图形学

[编辑 | 编辑源代码]

在计算机图形学中,它与卡通的概念相同:关键帧由用户创建,中间帧由计算机补充。“关键帧”保存对象的位置、旋转和缩放等参数。计算机对后续的中间帧进行插值。

一个对象将从一个角移动到另一个角。第一个关键帧显示对象位于左上角,第二个关键帧显示对象位于右下角。两者之间的所有内容都进行了插值。

插值方法

[编辑 | 编辑源代码]

前面的部分提到了一些关键帧动画支持多种插值方法。动画的插值描述了动画在其持续时间内如何在值之间过渡。通过选择与动画一起使用的关键帧类型,可以定义该关键帧段的插值方法。有三种不同的插值方法:线性、离散和样条。
[2]

各个片段以恒定的速度通过。

使用离散插值,动画函数在值之间跳转,而不进行插值。

样条插值

[编辑 | 编辑源代码]

http://msdn.microsoft.com/uk-en/library/ms742524.aspx

Figure 1: Interpolation with cubic splines between eight points. Making traditional hand-drawn technical drawings for ship-building etc flexible rulers were bent to follow pre-defined points (the "knots")


XNA 中的关键帧动画

[编辑 | 编辑源代码]

待编辑各个参数存储在一个列表中。如果现在有了时间线的长度和元素的数量,就可以从中推断出在何时可以访问哪个关键帧。(通过增加时间线计数器,然后调用相应关键帧,就像例如在连环画中,其中 1 页是 1 个关键帧,翻到相应的页一样)。

下面显示了一个类,可以用它来实现这一点。源代码在下面可以找到。


一个小的关键帧动画类

using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace PuzzleGame
{
    /// <summary>
    /// Keyframe animation helper class.
    /// </summary>
    public class Animation
    {
        /// <summary>
        /// List of keyframes in the animation.
        /// </summary>
        List<Keyframe> keyframes = new List<Keyframe>();

        /// <summary>
        /// Current position in the animation.
        /// </summary>
        int timeline;

        /// <summary>
        /// The last frame of the animation (set when keyframes are added).
        /// </summary>
        int lastFrame = 0;

        /// <summary>
        /// Marks the animation as ready to run/running.
        /// </summary>
        bool run = false;

        /// <summary>
        /// Current keyframe index.
        /// </summary>
        int currentIndex;

        /// <summary>
        /// Construct new animation helper.
        /// </summary>
        public Animation()
        {
        }

        /// <summary>
        /// Add a keyframe to the animation.
        /// </summary>
        /// <param name="time">Time for keyframe to happen.</param>
        /// <param name="value">Value at keyframe.</param>
        public void AddKeyframe(int time, float value)
        {
            Keyframe k = new Keyframe();
            k.time = time;
            k.value = value;
            keyframes.Add(k);
            keyframes.Sort(delegate(Keyframe a, Keyframe b) { return a.time.CompareTo(b.time); });
            lastFrame = (time > lastFrame) ? time : lastFrame;
        }

        /// <summary>
        /// Reset the animation and flag it as ready to run.
        /// </summary>
        public void Start()
        {
            timeline = 0;
            currentIndex = 0;
            run = true;
        }

        /// <summary>
        /// Update the animation timeline.
        /// </summary>
        /// <param name="gameTime">Current game time.</param>
        /// <param name="value">Reference to value to change.</param>
        public void Update(GameTime gameTime, ref float value)
        {
            if (run)
            {
                timeline += gameTime.ElapsedGameTime.Milliseconds;
                value = MathHelper.SmoothStep(keyframes[currentIndex].value, keyframes[currentIndex + 1].value
                (float)timeline / (float)keyframes[currentIndex + 1].time);
                if (timeline >= keyframes[currentIndex + 1].time && currentIndex != keyframes.Count) { currentIndex++; }
                if (timeline >= lastFrame) { run = false; }
            }
        }

        /// <summary>
        /// Represents a keyframe on the timeline.
        /// </summary>
        public struct Keyframe
        {
            public int time;
            public float value;
        }
    }
}


资源: http://tcsavage.org/2011/04/keyframe-animation-in-xna/

参考文献

[编辑 | 编辑源代码]

http://xnanimation.codeplex.com/
http://msdn.microsoft.com/uk-en/library/ms742524.aspx
http://en.wikipedia.org/wiki/Animation
http://msdn.microsoft.com/uk-en/library/ms752312.aspx
http://tcsavage.org/2011/04/keyframe-animation-in-xna/
http://de.wikipedia.org/wiki/Spline-Interpolation
http://en.wikipedia.org/wiki/Spline_interpolation

ARei

骨骼动画

[编辑 | 编辑源代码]

骨骼动画是计算机动画中的一种技术,它由两部分组成:皮肤部分(称为网格)和骨骼部分(称为绑定)。皮肤表示为表面的组合,骨骼表示为骨骼的组合。这些骨骼像真正的骨骼一样相互连接,并且是分层集的一部分。结果是,您移动一根骨骼,其他应该相互作用的骨骼也会随之移动。骨骼以相同的方式动画化网格(表面)。虽然此技术通常用于动画化人类或更普遍地用于有机建模,但它仅用于使动画过程更直观,并且可以使用相同的技术来控制任何对象的变形,例如建筑物、汽车等。

骨骼(绿色)


这种技术对动画师非常有用,因为在所有动画系统中,这种简单的技术都是一个移植版本。因此,他们不需要任何复杂的算法来动画化模型。如果没有这种技术,几乎不可能将网格与骨骼结合起来进行动画化。
http://en.wikipedia.org/wiki/Skeletal_animation






骨骼-腿)


绑定是创建骨骼以动画化模型的技术。这个骨骼由骨骼(绑定)和关节组成,关节是骨骼之间的连接。通常,您将这些骨骼和关节与真实骨骼的特性相关联。例如,您首先创建上腿作为骨骼,然后创建膝盖作为关节。
http://de.wikipedia.org/wiki/Rigging_%28Animation%29




皮肤和骨骼

蒙皮(Skinning)是一种将皮肤材质赋予骨骼框架(骨骼)的技术,皮肤的运动如同骨骼的运动。蒙皮技术在绑定(Rigging)之后进行。蒙皮和绑定的区别在于,蒙皮是模型(你的模型)的视觉变形。能够设置每个单独的表面是一个很有用的特性,这在诸如手臂运动之类的场景中非常有用。即使你移动你的手臂(或模型的手臂),你的皮肤(模型的表面)会以不同的方式与运动交互,这取决于位置,例如你的肘部内侧或肘部外侧。在此背景下,还可以模拟肌肉运动。http://de.wikipedia.org/wiki/Skinning



在XNA中,模型的骨骼和多边形数量有限。

[编辑 | 编辑源代码]
  1. 骨骼:4.0版本中最多可达59到79根。
  2. 多边形:取决于硬件。
常用的程序有:
[编辑 | 编辑源代码]


XNA中的动画

[编辑 | 编辑源代码]

在XNA中,获取模型动画的最简单方法是在3D开发工具中创建动画。这些动画会自动成为导出的.x文件或.fbx文件的一部分。

一个展示XNA中动画处理的简单方法是来自http://create.msdn.com/en-US/education/catalog/sample/skinned_model的一个不错的演示。

首先我们需要一个模型和一个动画。

Model currentModel;
        AnimationPlayer animationPlayer;


下一步是更新LoadContent()方法。

protected override void LoadContent()
        {
            // Load the model.
            currentModel = Content.Load<Model>("dude");

            // Look up our custom skinning information.
            SkinningData skinningData = currentModel.Tag as SkinningData;

            if (skinningData == null)
                throw new InvalidOperationException
                    ("This model does not contain a SkinningData tag.");

            // Create an animation player, and start decoding an animation clip.
            animationPlayer = new AnimationPlayer(skinningData);

            AnimationClip clip = skinningData.AnimationClips["Take 001"];

            animationPlayer.StartClip(clip);
        }


如果将clib变量设置为数组,可以保存许多不同的动画。

AnimationClip clips= new AnimationClip[skinningData.AnimationClips.Keys.Count];
clips[0] = skinningData.AnimationClips["moveleft"];
clips[1] = skinningData.AnimationClips["moveright"];
clips[2] = skinningData.AnimationClips["jump"];


之后就可以轻松地调用不同的动画,例如通过按下跳跃键。

animationPlayer.StartClip(clip[2]);


其他动画也适用相同的方法。


参考文献

[编辑 | 编辑源代码]

http://de.wikipedia.org/wiki/Skinning
http://create.msdn.com/en-US/education/catalog/sample/skinned_model
http://de.wikipedia.org/wiki/Rigging_%28Animation%29
http://www.mit.edu/~ibaran/autorig/
http://www.mixamo.com/c/auto-rigger
http://www.der-softwareentwickler-blog.de/2011/05/30/video-tutorials-rigging-und-animation/
http://www.digitalproducer.com/2004/01_jan/tutorials/01_26/maya_rigging.htm

FixSpix

我们在本章中学到了什么

[编辑 | 编辑源代码]

在本章中,我们学习了两种不同的角色动画方法。首先是关键帧动画,然后是骨骼动画。这两种技术在XNA中非常重要。

但是哪种更好呢?

[编辑 | 编辑源代码]

在这个语境下,“更好”这个词用得不太合适,让我们用“在什么情况下更好”来替换“更好”。很简单……在3D场景中使用骨骼动画,在2D场景中使用关键帧动画。

fixspix

A.Rei 和 FixSpix

华夏公益教科书