程序化动画
中级 程序员
**程序化动画(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 字段。