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
Related
What I'm trying to do is make my control "pulse" (as in enlarge a bit, then shrink a bit, then back to its normal size). The following code does exactly what I want, but it just seems like there should be a simpler way. It has to be done in code behind because this is an abstract class that most of our controls derive from. I don't have much experience with animation controls and story boards.
So specifically my question is, is there a simpler way to get the desired results than the following method (while still being done in the code-behind)?
public void Pulse()
{
var storyboard = new Storyboard
{
FillBehavior = FillBehavior.Stop,
RepeatBehavior = new RepeatBehavior(2)
};
double timeIncrement = .15;
double changePercent = 20;
var firstTime = TimeSpan.FromSeconds(timeIncrement);
var secondTime = TimeSpan.FromSeconds(timeIncrement * 3);
var thirdTime = TimeSpan.FromSeconds(timeIncrement * 4);
var scale = new ScaleTransform(1.0, 1.0);
RenderTransformOrigin = new Point(.5, .5);
RenderTransform = scale;
//enlarge
{
DoubleAnimation growX = new DoubleAnimation
{
Duration = firstTime,
To = 1 + changePercent
};
storyboard.Children.Add(growX);
Storyboard.SetTargetProperty(growX, new PropertyPath("RenderTransform.ScaleX"));
DoubleAnimation growY = new DoubleAnimation
{
Duration = firstTime,
To = 1 + changePercent
};
storyboard.Children.Add(growY);
Storyboard.SetTargetProperty(growY, new PropertyPath("RenderTransform.ScaleY"));
}
//shrink
{
DoubleAnimation shrinkX = new DoubleAnimation
{
Duration = secondTime,
To = 1 - changePercent
};
storyboard.Children.Add(shrinkX);
Storyboard.SetTargetProperty(shrinkX, new PropertyPath("RenderTransform.ScaleX"));
DoubleAnimation shrinkY = new DoubleAnimation
{
Duration = secondTime,
To = 1 - changePercent
};
storyboard.Children.Add(shrinkY);
Storyboard.SetTargetProperty(shrinkY, new PropertyPath("RenderTransform.ScaleY"));
}
//back to normal
{
DoubleAnimation normX = new DoubleAnimation
{
Duration = thirdTime,
To = 1
};
storyboard.Children.Add(normX);
Storyboard.SetTargetProperty(normX, new PropertyPath("RenderTransform.ScaleX"));
DoubleAnimation normY = new DoubleAnimation
{
Duration = thirdTime,
To = 1
};
storyboard.Children.Add(normY);
Storyboard.SetTargetProperty(normY, new PropertyPath("RenderTransform.ScaleY"));
}
BeginStoryboard(storyboard, HandoffBehavior.SnapshotAndReplace, false);
}
You could perhaps simplify things a little by moving your grow/shrink logic into its own method. This reduces duplication and provides a potentially more reusable code kibble.
public void Pulse()
{
var storyboard = new Storyboard
{
FillBehavior = FillBehavior.Stop,
RepeatBehavior = new RepeatBehavior(2)
};
double timeIncrement = .15;
double growPercent = 20;
double shrinkPercent = -20;
var firstTime = TimeSpan.FromSeconds(timeIncrement);
var secondTime = TimeSpan.FromSeconds(timeIncrement * 3);
var thirdTime = TimeSpan.FromSeconds(timeIncrement * 4);
var scale = new ScaleTransform(1.0, 1.0);
RenderTransformOrigin = new Point(.5, .5);
RenderTransform = scale;
storyboard = AddSizeChange(firstTime, growPercent, storyboard);
storyboard = AddSizeChange(secondTime, shrinkPercent, storyboard);
storyboard = AddSizeChange(thirdTime, growPercent, storyboard);
BeginStoryboard(storyboard, HandoffBehavior.SnapshotAndReplace, false);
}
public Storyboard AddSizeChange(TimeSpan animTime, double changePercent, Storyboard storyboard)
{
DoubleAnimation growX = new DoubleAnimation
{
Duration = animTime,
To = 1 + changePercent
};
storyboard.Children.Add(growX);
Storyboard.SetTargetProperty(growX, new PropertyPath("RenderTransform.ScaleX"));
DoubleAnimation growY = new DoubleAnimation
{
Duration = animTime,
To = 1 + changePercent
};
storyboard.Children.Add(growY);
Storyboard.SetTargetProperty(growY, new PropertyPath("RenderTransform.ScaleY"));
return storyboard;
}
That's a fine way of doing it, WPF does not provide methods or events to automatically pulse. Yours is simple and as you've created it, you know how to modify it as you wish.
I am trying to make one-hand machine game. To animate falling down Images I am using storyboard. My question is if someone know how to make multiple images. In this code I have just on image falling down. Somone know how to make e.g 100 images animation in the storyboard?
private Storyboard CreateStoryBoard()
{
Storyboard sb = new Storyboard();
DoubleAnimation firstAnimation = new DoubleAnimation();
firstAnimation.SpeedRatio = 8;
firstAnimation.From = 0;
firstAnimation.To = 600;
firstAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
Storyboard.SetTarget(firstAnimation, Okejka);
Storyboard.SetTargetProperty(firstAnimation, new PropertyPath("(Canvas.Top)"));
sb.Children.Add(firstAnimation);
return sb;
}
private void SpinButton_Click(object sender, RoutedEventArgs e)
{
Storyboard sb = CreateStoryBoard();
sb.Begin();
}
For each object you need create DoubleAnimation, each DoubleAnimation add to one Storyboard and then play it.
This is one method of my AnimationHelper, I modified it for this case.
public static void Animate(List<DependencyObject> objects, EventHandler onComplete = null)
{
Storyboard sb = new Storyboard();
foreach (DependencyObject obj in objects)
{
DoubleAnimation da = new DoubleAnimation();
da.From = FromValue; // Set you From value
da.To = ToValue; // Set your To value
da.Duration = new Duration(TimeSpan.FromSeconds(2)); // Set your Duration
// a.EasingFunction = anim.Func; Easing function
// da.BeginTime = anim.BeginTime; Begin time for each DA
Storyboard.SetTarget(da, obj);
Storyboard.SetTargetProperty(da, new PropertyPath(/* this your Property path */));
sb.Children.Add(da);
}
if (onComplete != null)
sb.Completed += onComplete;
sb.Begin();
}
UPDATE #1 The next code is Button.Click event handler, this code creates 20 Images and adds it to Canvas, next step is create animations for each Image using one instance of Storyboard.
private async void b1_Click(object sender, RoutedEventArgs e)
{
CanvasContainer.Children.Clear();
_images = new List<Image>();
// load bitmap
BitmapImage bmp = new BitmapImage(new Uri("Assets/appbar/appbar.italic.png", UriKind.Relative));
// create 20 Image instance
for (int i = 0; i < 20; i++)
{
Image img = new Image();
img.Source = bmp;
img.Stretch = Stretch.Fill;
img.Width = 20;
img.Height = 20;
_images.Add(img);
Canvas.SetTop(img, 0);
Canvas.SetLeft(img, i * 20 + 5);
CanvasContainer.Children.Add(img);
}
// Simulate some delay or any task (3 sec)
await Task.Delay(3000);
Storyboard sb = new Storyboard();
// delay animation time for each object
TimeSpan beginTime = TimeSpan.FromMilliseconds(0);
foreach (Image img in _images)
{
DoubleAnimation da = new DoubleAnimation();
da.From = 0; // Set start value to 0 px
da.To = 700; // Set end value to 700 px
da.Duration = new Duration(TimeSpan.FromSeconds(2)); // Set animation time to 2 sec
da.BeginTime = beginTime; // Set delay for each Image
beginTime += TimeSpan.FromMilliseconds(100);
Storyboard.SetTarget(da, img);
Storyboard.SetTargetProperty(da, new PropertyPath("(Canvas.Top)"));
sb.Children.Add(da);
}
sb.Begin();
}
Code result:
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 saw an example on how to create a glow effect when the image gets focus in mark-up.
Below is the C# code example i found that uses double animation for opacity of a rectangle, i need to perform a glow or swivel effect using C# and not markup since i am not comfortable with it.
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 1.0;
myDoubleAnimation.To = 0.0;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
myDoubleAnimation.AutoReverse = true;
myDoubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.OpacityProperty));
You can add this in your Main(), after InitializeComponent();
textBlock1.Text = "Mouse over me";
var effect = new DropShadowEffect();
effect.Color = Colors.Red;
effect.BlurRadius = 10d;
effect.Opacity = 0d;
effect.ShadowDepth = 0d;
textBlock1.Effect = effect;
textBlock1.MouseEnter += (s, e) => {
var anim = new DoubleAnimation(0d, 1d, new Duration(TimeSpan.FromMilliseconds(500)));
effect.BeginAnimation(DropShadowEffect.OpacityProperty, anim); };
textBlock1.MouseLeave += (s, e) => {
var anim = new DoubleAnimation(1d, 0d, new Duration(TimeSpan.FromMilliseconds(500)));
effect.BeginAnimation(DropShadowEffect.OpacityProperty, anim); };
But I'd suggest stop wasting time with C# for GUI code, XAML is so much more convenient.
I am trying to get these ellipses to grow but I cannot figure out how to start the animation. This is my first attempt at WPF animation and I don't quite understand how it all works.
private void drawEllipseAnimation(double x, double y)
{
StackPanel myPanel = new StackPanel();
myPanel.Margin = new Thickness(10);
Ellipse e = new Ellipse();
e.Fill = Brushes.Yellow;
e.Stroke = Brushes.Black;
e.Height = 0;
e.Width = 0;
e.Opacity = .8;
canvas2.Children.Add(e);
Canvas.SetLeft(e, x);
Canvas.SetTop(e, y);
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 0;
myDoubleAnimation.To = 10;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
Storyboard.SetTargetName(myDoubleAnimation, e.Name);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Ellipse.HeightProperty));
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Ellipse.WidthProperty));
}
You don't need a Storyboard here. Just do
e.BeginAnimation(Ellipse.WidthProperty, myDoubleAnimation);
e.BeginAnimation(Ellipse.HeightProperty, myDoubleAnimation);
If you really need to do it with a Storyboard, you will have to add separate animations, one per animated property, to the Storyboard. And you have to call SetTarget instead of SetTargetName when you don't apply a name. Finally you'll need to start the Storyboard by calling Begin:
DoubleAnimation widthAnimation = new DoubleAnimation
{
From = 0,
To = 10,
Duration = TimeSpan.FromSeconds(5)
};
DoubleAnimation heightAnimation = new DoubleAnimation
{
From = 0,
To = 10,
Duration = TimeSpan.FromSeconds(5)
};
Storyboard.SetTargetProperty(widthAnimation, new PropertyPath(Ellipse.WidthProperty));
Storyboard.SetTarget(widthAnimation, e);
Storyboard.SetTargetProperty(heightAnimation, new PropertyPath(Ellipse.HeightProperty));
Storyboard.SetTarget(heightAnimation, e);
Storyboard s = new Storyboard();
s.Children.Add(widthAnimation);
s.Children.Add(heightAnimation);
s.Begin();