First of all I place here my situation sreenshot:
So I need to move these cars(cars are Images) where the arrow goes.
For driving straight I use this function:
private static void Eiti(Image target, double naujasX, double naujasY, double greitis, char kelias)
{
var top = Canvas.GetTop(target);
var left = Canvas.GetLeft(target);
top = Double.IsNaN(top) ? 0 : top;
left = Double.IsNaN(left) ? 0 : left;
naujasY = Paveikslas.canvasY(naujasY, kelias);
var storyboard = new Storyboard();
greitis = 100 / greitis; // apverciamas greitis, kad butu logiska
storyboard.Completed += Storyboard_Completed;
if (naujasY != -1) // isejimo simbolis (escape)
{
DoubleAnimation anim1 = new DoubleAnimation(top, naujasY, TimeSpan.FromSeconds(greitis));
Storyboard.SetTarget(anim1, target);
Storyboard.SetTargetProperty(anim1, new PropertyPath(Canvas.TopProperty));
storyboard.Children.Add(anim1);
DoubleAnimation anim2 = new DoubleAnimation(left, naujasX, TimeSpan.FromSeconds(greitis));
Storyboard.SetTarget(anim2, target);
Storyboard.SetTargetProperty(anim2, new PropertyPath(Canvas.LeftProperty));
storyboard.Children.Add(anim2);
}
storyboard.Begin();
}
and it's working good for me!
But now I have problem with rotating and driving car at the same time (for example like in my screenshot driving through green square).
Is there any function or other ways to solve this ?
Related
I want to make a turning animation to show the vehicle turning in Junction. Also, I want to do it with c#(code behind) because my vehicles are dynamically added.
Solution tried:
I tried to use TranslateTransform and RotateTransform but I could only create sharp turn animation. I want to create a smooth turn animation.
Current Output:
Sample Code
//Code to add car
private void Click1_Click(object sender, RoutedEventArgs e)
{
var myCar = new Image()
{
Source = new BitmapImage(new Uri("ms-appx:///Assets/RedCar.png")),
Width = 140,
Height = 65,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
RenderTransform = new TranslateTransform()
{
X = 0,
Y = actualHeight / 2 - 145
}
};
VehicleGrid.Children.Add(myCar);
}
//Code to create forward animation
private void MoreForward(UIElement element)
{
Storyboard storyboard = new Storyboard();
DoubleAnimation doubleAnimation = new DoubleAnimation()
{
Duration = new Duration(new TimeSpan(0, 0, 3)),
To = LeftRoad.ActualWidth - 140
};
Storyboard.SetTarget(doubleAnimation, element.RenderTransform);
Storyboard.SetTargetProperty(doubleAnimation, "X");
storyboard.Children.Add(doubleAnimation);
storyboard.Begin();
}
Full Code
You can see my full code in Github: TrafficManagementSystem
There's an interesting post that details how to create a layout path where your object can move along with.
But in a simple scenario like yours, you basically just want a curved motion like what's described in this Android's Material Design Guideline. Yeah... Android's, as we don't have native curved motion API support just yet (the Windows UI team did mention that they are looking to support this in the future though).
However, it's not too difficult to create your own curved motion. In fact, many have used this trick on the web already - apply the opposite speed of an easing on each axis. Also in your case, you will want the curve of the easing to be sharp in order to produce a nice turning animation.
For example, for a car coming from left to right and then doing a left turn, you can apply a QuinticEase with EaseIn mode on x-axis and one with EaseOut on y-axis. To turn the vehicle, just apply another rotation animation to it but with a short delay and lesser duration to ensure the turning only happens at the crossroad.
By slightly modifying my answer in this question, you can achieve what I described above with the following code
The AnimateTransform helper method
public static void AnimateTransform(this UIElement target, string propertyToAnimate, Orientation? orientation, double? from, double to, int duration = 3000, int startTime = 0, EasingFunctionBase easing = null)
{
if (easing == null)
{
easing = new ExponentialEase();
}
var transform = target.RenderTransform as CompositeTransform;
if (transform == null)
{
transform = new CompositeTransform();
target.RenderTransform = transform;
}
target.RenderTransformOrigin = new Point(0.5, 0.5);
var db = new DoubleAnimation
{
To = to,
From = from,
EasingFunction = easing,
Duration = TimeSpan.FromMilliseconds(duration)
};
Storyboard.SetTarget(db, target);
var axis = string.Empty;
if (orientation.HasValue)
{
axis = orientation.Value == Orientation.Horizontal ? "X" : "Y";
}
Storyboard.SetTargetProperty(db, $"(UIElement.RenderTransform).(CompositeTransform.{propertyToAnimate}{axis})");
var sb = new Storyboard
{
BeginTime = TimeSpan.FromMilliseconds(startTime)
};
sb.Children.Add(db);
sb.Begin();
}
Create the turning animations
MyCar.AnimateTransform("Translate", Orientation.Horizontal, null, -600, duration: 3000, easing: new QuinticEase
{
EasingMode = EasingMode.EaseIn
});
MyCar.AnimateTransform("Translate", Orientation.Vertical, null, -600, duration: 3000, easing: new QuinticEase
{
EasingMode = EasingMode.EaseOut
});
MyCar.AnimateTransform("Rotation", null, null, -90, duration: 2000, startTime: 500);
Result in motion
Alternatively, you can replace the traditional Storyboard animation with the new Composition API, which provides fully customizable easing functions (see below), but the idea is the same.
public static CubicBezierEasingFunction EaseOutExpo(this Compositor compositor) =>
compositor.CreateCubicBezierEasingFunction(new Vector2(0.14f, 1f), new Vector2(0.34f, 1f));
Hope this helps!
I've got a question regarding animations for WPF in C# code!
I have an handler function for the MouseWheel event. It just checks if you 'zoomed in' or 'zoomed out'. Just have a look at the code, the important line here is the 4th line, where the RenderTransform is set.
private void ZoomPanCanvas_MouseWheel(object sender, MouseWheelEventArgs e) {
var factor = (e.Delta > 0) ? (1.1) : (1 / 1.1);
currrentScale = factor * currrentScale;
mNetworkUI.RenderTransform = new ScaleTransform(currrentScale, currrentScale);
var pos = e.GetPosition(mNetworkUI);
mNetworkUI.Width = ZoomPanCanvas.ActualWidth / currrentScale;
mNetworkUI.Height = ZoomPanCanvas.ActualHeight /currrentScale;
var dummyTransform = new ScaleTransform(factor, factor, pos.X, pos.Y);
var offSet = new Point(dummyTransform.Value.OffsetX, dummyTransform.Value.OffsetY);
mNetworkUI.ViewModel.Network.SetTransformOffset(offSet);
}
I kept the rest of the function in the code for completeness.
What I want to do, is to animate this change of the RenderTransform!
I already tried to use a Storyboard (with UIElement.RenderTransformProperty set). Best result was a not-animated change of the RenderTransform (but it was not the same result as this 4th line from the code achieves).
Maybe you can help me, I already tried a few suggested methods from the other questions here.
Edit:
Here's the non-working attempt and at first the chenged code from above:
private void ZoomPanCanvas_MouseWheel(object sender, MouseWheelEventArgs e) {
var factor = (e.Delta > 0) ? (1.1) : (1 / 1.1);
currrentScale = factor * currrentScale;
///mNetworkUI.RenderTransform = new ScaleTransform(currrentScale, currrentScale);
Helper.Animations.RenderTransformAnimation(mNetworkUI, new ScaleTransform(currrentScale, currrentScale));
var pos = e.GetPosition(mNetworkUI);
mNetworkUI.Width = ZoomPanCanvas.ActualWidth / currrentScale;
mNetworkUI.Height = ZoomPanCanvas.ActualHeight /currrentScale;
var dummyTransform = new ScaleTransform(factor, factor, pos.X, pos.Y);
var offSet = new Point(dummyTransform.Value.OffsetX, dummyTransform.Value.OffsetY);
mNetworkUI.ViewModel.Network.SetTransformOffset(offSet);
}
And it follows the static helper function:
public static void RenderTransformAnimation(FrameworkElement element, Transform newTransform) {
MatrixAnimationUsingKeyFrames anim = new MatrixAnimationUsingKeyFrames();
var key1 = new DiscreteMatrixKeyFrame(element.RenderTransform.Value, KeyTime.FromPercent(0));
var key2 = new DiscreteMatrixKeyFrame(newTransform.Value, KeyTime.FromPercent(1));
Storyboard.SetTarget(anim, element.RenderTransform);
Storyboard.SetTargetProperty(anim, new PropertyPath(UIElement.RenderTransformProperty));
Storyboard sb = new Storyboard();
sb.Children.Add(anim);
sb.Duration = AnimationDuration;
sb.Begin();
}
It always throws an exception on the sb.Begin() call, telling me, something was not ok with my 'PropertyPath'. I don't know how to do it :(.
I mean, there is no way to directly create a "TransformAnimation", right? Only MatrixAnimations are available...
I've provided a simple animation of a ScaleTransform below. In the interests of providing a 'minimal' example, I only adjust the scale; I don't do any of the offset calculations that you're doing based on mouse position. You should be able to figure out where to go from here:
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var factor = e.Delta > 0d ? 1.1d : 0.9d;
var t = mNetworkUI.RenderTransform as ScaleTransform;
if (t == null)
{
mNetworkUI.RenderTransform = t = new ScaleTransform(1d, 1d)
{
CenterX = 0.5d,
CenterY = 0.5d
};
}
var oldScale = (double)t.GetAnimationBaseValue(ScaleTransform.ScaleXProperty);
var newScale = oldScale * factor;
//
// Make sure `GetAnimationBaseValue()` reflects the `To` value next time
// (needed to calculate `oldScale`, and for the animation to infer `From`).
//
t.ScaleX = newScale;
t.ScaleY = newScale;
var animation = new DoubleAnimation
{
To = newScale,
Duration = TimeSpan.FromSeconds(0.5d),
DecelerationRatio = 0.5d,
FillBehavior = FillBehavior.Stop
};
//
// Use `HandoffBehavior.Compose` to transition more smoothly if an animation
// is already in progress.
//
t.BeginAnimation(
ScaleTransform.ScaleXProperty,
animation,
HandoffBehavior.Compose);
t.BeginAnimation(
ScaleTransform.ScaleYProperty,
animation,
HandoffBehavior.Compose);
}
I'm newbie to C# and I'm trying to do a simple application which have elements that move a lot on the screen. After some research I found a code that moved a button. The problem is the button returns to it's original state (although invisible). When I click the button it moves outside the screen (just as I wanted), but when I click it back (it should do the reverse animation) but instead, it just magically appears on the screen again.
I also tried to make it change position after the animation ended, but that didn't work either. Here's my code:
private void ButtonOnClick(object sender, RoutedEventArgs e)
{
if (nextSlideMoving)
return;
nextSlideMoving = true;
KinectTileButton target = (KinectTileButton)sender;
Vector offset = VisualTreeHelper.GetOffset(target);
if (nextSlideHidden)
moveAnimation(target, 0, offset.Y);
else
moveAnimation(target, -target.ActualWidth, offset.Y);
}
private void moveAnimation(KinectTileButton target, double newX, double newY)
{
Vector offset = VisualTreeHelper.GetOffset(target);
var top = offset.Y;
var left = offset.X;
TranslateTransform trans = new TranslateTransform();
target.RenderTransform = trans;
DoubleAnimation anim1 = new DoubleAnimation(0, newY - top, TimeSpan.FromSeconds(0.5));
trans.BeginAnimation(TranslateTransform.YProperty, anim1);
DoubleAnimation anim2 = new DoubleAnimation(0, newX - left, TimeSpan.FromSeconds(0.5));
anim2.Completed += new EventHandler(finishedAnimation);
trans.BeginAnimation(TranslateTransform.XProperty, anim2);
}
public void finishedAnimation(Object sender, EventArgs e)
{
nextSlideMoving = false;
nextSlideHidden = !nextSlideHidden;
Console.WriteLine(nextSlideHidden);
if (nextSlideHidden)
nextSlide.Margin = new Thickness(-(SystemParameters.VirtualScreenWidth * 0.2), SystemParameters.VirtualScreenHeight * 0.2, SystemParameters.VirtualScreenWidth * 0.8, SystemParameters.VirtualScreenHeight * 0.2); // (LEFT, TOP, RIGHT, BOTTOM)
else
nextSlide.Margin = new Thickness(0, SystemParameters.VirtualScreenHeight * 0.2, SystemParameters.VirtualScreenWidth * 0.8, SystemParameters.VirtualScreenHeight * 0.2); // (LEFT, TOP, RIGHT, BOTTOM)
}
You seem to be asking for FillBehavior. Take a look at following link:
http://msdn.microsoft.com/en-us/library/system.windows.media.animation.timeline.fillbehavior(v=vs.110).aspx
Also take a look at AutoReverse property:
http://msdn.microsoft.com/en-us/library/system.windows.media.animation.timeline.autoreverse(v=vs.110).aspx
If those properties doesn't help you any futher I would gladly take a look at your example just please upload it online.
I have the following method which works. I'd like to put it in a utility method that returns a Storyboard. Every attempt I have made at converting this to a Storyboard has failed, and I've spent a lot of time researching. I'm ready to give up unless someone comes to my rescue.
Here's the code I want to convert:
public override void Begin(FrameworkElement element, int duration)
{
var transform = new ScaleTransform();
element.LayoutTransform = transform;
var animation = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromMilliseconds(duration),
FillBehavior = FillBehavior.Stop,
EasingFunction = new QuinticEase { EasingMode = EasingMode.EaseIn }
};
transform.BeginAnimation(ScaleTransform.ScaleXProperty, animation);
transform.BeginAnimation(ScaleTransform.ScaleYProperty, animation);
}
So, instead of the two BeginAnimation() calls, I want to return a Storyboard so all I have to do is call storyboard.Begin(). I know this shouldn't be that hard to do, but I'm just not getting it.
Thanks.
EDIT: In response to H.B's suggestions, I tried the following code, which still does not work:
private static Storyboard CreateAnimationStoryboard(FrameworkElement element, int duration)
{
var sb = new Storyboard();
var scale = new ScaleTransform(1, 1);
element.RenderTransform = scale;
element.RegisterName("scale", scale);
var animation = new DoubleAnimation
{
From = 1,
To = 0,
Duration = TimeSpan.FromMilliseconds(duration),
FillBehavior = FillBehavior.Stop,
EasingFunction = new QuinticEase { EasingMode = EasingMode.EaseIn }
};
sb.Children.Add(animation);
Storyboard.SetTarget(animation, scale);
Storyboard.SetTargetProperty(animation, new PropertyPath(ScaleTransform.ScaleXProperty));
return sb;
}
I know I only animated the X axis - just want to get something to work first.
You'll need two animations and then set the attached Storyboard properties to animated the right property on the right object using SetTargetProperty and SetTargetName.
Due to how storyboards work you also need to set a namescope (NameScope.SetNameScope), register the name of the transform, and call StoryBoard.Begin with the containing element overload.
e.g.
NameScope.SetNameScope(element, new NameScope());
var transform = new ScaleTransform();
var transformName = "transform";
element.RegisterName(transformName, transform);
element.RenderTransform = transform;
var xAnimation = new DoubleAnimation(2, TimeSpan.FromSeconds(1));
var yAnimation = xAnimation.Clone();
var storyboard = new Storyboard()
{
Children = { xAnimation, yAnimation }
};
Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(ScaleTransform.ScaleX)"));
Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(ScaleTransform.ScaleY)"));
Storyboard.SetTargetName(xAnimation, transformName);
Storyboard.SetTargetName(yAnimation, transformName);
storyboard.Begin(element);
I suggest using Expression Blend and start recording from there, it should create your storyboards in XAML. Rather than hard coding it with C# and trying to translate it 1 by 1 to storyboard thus it can be a prone error.
I'm designing a game like this
class Anima
{
Storyboard story;
Random rand;
Canvas canvas;
Ellipse target;
public Anima() {
rand = new Random();
canvas = new Canvas();
target = new Ellipse();
target.Fill = Brushes.Red;
target.Width = 50;
target.Height = 50;
Canvas.SetLeft(target, rand.NextDouble() * 300);
Canvas.SetTop(target, rand.NextDouble() * 300);
canvas.Children.Add(target);
story = new Storyboard();
story.BeginTime = TimeSpan.FromMilliseconds(rand.Next(500, 5000));
DoubleAnimation a = new DoubleAnimation();
a.To = rand.NextDouble() * 300;
a.Duration = new Duration(TimeSpan.FromMilliseconds(50));
Storyboard.SetTarget(a, target);
Storyboard.SetTargetProperty(a, new PropertyPath(Canvas.LeftProperty));
story.Children.Add(a);
DoubleAnimation b = new DoubleAnimation();
b.To = rand.NextDouble() * 300;
b.Duration = new Duration(TimeSpan.FromMilliseconds(50));
Storyboard.SetTarget(b, target);
Storyboard.SetTargetProperty(b, new PropertyPath(Canvas.TopProperty));
story.Children.Add(b);
story.Completed += new EventHandler(story_Completed);
Window win = new Window();
win.Loaded += delegate(object sender, RoutedEventArgs e) {
story.Begin();
};
win.Content = canvas;
win.Show();
}
void story_Completed(object sender, EventArgs e) {
int next = rand.Next(500, 5000);
double left = rand.NextDouble() * 300;
double top = rand.NextDouble() * 300;
Console.WriteLine("position: ({0:G6}, {1:G6})", Canvas.GetLeft(target), Canvas.GetTop(target));
Console.WriteLine("state : wait for " + next + " ms");
Console.WriteLine("next : ({0:G6}, {1:G6})", left, top);
Console.WriteLine();
(story.Children[0] as DoubleAnimation).To = left;
(story.Children[1] as DoubleAnimation).To = top;
story.BeginTime = TimeSpan.FromMilliseconds(next);
story.Begin();
}
}
Everything is great, but I found that the ellipse didn't get the right position and make an error about 2% to 50%. It seems like the Storyboard.Completed event triggered before the end of animation.
What's wrong with it?
I would suggest that you use CompositionTarget.Rendering event or DispatcherTimer for controlling complex animations, especially if you are developing a game. The built-in animations are not precise enough and cannot be controlled easily.
Here are some links that will get you started on CompositionTarget
How to: Render on a Per Frame Interval Using CompositionTarget
Using the CompositionTarget
Fun with Animation Part 1
use Dispatcher.BeginInvoke(...), you can get the right value