public void GetDefaultDurationTest() { var timelineGroup = new TimelineGroup(); var animation = new Vector2FAnimation(); var animation2 = new AnimationClip <float> { Animation = new SingleFromToByAnimation(), Delay = TimeSpan.FromSeconds(10), }; var animation3 = new AnimationClip <float> { Animation = new SingleFromToByAnimation(), Delay = TimeSpan.FromSeconds(8), Duration = TimeSpan.FromSeconds(4), }; Assert.AreEqual(TimeSpan.FromSeconds(0.0), timelineGroup.GetTotalDuration()); timelineGroup.Add(animation); Assert.AreEqual(TimeSpan.FromSeconds(0.0), timelineGroup.GetTotalDuration()); timelineGroup.Add(animation2); Assert.AreEqual(TimeSpan.FromSeconds(11.0), timelineGroup.GetTotalDuration()); timelineGroup.Add(animation3); Assert.AreEqual(TimeSpan.FromSeconds(12.0), timelineGroup.GetTotalDuration()); }
public void GetSelfAndAncestors() { var animationA = new TimelineGroup(); var animationB = new SingleFromToByAnimation(); var animationC = new SingleFromToByAnimation(); var animationD = new TimelineGroup(); var animationE = new TimelineGroup(); var animationF = new SingleFromToByAnimation(); var animationG = new TimelineGroup(); animationA.Add(animationB); animationA.Add(animationC); animationA.Add(animationD); animationD.Add(animationE); animationD.Add(animationF); animationE.Add(animationG); Assert.That(() => AnimationHelper.GetSelfAndAncestors(null), Throws.TypeOf <ArgumentNullException>()); var animationInstance = animationA.CreateInstance(); var ancestors = animationInstance.Children[2].Children[0].GetSelfAndAncestors().ToArray(); Assert.AreEqual(3, ancestors.Length); Assert.AreEqual(animationInstance.Children[2].Children[0], ancestors[0]); Assert.AreEqual(animationInstance.Children[2], ancestors[1]); Assert.AreEqual(animationInstance, ancestors[2]); }
public void GetDescendantsDepthFirst() { var animationA = new TimelineGroup(); var animationB = new SingleFromToByAnimation(); var animationC = new SingleFromToByAnimation(); var animationD = new TimelineGroup(); var animationE = new TimelineGroup(); var animationF = new SingleFromToByAnimation(); var animationG = new TimelineGroup(); animationA.Add(animationB); animationA.Add(animationC); animationA.Add(animationD); animationD.Add(animationE); animationD.Add(animationF); animationE.Add(animationG); Assert.That(() => AnimationHelper.GetDescendants(null), Throws.TypeOf <ArgumentNullException>()); var animationInstance = animationA.CreateInstance(); var descendants = animationInstance.GetDescendants().ToArray(); Assert.AreEqual(6, descendants.Length); Assert.AreEqual(animationInstance.Children[0], descendants[0]); Assert.AreEqual(animationInstance.Children[1], descendants[1]); Assert.AreEqual(animationInstance.Children[2], descendants[2]); Assert.AreEqual(animationInstance.Children[2].Children[0], descendants[3]); Assert.AreEqual(animationInstance.Children[2].Children[0].Children[0], descendants[4]); Assert.AreEqual(animationInstance.Children[2].Children[1], descendants[5]); }
public void CreateInstanceTest() { var animation1 = new SingleFromToByAnimation(); var animation2 = new SingleFromToByAnimation(); var animation3 = new SingleFromToByAnimation(); var childGroup = new TimelineGroup(); childGroup.Add(animation2); childGroup.Add(animation3); var rootGroup = new TimelineGroup(); rootGroup.Add(animation1); rootGroup.Add(childGroup); var animationInstance = rootGroup.CreateInstance(); Assert.IsNotNull(animationInstance); Assert.AreEqual(2, animationInstance.Children.Count); Assert.That(animationInstance.Children[0], Is.TypeOf <AnimationInstance <float> >()); Assert.That(animationInstance.Children[0].Animation, Is.EqualTo(animation1)); Assert.That(animationInstance.Children[1], Is.TypeOf <AnimationInstance>()); Assert.That(animationInstance.Children[1].Animation, Is.EqualTo(childGroup)); Assert.That(animationInstance.Children[1].Children[0], Is.TypeOf <AnimationInstance <float> >()); Assert.That(animationInstance.Children[1].Children[0].Animation, Is.EqualTo(animation2)); Assert.That(animationInstance.Children[1].Children[1], Is.TypeOf <AnimationInstance <float> >()); Assert.That(animationInstance.Children[1].Children[1].Animation, Is.EqualTo(animation3)); }
public void GetDefaultDurationTest() { var timelineGroup = new TimelineGroup(); var animation = new Vector2FAnimation(); var animation2 = new AnimationClip<float> { Animation = new SingleFromToByAnimation(), Delay = TimeSpan.FromSeconds(10), }; var animation3 = new AnimationClip<float> { Animation = new SingleFromToByAnimation(), Delay = TimeSpan.FromSeconds(8), Duration = TimeSpan.FromSeconds(4), }; Assert.AreEqual(TimeSpan.FromSeconds(0.0), timelineGroup.GetTotalDuration()); timelineGroup.Add(animation); Assert.AreEqual(TimeSpan.FromSeconds(0.0), timelineGroup.GetTotalDuration()); timelineGroup.Add(animation2); Assert.AreEqual(TimeSpan.FromSeconds(11.0), timelineGroup.GetTotalDuration()); timelineGroup.Add(animation3); Assert.AreEqual(TimeSpan.FromSeconds(12.0), timelineGroup.GetTotalDuration()); }
public void CreateInstanceTest() { var animation1 = new SingleFromToByAnimation(); var animation2 = new SingleFromToByAnimation(); var animation3 = new SingleFromToByAnimation(); var childGroup = new TimelineGroup(); childGroup.Add(animation2); childGroup.Add(animation3); var rootGroup = new TimelineGroup(); rootGroup.Add(animation1); rootGroup.Add(childGroup); var animationInstance = rootGroup.CreateInstance(); Assert.IsNotNull(animationInstance); Assert.AreEqual(2, animationInstance.Children.Count); Assert.That(animationInstance.Children[0], Is.TypeOf<AnimationInstance<float>>()); Assert.That(animationInstance.Children[0].Animation, Is.EqualTo(animation1)); Assert.That(animationInstance.Children[1], Is.TypeOf<AnimationInstance>()); Assert.That(animationInstance.Children[1].Animation, Is.EqualTo(childGroup)); Assert.That(animationInstance.Children[1].Children[0], Is.TypeOf<AnimationInstance<float>>()); Assert.That(animationInstance.Children[1].Children[0].Animation, Is.EqualTo(animation2)); Assert.That(animationInstance.Children[1].Children[1], Is.TypeOf<AnimationInstance<float>>()); Assert.That(animationInstance.Children[1].Children[1].Animation, Is.EqualTo(animation3)); }
public void AnimateProperty() { var property = new AnimatableProperty <float> { Value = 10.0f }; var animationA = new SingleFromToByAnimation { From = 100.0f, To = 200.0f, TargetObject = "ObjectA", // Should be ignored. TargetProperty = "PropertyA", // Should be ignored. }; var animationB = new SingleFromToByAnimation { From = 200.0f, To = 300.0f, TargetObject = "ObjectB", // Should be ignored. TargetProperty = "PropertyB", // Should be ignored. }; var animationGroup = new TimelineGroup(); animationGroup.Add(animationA); animationGroup.Add(animationB); var manager = new AnimationManager(); // Should assign both animations to 'property'. var controller = manager.CreateController(animationGroup, property); Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property); Assert.IsFalse(manager.IsAnimated(property)); // When started then animationB (last in the composition chain) should be active. controller.Start(); controller.UpdateAndApply(); Assert.AreEqual(200.0f, property.Value); Assert.IsTrue(manager.IsAnimated(property)); controller.Stop(); controller.UpdateAndApply(); Assert.AreEqual(10.0f, property.Value); Assert.IsFalse(manager.IsAnimated(property)); // Same test for AnimationManager.StartAnimation() controller = manager.StartAnimation(animationGroup, property); controller.UpdateAndApply(); Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(200.0f, property.Value); Assert.IsTrue(manager.IsAnimated(property)); manager.StopAnimation(property); manager.UpdateAndApplyAnimation(property); Assert.AreEqual(10.0f, property.Value); Assert.IsFalse(manager.IsAnimated(property)); }
// The following code contains two helper methods to animate the opacity and offset // of a group of UI controls. The methods basically do the same, they animate the // properties from/to a specific value. However the methods demonstrate two different // approaches. // // The AnimateFrom method uses a more direct approach. It directly starts an // animation for each UI control in list, thereby creating several independently // running animations. // // The AnimateTo method uses a more declarative approach. All animations are // defined and assigned to the target objects by setting the name of the UI control // in the TargetObject property. Then all animations are grouped together into // a single animation. When the resulting animation is started the animation system // creates the required animation instances and assigns the instances to the correct // objects and properties by matching the TargetObject and TargetProperty with the // name of the UI controls and their properties. // // Both methods achieve a similar result. The advantage of the first method is more // direct control. The advantage of the seconds method is that only a single animation // controller is required to control all animations at once. /// <summary> /// Animates the opacity and offset of a group of controls from the specified value to their /// current value. /// </summary> /// <param name="controls">The UI controls to be animated.</param> /// <param name="opacity">The initial opacity.</param> /// <param name="offset">The initial offset.</param> private void AnimateFrom(IList <UIControl> controls, float opacity, Vector2F offset) { TimeSpan duration = TimeSpan.FromSeconds(0.8); // First, let's define the animation that is going to be applied to a control. // Animate the "Opacity" from the specified value to its current value. var opacityAnimation = new SingleFromToByAnimation { TargetProperty = "Opacity", From = opacity, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseOut }, }; // Animate the "RenderTranslation" property from the specified offset to its // its current value, which is usually (0, 0). var offsetAnimation = new Vector2FFromToByAnimation { TargetProperty = "RenderTranslation", From = offset, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseOut }, }; // Group the opacity and offset animation together using a TimelineGroup. var timelineGroup = new TimelineGroup(); timelineGroup.Add(opacityAnimation); timelineGroup.Add(offsetAnimation); // Run the animation on each control using a negative delay to give the first controls // a slight head start. var numberOfControls = controls.Count; for (int i = 0; i < controls.Count; i++) { var clip = new TimelineClip(timelineGroup) { Delay = TimeSpan.FromSeconds(-0.04 * (numberOfControls - i)), FillBehavior = FillBehavior.Stop, // Stop and remove the animation when it is done. }; var animationController = AnimationService.StartAnimation(clip, controls[i]); animationController.UpdateAndApply(); // Enable "auto-recycling" to ensure that the animation resources are recycled once // the animation stops or the target objects are garbage collected. animationController.AutoRecycle(); } }
public CharacterCrossFadeSample(Microsoft.Xna.Framework.Game game) : base(game) { var modelNode = ContentManager.Load <ModelNode>("Marine/PlayerMarine"); _meshNode = modelNode.GetSubtree().OfType <MeshNode>().First().Clone(); SampleHelper.EnablePerPixelLighting(_meshNode); GraphicsScreen.Scene.Children.Add(_meshNode); Dictionary <string, SkeletonKeyFrameAnimation> animations = _meshNode.Mesh.Animations; // Create a looping 'Idle' animation. _idleAnimation = new AnimationClip <SkeletonPose>(animations["Idle"]) { LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Create a looping 'Run' animation. _runAnimation = new AnimationClip <SkeletonPose>(animations["Run"]) { LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Combine the 'Aim' and 'Shoot' animation. The 'Aim' animation should start immediately. // The 'Shoot' animation should start after 0.3 seconds. // (Animations can be combined by creating timeline groups. All timelines/animations // in a timeline group are played simultaneously. AnimationClips can be used to // arrange animations on a timeline. The property Delay, for example, can be used to // set the begin time.) _aimAndShootAnimation = new TimelineGroup(); _aimAndShootAnimation.Add(animations["Aim"]); _aimAndShootAnimation.Add(new AnimationClip <SkeletonPose>(animations["Shoot"]) { Delay = TimeSpan.FromSeconds(0.3) }); // Start 'Idle' animation. We use a Replace transition with a fade-in. _idleAnimationController = AnimationService.StartAnimation( _idleAnimation, (IAnimatableProperty)_meshNode.SkeletonPose, AnimationTransitions.Replace(TimeSpan.FromSeconds(0.5))); _idleAnimationController.AutoRecycle(); }
public CharacterCrossFadeSample(Microsoft.Xna.Framework.Game game) : base(game) { var modelNode = ContentManager.Load<ModelNode>("Marine/PlayerMarine"); _meshNode = modelNode.GetSubtree().OfType<MeshNode>().First().Clone(); SampleHelper.EnablePerPixelLighting(_meshNode); GraphicsScreen.Scene.Children.Add(_meshNode); Dictionary<string, SkeletonKeyFrameAnimation> animations = _meshNode.Mesh.Animations; // Create a looping 'Idle' animation. _idleAnimation = new AnimationClip<SkeletonPose>(animations["Idle"]) { LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Create a looping 'Run' animation. _runAnimation = new AnimationClip<SkeletonPose>(animations["Run"]) { LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Combine the 'Aim' and 'Shoot' animation. The 'Aim' animation should start immediately. // The 'Shoot' animation should start after 0.3 seconds. // (Animations can be combined by creating timeline groups. All timelines/animations // in a timeline group are played simultaneously. AnimationClips can be used to // arrange animations on a timeline. The property Delay, for example, can be used to // set the begin time.) _aimAndShootAnimation = new TimelineGroup(); _aimAndShootAnimation.Add(animations["Aim"]); _aimAndShootAnimation.Add(new AnimationClip<SkeletonPose>(animations["Shoot"]) { Delay = TimeSpan.FromSeconds(0.3) }); // Start 'Idle' animation. We use a Replace transition with a fade-in. _idleAnimationController = AnimationService.StartAnimation( _idleAnimation, (IAnimatableProperty)_meshNode.SkeletonPose, AnimationTransitions.Replace(TimeSpan.FromSeconds(0.5))); _idleAnimationController.AutoRecycle(); }
public void GetRoot() { var animationA = new TimelineGroup(); var animationB = new SingleFromToByAnimation(); var animationC = new SingleFromToByAnimation(); var animationD = new TimelineGroup(); var animationE = new TimelineGroup(); var animationF = new SingleFromToByAnimation(); var animationG = new TimelineGroup(); animationA.Add(animationB); animationA.Add(animationC); animationA.Add(animationD); animationD.Add(animationE); animationD.Add(animationF); animationE.Add(animationG); Assert.That(() => AnimationHelper.GetRoot(null), Throws.TypeOf <ArgumentNullException>()); var animationInstance = animationA.CreateInstance(); Assert.AreEqual(animationInstance, animationInstance.GetRoot()); Assert.AreEqual(animationInstance, animationInstance.Children[2].Children[0].GetRoot()); }
public void AnimateObjects() { var objectA = new AnimatableObject("ObjectA"); var propertyA1 = new AnimatableProperty <float> { Value = 10.0f }; objectA.Properties.Add("Value", propertyA1); var propertyA2 = new AnimatableProperty <float> { Value = 20.0f }; objectA.Properties.Add("Value2", propertyA2); var objectB = new AnimatableObject("ObjectB"); var propertyB = new AnimatableProperty <float> { Value = 30.0f }; objectB.Properties.Add("Value", propertyB); var objectC = new AnimatableObject("ObjectC"); var propertyC = new AnimatableProperty <float> { Value = 40.0f }; objectC.Properties.Add("Value", propertyC); var animationA1 = new SingleFromToByAnimation // Should be assigned to ObjectA { From = 100.0f, To = 200.0f, TargetObject = "ObjectXyz", // Ignored because ObjectA is selected by animationGroup1. TargetProperty = "Value", // Required. }; var animationA2 = new SingleFromToByAnimation // Should be assigned to ObjectA { From = 200.0f, To = 300.0f, TargetObject = "ObjectB", // Ignored because ObjectA is selected by animationGroup1. TargetProperty = "Value", // Required. }; var animationA3 = new Vector3FFromToByAnimation // Ignored because of incompatible type. { From = new Vector3F(300.0f), To = new Vector3F(400.0f), TargetObject = "ObjectA", TargetProperty = "Value", }; var animationA4 = new SingleFromToByAnimation // Ignored because TargetProperty is not set. { From = 400.0f, To = 500.0f, TargetObject = "", TargetProperty = "", }; var animationGroupA = new TimelineGroup { TargetObject = "ObjectA" }; animationGroupA.Add(animationA1); animationGroupA.Add(animationA2); animationGroupA.Add(animationA3); animationGroupA.Add(animationA4); var animationB1 = new SingleFromToByAnimation // Should be assigned to ObjectB { From = 100.0f, To = 200.0f, TargetObject = "ObjectB", TargetProperty = "Value", }; var animationA5 = new SingleFromToByAnimation { From = 600.0f, To = 700.0f, TargetObject = "", TargetProperty = "Value", }; var animationGroupRoot = new TimelineGroup(); animationGroupRoot.Add(animationGroupA); animationGroupRoot.Add(animationB1); animationGroupRoot.Add(animationA5); var manager = new AnimationManager(); // CreateController() var controller = manager.CreateController(animationGroupRoot, new[] { objectA, objectB, objectC }); Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[0]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[1]).Property); Assert.AreEqual(null, ((AnimationInstance <Vector3F>)controller.AnimationInstance.Children[0].Children[2]).Property); Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[3]).Property); Assert.AreEqual(propertyB, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[2]).Property); Assert.AreEqual(10.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(30.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsFalse(manager.IsAnimated(objectA)); Assert.IsFalse(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsFalse(manager.IsAnimated(objectB)); Assert.IsFalse(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); controller.Start(); controller.UpdateAndApply(); Assert.AreEqual(600.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(100.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsTrue(manager.IsAnimated(objectA)); Assert.IsTrue(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsTrue(manager.IsAnimated(objectB)); Assert.IsTrue(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); controller.Stop(); controller.UpdateAndApply(); Assert.AreEqual(10.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(30.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsFalse(manager.IsAnimated(objectA)); Assert.IsFalse(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsFalse(manager.IsAnimated(objectB)); Assert.IsFalse(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); // StartAnimation() controller = manager.StartAnimation(animationGroupRoot, new[] { objectA, objectB, objectC }); controller.UpdateAndApply(); Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[0]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[1]).Property); Assert.AreEqual(null, ((AnimationInstance <Vector3F>)controller.AnimationInstance.Children[0].Children[2]).Property); Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[0].Children[3]).Property); Assert.AreEqual(propertyB, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance <float>)controller.AnimationInstance.Children[2]).Property); Assert.AreEqual(600.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(100.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsTrue(manager.IsAnimated(objectA)); Assert.IsTrue(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsTrue(manager.IsAnimated(objectB)); Assert.IsTrue(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); manager.StopAnimation(new[] { objectA, objectB, objectC }); manager.UpdateAndApplyAnimation(new[] { objectA, objectB, objectC }); Assert.AreEqual(10.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(30.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsFalse(manager.IsAnimated(objectA)); Assert.IsFalse(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsFalse(manager.IsAnimated(objectB)); Assert.IsFalse(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); }
public void AnimateObject() { var obj = new AnimatableObject("TestObject"); var property = new AnimatableProperty <float> { Value = 10.0f }; obj.Properties.Add("Value", property); var property2 = new AnimatableProperty <float> { Value = 20.0f }; obj.Properties.Add("Value2", property2); var animationA = new SingleFromToByAnimation { From = 100.0f, To = 200.0f, TargetObject = "ObjectA", // Should be ignored. TargetProperty = "Value", }; var animationB = new SingleFromToByAnimation { From = 200.0f, To = 300.0f, TargetObject = "ObjectB", // Should be ignored. TargetProperty = "", }; var animationGroup = new TimelineGroup(); animationGroup.Add(animationA); animationGroup.Add(animationB); var manager = new AnimationManager(); // Should assign animationA to 'obj'. var controller = manager.CreateController(animationGroup, obj); Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property); Assert.IsFalse(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); Assert.IsFalse(manager.IsAnimated(obj)); // When started then animationA should be active. controller.Start(); controller.UpdateAndApply(); Assert.AreEqual(100.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsTrue(manager.IsAnimated(obj)); Assert.IsTrue(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); controller.Stop(); controller.UpdateAndApply(); Assert.AreEqual(10.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsFalse(manager.IsAnimated(obj)); Assert.IsFalse(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); // Same test for AnimationManager.StartAnimation() controller = manager.StartAnimation(animationGroup, obj); controller.UpdateAndApply(); Assert.AreEqual(property, ((AnimationInstance <float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(null, ((AnimationInstance <float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(100.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsTrue(manager.IsAnimated(obj)); Assert.IsTrue(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); manager.StopAnimation(obj); manager.UpdateAndApplyAnimation(obj); Assert.AreEqual(10.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsFalse(manager.IsAnimated(obj)); Assert.IsFalse(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); }
/// <summary> /// Animates the opacity and offset of a group of controls from their current value to the /// specified value. /// </summary> /// <param name="controls">The UI controls to be animated.</param> /// <param name="opacity">The opacity.</param> /// <param name="offset">The offset.</param> private AnimationController AnimateTo(IList<UIControl> controls, float opacity, Vector2F offset) { TimeSpan duration = TimeSpan.FromSeconds(0.6f); // First, let's define the animation that is going to be applied to a control. // Animate the "Opacity" from its current value to the specified value. var opacityAnimation = new SingleFromToByAnimation { TargetProperty = "Opacity", To = opacity, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseIn }, }; // Animate the "RenderTranslation" property from its current value, which is // usually (0, 0), to the specified value. var offsetAnimation = new Vector2FFromToByAnimation { TargetProperty = "RenderTranslation", To = offset, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseIn }, }; // Group the opacity and offset animation together using a TimelineGroup. var timelineGroup = new TimelineGroup(); timelineGroup.Add(opacityAnimation); timelineGroup.Add(offsetAnimation); // Now we duplicate this animation by creating new TimelineClips that wrap the TimelineGroup. // A TimelineClip is assigned to a target by setting the TargetObject property. var storyboard = new TimelineGroup(); for (int i = 0; i < controls.Count; i++) { var clip = new TimelineClip(timelineGroup) { TargetObject = controls[i].Name, // Assign the clip to the i-th control. Delay = TimeSpan.FromSeconds(0.04f * i), FillBehavior = FillBehavior.Hold, // Hold the last value of the animation when it }; // because we don't want to opacity and offset to // jump back to their original value. storyboard.Add(clip); } // Now we apply the "storyboard" to the group of UI controls. The animation system // will automatically assign individual animations to the right objects and // properties. #if !XBOX && !WP7 var animationController = AnimationService.StartAnimation(storyboard, controls); #else var animationController = AnimationService.StartAnimation(storyboard, controls.Cast<IAnimatableObject>()); #endif animationController.UpdateAndApply(); // The returned animation controller can be used to start, stop, pause, ... all // animations at once. (Note that we don't set AutoRecycle here, because we will // explicitly stop and recycle the animations in the code above.) return animationController; }
protected override void LoadContent() { _model = Game.Content.Load<Model>("PlayerMarine"); var additionalData = (Dictionary<string, object>)_model.Tag; var skeleton = (Skeleton)additionalData["Skeleton"]; _skeletonPose = SkeletonPose.Create(skeleton); var animations = (Dictionary<string, SkeletonKeyFrameAnimation>)additionalData["Animations"]; // Create a looping 'Idle' animation. _idleAnimation = new AnimationClip<SkeletonPose>(animations["Idle"]) { LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Create a looping 'Run' animation. _runAnimation = new AnimationClip<SkeletonPose>(animations["Run"]) { LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Combine the 'Aim' and 'Shoot' animation. The 'Aim' animation should start immediately. // The 'Shoot' animation should start after 0.3 seconds. // (Animations can be combined by creating timeline groups. All timelines/animations // in a timeline group are played simultaneously. AnimationClips can be used to // arrange animations on a timeline. The property Delay, for example, can be used to // set the begin time.) _aimAndShootAnimation = new TimelineGroup(); _aimAndShootAnimation.Add(animations["Aim"]); _aimAndShootAnimation.Add(new AnimationClip<SkeletonPose>(animations["Shoot"]) { Delay = TimeSpan.FromSeconds(0.3) }); // Start 'Idle' animation. We use a Replace transition with a fade-in. _idleAnimationController = AnimationService.StartAnimation( _idleAnimation, (IAnimatableProperty)_skeletonPose, AnimationTransitions.Replace(TimeSpan.FromSeconds(0.5))); _idleAnimationController.AutoRecycle(); base.LoadContent(); }
public void AnimateObject() { var obj = new AnimatableObject("TestObject"); var property = new AnimatableProperty<float> { Value = 10.0f }; obj.Properties.Add("Value", property); var property2 = new AnimatableProperty<float> { Value = 20.0f }; obj.Properties.Add("Value2", property2); var animationA = new SingleFromToByAnimation { From = 100.0f, To = 200.0f, TargetObject = "ObjectA", // Should be ignored. TargetProperty = "Value", }; var animationB = new SingleFromToByAnimation { From = 200.0f, To = 300.0f, TargetObject = "ObjectB", // Should be ignored. TargetProperty = "", }; var animationGroup = new TimelineGroup(); animationGroup.Add(animationA); animationGroup.Add(animationB); var manager = new AnimationManager(); // Should assign animationA to 'obj'. var controller = manager.CreateController(animationGroup, obj); Assert.AreEqual(property, ((AnimationInstance<float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(null, ((AnimationInstance<float>)controller.AnimationInstance.Children[1]).Property); Assert.IsFalse(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); Assert.IsFalse(manager.IsAnimated(obj)); // When started then animationA should be active. controller.Start(); controller.UpdateAndApply(); Assert.AreEqual(100.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsTrue(manager.IsAnimated(obj)); Assert.IsTrue(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); controller.Stop(); controller.UpdateAndApply(); Assert.AreEqual(10.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsFalse(manager.IsAnimated(obj)); Assert.IsFalse(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); // Same test for AnimationManager.StartAnimation() controller = manager.StartAnimation(animationGroup, obj); controller.UpdateAndApply(); Assert.AreEqual(property, ((AnimationInstance<float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(null, ((AnimationInstance<float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(100.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsTrue(manager.IsAnimated(obj)); Assert.IsTrue(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); manager.StopAnimation(obj); manager.UpdateAndApplyAnimation(obj); Assert.AreEqual(10.0f, property.Value); Assert.AreEqual(20.0f, property2.Value); Assert.IsFalse(manager.IsAnimated(obj)); Assert.IsFalse(manager.IsAnimated(property)); Assert.IsFalse(manager.IsAnimated(property2)); }
public void AnimateProperty() { var property = new AnimatableProperty<float> { Value = 10.0f }; var animationA = new SingleFromToByAnimation { From = 100.0f, To = 200.0f, TargetObject = "ObjectA", // Should be ignored. TargetProperty = "PropertyA", // Should be ignored. }; var animationB = new SingleFromToByAnimation { From = 200.0f, To = 300.0f, TargetObject = "ObjectB", // Should be ignored. TargetProperty = "PropertyB", // Should be ignored. }; var animationGroup = new TimelineGroup(); animationGroup.Add(animationA); animationGroup.Add(animationB); var manager = new AnimationManager(); // Should assign both animations to 'property'. var controller = manager.CreateController(animationGroup, property); Assert.AreEqual(property, ((AnimationInstance<float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(property, ((AnimationInstance<float>)controller.AnimationInstance.Children[1]).Property); Assert.IsFalse(manager.IsAnimated(property)); // When started then animationB (last in the composition chain) should be active. controller.Start(); controller.UpdateAndApply(); Assert.AreEqual(200.0f, property.Value); Assert.IsTrue(manager.IsAnimated(property)); controller.Stop(); controller.UpdateAndApply(); Assert.AreEqual(10.0f, property.Value); Assert.IsFalse(manager.IsAnimated(property)); // Same test for AnimationManager.StartAnimation() controller = manager.StartAnimation(animationGroup, property); controller.UpdateAndApply(); Assert.AreEqual(property, ((AnimationInstance<float>)controller.AnimationInstance.Children[0]).Property); Assert.AreEqual(property, ((AnimationInstance<float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(200.0f, property.Value); Assert.IsTrue(manager.IsAnimated(property)); manager.StopAnimation(property); manager.UpdateAndApplyAnimation(property); Assert.AreEqual(10.0f, property.Value); Assert.IsFalse(manager.IsAnimated(property)); }
public void AnimateObjects() { var objectA = new AnimatableObject("ObjectA"); var propertyA1 = new AnimatableProperty<float> { Value = 10.0f }; objectA.Properties.Add("Value", propertyA1); var propertyA2 = new AnimatableProperty<float> { Value = 20.0f }; objectA.Properties.Add("Value2", propertyA2); var objectB = new AnimatableObject("ObjectB"); var propertyB = new AnimatableProperty<float> { Value = 30.0f }; objectB.Properties.Add("Value", propertyB); var objectC = new AnimatableObject("ObjectC"); var propertyC = new AnimatableProperty<float> { Value = 40.0f }; objectC.Properties.Add("Value", propertyC); var animationA1 = new SingleFromToByAnimation // Should be assigned to ObjectA { From = 100.0f, To = 200.0f, TargetObject = "ObjectXyz", // Ignored because ObjectA is selected by animationGroup1. TargetProperty = "Value", // Required. }; var animationA2 = new SingleFromToByAnimation // Should be assigned to ObjectA { From = 200.0f, To = 300.0f, TargetObject = "ObjectB", // Ignored because ObjectA is selected by animationGroup1. TargetProperty = "Value", // Required. }; var animationA3 = new Vector3FFromToByAnimation // Ignored because of incompatible type. { From = new Vector3F(300.0f), To = new Vector3F(400.0f), TargetObject = "ObjectA", TargetProperty = "Value", }; var animationA4 = new SingleFromToByAnimation // Ignored because TargetProperty is not set. { From = 400.0f, To = 500.0f, TargetObject = "", TargetProperty = "", }; var animationGroupA = new TimelineGroup { TargetObject = "ObjectA" }; animationGroupA.Add(animationA1); animationGroupA.Add(animationA2); animationGroupA.Add(animationA3); animationGroupA.Add(animationA4); var animationB1 = new SingleFromToByAnimation // Should be assigned to ObjectB { From = 100.0f, To = 200.0f, TargetObject = "ObjectB", TargetProperty = "Value", }; var animationA5 = new SingleFromToByAnimation { From = 600.0f, To = 700.0f, TargetObject = "", TargetProperty = "Value", }; var animationGroupRoot = new TimelineGroup(); animationGroupRoot.Add(animationGroupA); animationGroupRoot.Add(animationB1); animationGroupRoot.Add(animationA5); var manager = new AnimationManager(); // CreateController() var controller = manager.CreateController(animationGroupRoot, new[] { objectA, objectB, objectC }); Assert.AreEqual(propertyA1, ((AnimationInstance<float>)controller.AnimationInstance.Children[0].Children[0]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance<float>)controller.AnimationInstance.Children[0].Children[1]).Property); Assert.AreEqual(null, ((AnimationInstance<Vector3F>)controller.AnimationInstance.Children[0].Children[2]).Property); Assert.AreEqual(null, ((AnimationInstance<float>)controller.AnimationInstance.Children[0].Children[3]).Property); Assert.AreEqual(propertyB, ((AnimationInstance<float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance<float>)controller.AnimationInstance.Children[2]).Property); Assert.AreEqual(10.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(30.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsFalse(manager.IsAnimated(objectA)); Assert.IsFalse(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsFalse(manager.IsAnimated(objectB)); Assert.IsFalse(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); controller.Start(); controller.UpdateAndApply(); Assert.AreEqual(600.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(100.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsTrue(manager.IsAnimated(objectA)); Assert.IsTrue(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsTrue(manager.IsAnimated(objectB)); Assert.IsTrue(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); controller.Stop(); controller.UpdateAndApply(); Assert.AreEqual(10.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(30.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsFalse(manager.IsAnimated(objectA)); Assert.IsFalse(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsFalse(manager.IsAnimated(objectB)); Assert.IsFalse(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); // StartAnimation() controller = manager.StartAnimation(animationGroupRoot, new[] { objectA, objectB, objectC }); controller.UpdateAndApply(); Assert.AreEqual(propertyA1, ((AnimationInstance<float>)controller.AnimationInstance.Children[0].Children[0]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance<float>)controller.AnimationInstance.Children[0].Children[1]).Property); Assert.AreEqual(null, ((AnimationInstance<Vector3F>)controller.AnimationInstance.Children[0].Children[2]).Property); Assert.AreEqual(null, ((AnimationInstance<float>)controller.AnimationInstance.Children[0].Children[3]).Property); Assert.AreEqual(propertyB, ((AnimationInstance<float>)controller.AnimationInstance.Children[1]).Property); Assert.AreEqual(propertyA1, ((AnimationInstance<float>)controller.AnimationInstance.Children[2]).Property); Assert.AreEqual(600.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(100.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsTrue(manager.IsAnimated(objectA)); Assert.IsTrue(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsTrue(manager.IsAnimated(objectB)); Assert.IsTrue(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); manager.StopAnimation(new[] { objectA, objectB, objectC }); manager.UpdateAndApplyAnimation(new[] { objectA, objectB, objectC }); Assert.AreEqual(10.0f, propertyA1.Value); Assert.AreEqual(20.0f, propertyA2.Value); Assert.AreEqual(30.0f, propertyB.Value); Assert.AreEqual(40.0f, propertyC.Value); Assert.IsFalse(manager.IsAnimated(objectA)); Assert.IsFalse(manager.IsAnimated(propertyA1)); Assert.IsFalse(manager.IsAnimated(propertyA2)); Assert.IsFalse(manager.IsAnimated(objectB)); Assert.IsFalse(manager.IsAnimated(propertyB)); Assert.IsFalse(manager.IsAnimated(objectC)); Assert.IsFalse(manager.IsAnimated(propertyC)); }
// Create morph target animation in code. private static ITimeline CreateMorphingAnimation() { // The weight of each morph target is controlled by a keyframe animation. var browMad = new SingleKeyFrameAnimation { TargetProperty = "BROW-mad" }; browMad.KeyFrames.Add(new KeyFrame <float>(FrameToTime(15), 0)); browMad.KeyFrames.Add(new KeyFrame <float>(FrameToTime(30), 1)); browMad.KeyFrames.Add(new KeyFrame <float>(FrameToTime(65), 1)); browMad.KeyFrames.Add(new KeyFrame <float>(FrameToTime(80), 0)); var browSurp = new SingleKeyFrameAnimation { TargetProperty = "BROW-surp" }; browSurp.KeyFrames.Add(new KeyFrame <float>(FrameToTime(0), 0)); browSurp.KeyFrames.Add(new KeyFrame <float>(FrameToTime(15), 0.5f)); browSurp.KeyFrames.Add(new KeyFrame <float>(FrameToTime(30), 0)); browSurp.KeyFrames.Add(new KeyFrame <float>(FrameToTime(210), 0)); browSurp.KeyFrames.Add(new KeyFrame <float>(FrameToTime(220), 0.5f)); browSurp.KeyFrames.Add(new KeyFrame <float>(FrameToTime(230), 0.5f)); browSurp.KeyFrames.Add(new KeyFrame <float>(FrameToTime(250), 0)); var cheekIn = new SingleKeyFrameAnimation { TargetProperty = "CHEEK-in" }; cheekIn.KeyFrames.Add(new KeyFrame <float>(FrameToTime(0), 0)); cheekIn.KeyFrames.Add(new KeyFrame <float>(FrameToTime(15), 1)); cheekIn.KeyFrames.Add(new KeyFrame <float>(FrameToTime(30), 0)); var cheekOut = new SingleKeyFrameAnimation { TargetProperty = "CHEEK-out" }; cheekOut.KeyFrames.Add(new KeyFrame <float>(FrameToTime(25), 0)); cheekOut.KeyFrames.Add(new KeyFrame <float>(FrameToTime(35), 1)); cheekOut.KeyFrames.Add(new KeyFrame <float>(FrameToTime(65), 1)); cheekOut.KeyFrames.Add(new KeyFrame <float>(FrameToTime(80), 0)); var eyeClosed = new SingleKeyFrameAnimation { TargetProperty = "EYE-closed" }; eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(20), 0)); eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(25), 1)); eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(55), 0.2f)); eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(65), 0.2f)); eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(80), 0)); eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(230), 0)); eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(235), 1)); eyeClosed.KeyFrames.Add(new KeyFrame <float>(FrameToTime(240), 0)); var mouthE = new SingleKeyFrameAnimation { TargetProperty = "MOUTH-e" }; mouthE.KeyFrames.Add(new KeyFrame <float>(FrameToTime(115), 0)); mouthE.KeyFrames.Add(new KeyFrame <float>(FrameToTime(125), 0.7f)); mouthE.KeyFrames.Add(new KeyFrame <float>(FrameToTime(130), 0.7f)); mouthE.KeyFrames.Add(new KeyFrame <float>(FrameToTime(140), 0.9f)); mouthE.KeyFrames.Add(new KeyFrame <float>(FrameToTime(145), 0.9f)); mouthE.KeyFrames.Add(new KeyFrame <float>(FrameToTime(155), 0.0f)); var mouthU = new SingleKeyFrameAnimation { TargetProperty = "MOUTH-u" }; mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(25), 0)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(35), 0.5f)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(55), 0)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(145), 0)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(155), 0.5f)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(160), 0.5f)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(170), 0.9f)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(175), 0.9f)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(170), 0.9f)); mouthU.KeyFrames.Add(new KeyFrame <float>(FrameToTime(190), 0.0f)); var mouthSmile = new SingleKeyFrameAnimation { TargetProperty = "MOUTH-smile" }; mouthSmile.KeyFrames.Add(new KeyFrame <float>(FrameToTime(210), 0)); mouthSmile.KeyFrames.Add(new KeyFrame <float>(FrameToTime(220), 1)); mouthSmile.KeyFrames.Add(new KeyFrame <float>(FrameToTime(230), 1)); mouthSmile.KeyFrames.Add(new KeyFrame <float>(FrameToTime(250), 0)); // Combine the key frame animations into a single animation. var timelineGroup = new TimelineGroup(); timelineGroup.Add(browMad); timelineGroup.Add(browSurp); timelineGroup.Add(cheekIn); timelineGroup.Add(cheekOut); timelineGroup.Add(eyeClosed); timelineGroup.Add(mouthE); timelineGroup.Add(mouthU); timelineGroup.Add(mouthSmile); // Make an endless loop. return(new TimelineClip(timelineGroup) { Duration = TimeSpan.MaxValue, LoopBehavior = LoopBehavior.Cycle }); }
/// <summary> /// Animates the opacity and offset of a group of controls from their current value to the /// specified value. /// </summary> /// <param name="controls">The UI controls to be animated.</param> /// <param name="opacity">The opacity.</param> /// <param name="offset">The offset.</param> private AnimationController AnimateTo(IList<UIControl> controls, float opacity, Vector2F offset) { TimeSpan duration = TimeSpan.FromSeconds(0.6f); // First, let's define the animation that is going to be applied to a control. // Animate the "Opacity" from its current value to the specified value. var opacityAnimation = new SingleFromToByAnimation { TargetProperty = "Opacity", To = opacity, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseIn }, }; // Animate the "RenderTranslation" property from its current value, which is // usually (0, 0), to the specified value. var offsetAnimation = new Vector2FFromToByAnimation { TargetProperty = "RenderTranslation", To = offset, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseIn }, }; // Group the opacity and offset animation together using a TimelineGroup. var timelineGroup = new TimelineGroup(); timelineGroup.Add(opacityAnimation); timelineGroup.Add(offsetAnimation); // Now we duplicate this animation by creating new TimelineClips that wrap the TimelineGroup. // A TimelineClip is assigned to a target by setting the TargetObject property. var storyboard = new TimelineGroup(); for (int i = 0; i < controls.Count; i++) { var clip = new TimelineClip(timelineGroup) { TargetObject = controls[i].Name, // Assign the clip to the i-th control. Delay = TimeSpan.FromSeconds(0.04f * i), FillBehavior = FillBehavior.Hold, // Hold the last value of the animation when it }; // because we don't want to opacity and offset to // jump back to their original value. storyboard.Add(clip); } // Now we apply the "storyboard" to the group of UI controls. The animation system // will automatically assign individual animations to the right objects and // properties. var animationController = AnimationService.StartAnimation(storyboard, controls); #else var animationController = AnimationService.StartAnimation(storyboard, controls.Cast<IAnimatableObject>()); animationController.UpdateAndApply(); // The returned animation controller can be used to start, stop, pause, ... all // animations at once. (Note that we don't set AutoRecycle here, because we will // explicitly stop and recycle the animations in the code above.) return animationController; }
// Create morph target animation in code. private static ITimeline CreateMorphingAnimation() { // The weight of each morph target is controlled by a keyframe animation. var browMad = new SingleKeyFrameAnimation { TargetProperty = "BROW-mad" }; browMad.KeyFrames.Add(new KeyFrame<float>(FrameToTime(15), 0)); browMad.KeyFrames.Add(new KeyFrame<float>(FrameToTime(30), 1)); browMad.KeyFrames.Add(new KeyFrame<float>(FrameToTime(65), 1)); browMad.KeyFrames.Add(new KeyFrame<float>(FrameToTime(80), 0)); var browSurp = new SingleKeyFrameAnimation { TargetProperty = "BROW-surp" }; browSurp.KeyFrames.Add(new KeyFrame<float>(FrameToTime(0), 0)); browSurp.KeyFrames.Add(new KeyFrame<float>(FrameToTime(15), 0.5f)); browSurp.KeyFrames.Add(new KeyFrame<float>(FrameToTime(30), 0)); browSurp.KeyFrames.Add(new KeyFrame<float>(FrameToTime(210), 0)); browSurp.KeyFrames.Add(new KeyFrame<float>(FrameToTime(220), 0.5f)); browSurp.KeyFrames.Add(new KeyFrame<float>(FrameToTime(230), 0.5f)); browSurp.KeyFrames.Add(new KeyFrame<float>(FrameToTime(250), 0)); var cheekIn = new SingleKeyFrameAnimation { TargetProperty = "CHEEK-in" }; cheekIn.KeyFrames.Add(new KeyFrame<float>(FrameToTime(0), 0)); cheekIn.KeyFrames.Add(new KeyFrame<float>(FrameToTime(15), 1)); cheekIn.KeyFrames.Add(new KeyFrame<float>(FrameToTime(30), 0)); var cheekOut = new SingleKeyFrameAnimation { TargetProperty = "CHEEK-out" }; cheekOut.KeyFrames.Add(new KeyFrame<float>(FrameToTime(25), 0)); cheekOut.KeyFrames.Add(new KeyFrame<float>(FrameToTime(35), 1)); cheekOut.KeyFrames.Add(new KeyFrame<float>(FrameToTime(65), 1)); cheekOut.KeyFrames.Add(new KeyFrame<float>(FrameToTime(80), 0)); var eyeClosed = new SingleKeyFrameAnimation { TargetProperty = "EYE-closed" }; eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(20), 0)); eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(25), 1)); eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(55), 0.2f)); eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(65), 0.2f)); eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(80), 0)); eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(230), 0)); eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(235), 1)); eyeClosed.KeyFrames.Add(new KeyFrame<float>(FrameToTime(240), 0)); var mouthE = new SingleKeyFrameAnimation { TargetProperty = "MOUTH-e" }; mouthE.KeyFrames.Add(new KeyFrame<float>(FrameToTime(115), 0)); mouthE.KeyFrames.Add(new KeyFrame<float>(FrameToTime(125), 0.7f)); mouthE.KeyFrames.Add(new KeyFrame<float>(FrameToTime(130), 0.7f)); mouthE.KeyFrames.Add(new KeyFrame<float>(FrameToTime(140), 0.9f)); mouthE.KeyFrames.Add(new KeyFrame<float>(FrameToTime(145), 0.9f)); mouthE.KeyFrames.Add(new KeyFrame<float>(FrameToTime(155), 0.0f)); var mouthU = new SingleKeyFrameAnimation { TargetProperty = "MOUTH-u" }; mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(25), 0)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(35), 0.5f)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(55), 0)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(145), 0)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(155), 0.5f)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(160), 0.5f)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(170), 0.9f)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(175), 0.9f)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(170), 0.9f)); mouthU.KeyFrames.Add(new KeyFrame<float>(FrameToTime(190), 0.0f)); var mouthSmile = new SingleKeyFrameAnimation { TargetProperty = "MOUTH-smile" }; mouthSmile.KeyFrames.Add(new KeyFrame<float>(FrameToTime(210), 0)); mouthSmile.KeyFrames.Add(new KeyFrame<float>(FrameToTime(220), 1)); mouthSmile.KeyFrames.Add(new KeyFrame<float>(FrameToTime(230), 1)); mouthSmile.KeyFrames.Add(new KeyFrame<float>(FrameToTime(250), 0)); // Combine the key frame animations into a single animation. var timelineGroup = new TimelineGroup(); timelineGroup.Add(browMad); timelineGroup.Add(browSurp); timelineGroup.Add(cheekIn); timelineGroup.Add(cheekOut); timelineGroup.Add(eyeClosed); timelineGroup.Add(mouthE); timelineGroup.Add(mouthU); timelineGroup.Add(mouthSmile); // Make an endless loop. return new TimelineClip(timelineGroup) { Duration = TimeSpan.MaxValue, LoopBehavior = LoopBehavior.Cycle }; }
public AnimatableObjectSample(Microsoft.Xna.Framework.Game game) : base(game) { Rectangle bounds = GraphicsService.GraphicsDevice.Viewport.TitleSafeArea; // ----- Create three named AnimatableSprite instances. _animatableSpriteA = new AnimatableSprite("SpriteA", SpriteBatch, Logo) { Position = new Vector2(bounds.Center.X, bounds.Center.Y / 2.0f), Color = Color.Red, }; _animatableSpriteB = new AnimatableSprite("SpriteB", SpriteBatch, Logo) { Position = new Vector2(bounds.Center.X, bounds.Center.Y), Color = Color.Green, }; _animatableSpriteC = new AnimatableSprite("SpriteC", SpriteBatch, Logo) { Position = new Vector2(bounds.Center.X, 3.0f * bounds.Center.Y / 2.0f), Color = Color.Blue, }; // Create a looping color key-frame animation. ColorKeyFrameAnimation colorAnimation = new ColorKeyFrameAnimation { TargetProperty = "Color", EnableInterpolation = true, }; colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(0.0), Color.Red)); colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(0.5), Color.Orange)); colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(1.0), Color.Yellow)); colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(1.5), Color.Green)); colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(2.0), Color.Blue)); colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(2.5), Color.Indigo)); colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(3.0), Color.Violet)); colorAnimation.KeyFrames.Add(new KeyFrame<Color>(TimeSpan.FromSeconds(3.5), Color.Red)); AnimationClip<Color> loopedColorAnimation = new AnimationClip<Color>(colorAnimation) { TargetObject = "SpriteB", LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Create a oscillating from/to animation for the position. Vector2FromToByAnimation vector2Animation = new Vector2FromToByAnimation { TargetProperty = "Position", From = new Vector2(bounds.Left + 100, _animatableSpriteC.Position.Y), To = new Vector2(bounds.Right - 100, _animatableSpriteC.Position.Y), Duration = TimeSpan.FromSeconds(2), EasingFunction = new HermiteEase { Mode = EasingMode.EaseInOut }, }; AnimationClip<Vector2> oscillatingVector2Animation = new AnimationClip<Vector2>(vector2Animation) { LoopBehavior = LoopBehavior.Oscillate, Duration = TimeSpan.MaxValue, }; // Create a timeline group that contains both animations. TimelineGroup spriteCAnimation = new TimelineGroup { TargetObject = "SpriteC", }; spriteCAnimation.Add(loopedColorAnimation); spriteCAnimation.Add(oscillatingVector2Animation); // Create a timeline group that contains the animations for SpriteB and SpriteC. TimelineGroup allSpriteAnimations = new TimelineGroup(); allSpriteAnimations.Add(loopedColorAnimation); allSpriteAnimations.Add(spriteCAnimation); // There are several ways to apply animations to animatable objects and properties: // // Method 1: Apply to an IAnimatableProperty directly. // We either have direct access to a IAnimatableProperty or we can ask the IAnimatableObject // to give us a named IAnimatableProperty. // For example: // AnimationService.StartAnimation(loopedColorAnimation, _animatableSpriteB.GetAnimatableProperty<Color>("Color")); // AnimationService.StartAnimation(loopedColorAnimation, _animatableSpriteC.GetAnimatableProperty<Color>("Color")); // AnimationService.StartAnimation(oscillatingLeftRightAnimation, _animatableSpriteC.GetAnimatableProperty<Vector2>("Position")); // In this case, the "TargetObject" and the "TargetProperty" values of the timelines/animations do not matter. // // Method 2: Apply to an IAnimatableObject directly. // For example: // AnimationService.StartAnimation(loopedColorAnimation, _animatableSpriteB); // AnimationService.StartAnimation(spriteCAnimation, _animatableSpriteC); // The animation service checks the TargetProperty value of the animations to see which // IAnimatableProperty of the IAnimatableObjects should be animated by the animation. // The "TargetObject" values of the timelines are ignored. // // Method 3: Apply a timeline to a collection of IAnimatableObjects. // For example: var animationController = AnimationService.StartAnimation(allSpriteAnimations, new[] { _animatableSpriteA, _animatableSpriteB, _animatableSpriteC }); // The "TargetObject" and "TargetProperty" values are used to check which objects and // properties must be animated by which animation. // combinedSpriteAnimation is a "tree" of timelines: // // Type TargetObject TargetProperty // -------------------------------------------------------------------------------------------------- // allSpriteAnimations TimelineGroup - - // loopedColorAnimation AnimationClip<Color> "SpriteB" - // colorAnimation ColorKeyFrameAnimation - "Color" // spriteCAnimation TimelineGroup "SpriteC" - // loopedColorAnimation AnimationClip<Color> "SpriteB" - // colorAnimation ColorKeyFrameAnimation - "Color" // oscillatingVector2Animation AnimationClip<Vector2> - - // vector2Animation FromToByAnimation - "Position" // // No animation specifies "SpriteA" as its TargetObject, therefore no animation // are applied to SpriteA. // // The first loopedColorAnimation is applied to "SpriteB". And the contained // colorAnimation is applied to the Color property of SpriteB. // // The spriteCAnimation is applied to "SpriteC". Therefore, all "children" of // spriteCAnimation are also applied to this object! The TargetObject property of // the second loopedColorAnimation is ignored! The second loopedColorAnimation is // applied to SpriteC! animationController.UpdateAndApply(); }
// The following code contains two helper methods to animate the opacity and offset // of a group of UI controls. The methods basically do the same, they animate the // properties from/to a specific value. However the methods demonstrate two different // approaches. // // The AnimateFrom method uses a more direct approach. It directly starts an // animation for each UI control in list, thereby creating several independently // running animations. // // The AnimateTo method uses a more declarative approach. All animations are // defined and assigned to the target objects by setting the name of the UI control // in the TargetObject property. Then all animations are grouped together into // a single animation. When the resulting animation is started the animation system // creates the required animation instances and assigns the instances to the correct // objects and properties by matching the TargetObject and TargetProperty with the // name of the UI controls and their properties. // // Both methods achieve a similar result. The advantage of the first method is more // direct control. The advantage of the seconds method is that only a single animation // controller is required to control all animations at once. /// <summary> /// Animates the opacity and offset of a group of controls from the specified value to their /// current value. /// </summary> /// <param name="controls">The UI controls to be animated.</param> /// <param name="opacity">The initial opacity.</param> /// <param name="offset">The initial offset.</param> private void AnimateFrom(IList<UIControl> controls, float opacity, Vector2F offset) { TimeSpan duration = TimeSpan.FromSeconds(0.8); // First, let's define the animation that is going to be applied to a control. // Animate the "Opacity" from the specified value to its current value. var opacityAnimation = new SingleFromToByAnimation { TargetProperty = "Opacity", From = opacity, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseOut }, }; // Animate the "RenderTranslation" property from the specified offset to its // its current value, which is usually (0, 0). var offsetAnimation = new Vector2FFromToByAnimation { TargetProperty = "RenderTranslation", From = offset, Duration = duration, EasingFunction = new CubicEase { Mode = EasingMode.EaseOut }, }; // Group the opacity and offset animation together using a TimelineGroup. var timelineGroup = new TimelineGroup(); timelineGroup.Add(opacityAnimation); timelineGroup.Add(offsetAnimation); // Run the animation on each control using a negative delay to give the first controls // a slight head start. var numberOfControls = controls.Count; for (int i = 0; i < controls.Count; i++) { var clip = new TimelineClip(timelineGroup) { Delay = TimeSpan.FromSeconds(-0.04 * (numberOfControls - i)), FillBehavior = FillBehavior.Stop, // Stop and remove the animation when it is done. }; var animationController = AnimationService.StartAnimation(clip, controls[i]); animationController.UpdateAndApply(); // Enable "auto-recycling" to ensure that the animation resources are recycled once // the animation stops or the target objects are garbage collected. animationController.AutoRecycle(); } }
public void GetSelfAndAncestors() { var animationA = new TimelineGroup(); var animationB = new SingleFromToByAnimation(); var animationC = new SingleFromToByAnimation(); var animationD = new TimelineGroup(); var animationE = new TimelineGroup(); var animationF = new SingleFromToByAnimation(); var animationG = new TimelineGroup(); animationA.Add(animationB); animationA.Add(animationC); animationA.Add(animationD); animationD.Add(animationE); animationD.Add(animationF); animationE.Add(animationG); Assert.That(() => AnimationHelper.GetSelfAndAncestors(null), Throws.TypeOf<ArgumentNullException>()); var animationInstance = animationA.CreateInstance(); var ancestors = animationInstance.Children[2].Children[0].GetSelfAndAncestors().ToArray(); Assert.AreEqual(3, ancestors.Length); Assert.AreEqual(animationInstance.Children[2].Children[0], ancestors[0]); Assert.AreEqual(animationInstance.Children[2], ancestors[1]); Assert.AreEqual(animationInstance, ancestors[2]); }
public void GetSubtreeDepthFirst() { var animationA = new TimelineGroup(); var animationB = new SingleFromToByAnimation(); var animationC = new SingleFromToByAnimation(); var animationD = new TimelineGroup(); var animationE = new TimelineGroup(); var animationF = new SingleFromToByAnimation(); var animationG = new TimelineGroup(); animationA.Add(animationB); animationA.Add(animationC); animationA.Add(animationD); animationD.Add(animationE); animationD.Add(animationF); animationE.Add(animationG); Assert.That(() => AnimationHelper.GetSubtree(null), Throws.TypeOf<ArgumentNullException>()); var animationInstance = animationA.CreateInstance(); var descendants = animationInstance.Children[2].GetSubtree().ToArray(); Assert.AreEqual(4, descendants.Length); Assert.AreEqual(animationInstance.Children[2], descendants[0]); Assert.AreEqual(animationInstance.Children[2].Children[0], descendants[1]); Assert.AreEqual(animationInstance.Children[2].Children[0].Children[0], descendants[2]); Assert.AreEqual(animationInstance.Children[2].Children[1], descendants[3]); }
public AnimatableObjectSample(Microsoft.Xna.Framework.Game game) : base(game) { Rectangle bounds = GraphicsService.GraphicsDevice.Viewport.TitleSafeArea; // ----- Create three named AnimatableSprite instances. _animatableSpriteA = new AnimatableSprite("SpriteA", SpriteBatch, Logo) { Position = new Vector2(bounds.Center.X, bounds.Center.Y / 2.0f), Color = Color.Red, }; _animatableSpriteB = new AnimatableSprite("SpriteB", SpriteBatch, Logo) { Position = new Vector2(bounds.Center.X, bounds.Center.Y), Color = Color.Green, }; _animatableSpriteC = new AnimatableSprite("SpriteC", SpriteBatch, Logo) { Position = new Vector2(bounds.Center.X, 3.0f * bounds.Center.Y / 2.0f), Color = Color.Blue, }; // Create a looping color key-frame animation. ColorKeyFrameAnimation colorAnimation = new ColorKeyFrameAnimation { TargetProperty = "Color", EnableInterpolation = true, }; colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(0.0), Color.Red)); colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(0.5), Color.Orange)); colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(1.0), Color.Yellow)); colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(1.5), Color.Green)); colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(2.0), Color.Blue)); colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(2.5), Color.Indigo)); colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(3.0), Color.Violet)); colorAnimation.KeyFrames.Add(new KeyFrame <Color>(TimeSpan.FromSeconds(3.5), Color.Red)); AnimationClip <Color> loopedColorAnimation = new AnimationClip <Color>(colorAnimation) { TargetObject = "SpriteB", LoopBehavior = LoopBehavior.Cycle, Duration = TimeSpan.MaxValue, }; // Create a oscillating from/to animation for the position. Vector2FromToByAnimation vector2Animation = new Vector2FromToByAnimation { TargetProperty = "Position", From = new Vector2(bounds.Left + 100, _animatableSpriteC.Position.Y), To = new Vector2(bounds.Right - 100, _animatableSpriteC.Position.Y), Duration = TimeSpan.FromSeconds(2), EasingFunction = new HermiteEase { Mode = EasingMode.EaseInOut }, }; AnimationClip <Vector2> oscillatingVector2Animation = new AnimationClip <Vector2>(vector2Animation) { LoopBehavior = LoopBehavior.Oscillate, Duration = TimeSpan.MaxValue, }; // Create a timeline group that contains both animations. TimelineGroup spriteCAnimation = new TimelineGroup { TargetObject = "SpriteC", }; spriteCAnimation.Add(loopedColorAnimation); spriteCAnimation.Add(oscillatingVector2Animation); // Create a timeline group that contains the animations for SpriteB and SpriteC. TimelineGroup allSpriteAnimations = new TimelineGroup(); allSpriteAnimations.Add(loopedColorAnimation); allSpriteAnimations.Add(spriteCAnimation); // There are several ways to apply animations to animatable objects and properties: // // Method 1: Apply to an IAnimatableProperty directly. // We either have direct access to a IAnimatableProperty or we can ask the IAnimatableObject // to give us a named IAnimatableProperty. // For example: // AnimationService.StartAnimation(loopedColorAnimation, _animatableSpriteB.GetAnimatableProperty<Color>("Color")); // AnimationService.StartAnimation(loopedColorAnimation, _animatableSpriteC.GetAnimatableProperty<Color>("Color")); // AnimationService.StartAnimation(oscillatingLeftRightAnimation, _animatableSpriteC.GetAnimatableProperty<Vector2>("Position")); // In this case, the "TargetObject" and the "TargetProperty" values of the timelines/animations do not matter. // // Method 2: Apply to an IAnimatableObject directly. // For example: // AnimationService.StartAnimation(loopedColorAnimation, _animatableSpriteB); // AnimationService.StartAnimation(spriteCAnimation, _animatableSpriteC); // The animation service checks the TargetProperty value of the animations to see which // IAnimatabelProperty of the IAnimtableObjects should be animated by the animation. // The "TargetObject" values of the timelines are ignored. // // Method 3: Apply a timeline to a collection of IAnimatableObjects. // For example: var animationController = AnimationService.StartAnimation(allSpriteAnimations, new[] { _animatableSpriteA, _animatableSpriteB, _animatableSpriteC }); // The "TargetObject" and "TargetProperty" values are used to check which objects and // properties must be animated by which animation. // combinedSpriteAnimation is a "tree" of timelines: // // Type TargetObject TargetProperty // -------------------------------------------------------------------------------------------------- // allSpriteAnimations TimelineGroup - - // loopedColorAnimation AnimationClip<Color> "SpriteB" - // colorAnimation ColorKeyFrameAnimation - "Color" // spriteCAnimation TimelineGroup "SpriteC" - // loopedColorAnimation AnimationClip<Color> "SpriteB" - // colorAnimation ColorKeyFrameAnimation - "Color" // oscillatingVector2Animation AnimationClip<Vector2> - - // vector2Animation FromToByAnimation - "Position" // // No animation specifies "SpriteA" as its TargetObject, therefore no animation // are applied to SpriteA. // // The first loopedColorAnimation is applied to "SpriteB". And the contained // colorAnimation is applied to the Color property of SpriteB. // // The spriteCAnimation is applied to "SpriteC". Therefore, all "children" of // spriteCAnimation are also applied to this object! The TargetObject property of // the second loopedColorAnimation is ignored! The second loopedColorAnimation is // applied to SpriteC! animationController.UpdateAndApply(); }
public void GetRoot() { var animationA = new TimelineGroup(); var animationB = new SingleFromToByAnimation(); var animationC = new SingleFromToByAnimation(); var animationD = new TimelineGroup(); var animationE = new TimelineGroup(); var animationF = new SingleFromToByAnimation(); var animationG = new TimelineGroup(); animationA.Add(animationB); animationA.Add(animationC); animationA.Add(animationD); animationD.Add(animationE); animationD.Add(animationF); animationE.Add(animationG); Assert.That(() => AnimationHelper.GetRoot(null), Throws.TypeOf<ArgumentNullException>()); var animationInstance = animationA.CreateInstance(); Assert.AreEqual(animationInstance, animationInstance.GetRoot()); Assert.AreEqual(animationInstance, animationInstance.Children[2].Children[0].GetRoot()); }