Table of Contents

程序化动画

中级 程序员

**程序化动画(Procedural Animation)**是一种替代的动画方法。你不必自己创建动画,而是可以使用引擎组件在运行时驱动 3D 模型的动画。

在某些情况下,这能创造出更有效、更高效的动画。例如,想象一下玩家用缩小武器射击怪物时触发的缩小效果。你不必创建复杂的缩小动画,只需访问实体的 TransformComponent,简单地将敌人缩放到所需大小即可。

除了骨骼之外,动画还可以驱动各种组件的动画,包括:

Stride 的动画系统与 Blender 或 Maya 的曲线动画编辑器工作方式相同。每根骨骼/每个数值都会分配一条由若干点组成的曲线,这些点以线性、三次或常量方式插值。

代码示例

Transform 组件

public class AnimationScript : StartupScript
{
    public override void Start()
    {
        // 创建一个 AnimationClip,并确保正确设置其时长
        var animationClip = new AnimationClip { Duration = TimeSpan.FromSeconds(1) };

        // 添加指定变换属性路径的曲线。
        // - 你可以使用特殊语法按键索引组件。
        // - 属性可以用括号中的类型名加以限定。
        // - 如果类型不可序列化,则必须使用其完全限定名。

        animationClip.AddCurve("[TransformComponent.Key].Rotation", CreateRotationCurve());

        // 可选:将所有动画通道打包为优化的交错格式
        animationClip.Optimize();

        // 向当前实体添加 AnimationComponent 并注册我们的自定义剪辑
        const string animationName = "MyCustomAnimation";
        var animationComponent = Entity.GetOrCreate<AnimationComponent>();
        animationComponent.Animations.Add(animationName, animationClip);

        // 立即播放动画并循环
        var playingAnimation = animationComponent.Play(animationName);
        playingAnimation.RepeatMode = AnimationRepeatMode.LoopInfinite;
        playingAnimation.TimeFactor = 0.1f; // 放慢速度
        playingAnimation.CurrentTime = TimeSpan.FromSeconds(0.6f); // 从不同的时间开始
    }

    // 设置自定义的线性旋转曲线
    private AnimationCurve CreateRotationCurve()
    {
        return new AnimationCurve<Quaternion>
        {
            InterpolationType = AnimationCurveInterpolationType.Linear,
            KeyFrames =
            {
                CreateKeyFrame(0.00f, Quaternion.RotationX(0)),
                CreateKeyFrame(0.25f, Quaternion.RotationX(MathUtil.PiOverTwo)),
                CreateKeyFrame(0.50f, Quaternion.RotationX(MathUtil.Pi)),
                CreateKeyFrame(0.75f, Quaternion.RotationX(-MathUtil.PiOverTwo)),
                CreateKeyFrame(1.00f, Quaternion.RotationX(MathUtil.TwoPi))
            }
        };
    }

    private static KeyFrameData<T> CreateKeyFrame<T>(float keyTime, T value)
    {
        return new KeyFrameData<T>((CompressedTimeSpan)TimeSpan.FromSeconds(keyTime), value);
    }
}

灯光组件的颜色

public class AnimationLight : StartupScript
{
    public override void Start()
    {
        // 我们的实体应该有一个灯光组件
        var lightC = Entity.Get<LightComponent>();

        // 创建一个 AnimationClip,并存储不可序列化的类型。确保正确设置其时长
        var clip = new AnimationClip { Duration = TimeSpan.FromSeconds(1) };
        var colorLightBaseName = typeof(ColorLightBase).AssemblyQualifiedName;
        var colorRgbProviderName = typeof(ColorRgbProvider).AssemblyQualifiedName;

        // 指向灯光组件颜色属性的路径
        clip.AddCurve(
            $"[LightComponent.Key].Type.({colorLightBaseName})Color.({colorRgbProviderName})Value", 
            CreateLightColorCurve()
        );

        // 立即播放动画并循环
        clip.RepeatMode = AnimationRepeatMode.LoopInfinite;
        var animC = Entity.GetOrCreate<AnimationComponent>();
        animC.Animations.Add("LightCurve",clip);
        animC.Play("LightCurve");
    }
    private AnimationCurve CreateLightColorCurve()
    {
        return new AnimationCurve<Vector3>
        {
            InterpolationType = AnimationCurveInterpolationType.Linear,
            KeyFrames =
            {
                CreateKeyFrame(0.00f, Vector3.UnitX), // 将第一个关键帧设为红色

                CreateKeyFrame(0.50f, Vector3.UnitZ), // 然后是蓝色

                CreateKeyFrame(1.00f, Vector3.UnitX), // 然后再变回红色
            }
        };
    }

    private static KeyFrameData<T> CreateKeyFrame<T>(float keyTime, T value)
    {
        return new KeyFrameData<T>((CompressedTimeSpan)TimeSpan.FromSeconds(keyTime), value);
    }
}
Note

如果需要以程序化方式驱动某根骨骼的动画,必须使用 Skeleton 的 NodeTransformations 字段。

另请参阅