Silverlight Fast Moving Bitmap Does Not Update Smoothly - c#

When I move a bitmap-filled path in Silverlight by updating its RenderTransform, the image updates with a stutter/jitter about every half-second.
The Path uses a BitmapCache, and Hardware Acceleration is turned on. The hardware acceleration is verified using the EnableCacheVisualization option, and by observing that the frame rate is high ~ 60 fps.
You can see what I am talking about here. It is a working silverlight app showing the stuttering behavior. (You might imagine what game I am trying to make when you see the image...)
http://glerok.com/smb/index.html
You will notice that the bitmap scrolls smoothly, but every 0.5 to 1 seconds, it stutters, as if some frames were skipped. I verified this on two different PCs.
Is this expected behavior in silverlight, or am I doing something wrong by manually updating the RenderTransform of the object? (Please don't take this as an opportunity to disparage Silverlight...)
Thanks in advance!!!
Here is the code I am using to generate the path:
RectangleGeometry rg = new RectangleGeometry();
BitmapImage bi = new BitmapImage(new Uri("world.png", UriKind.Relative));
int WorldWidth = 703;
int WorldHeight = 240;
rg.Rect = new Rect(WorldTilePosition, 0, WorldWidth * Scale, WorldHeight * Scale);
Path p = new Path { RenderTransform = new TranslateTransform { X = 0, Y = 0 } };
p.Data = rg;
p.CacheMode = new BitmapCache();
p.Fill = new ImageBrush { ImageSource = bi };
canvas.Children.Add(p);
And here is the code that update the path position:
WorldTilePosition-=10;
TranslateTransform tt = WorldPath.RenderTransform as TranslateTransform;
tt.X = -WorldTilePosition;
if (WorldTilePosition < -1000) WorldTilePosition = 1000;
I observed the stuttering/jittering in Silverlight even for an object that is animated with a storyboard. I guess this happens because there is currently no back-buffering in silverlight, making it currently unsuitable for even simple sprite-based games on PC? Note that the overall screen refresh rate is still reported to be 60 fps.
The code below generates an animated rectangle that flickers during the screen refresh. Vertical objects show better the banding during the refresh rate, at least on my laptop.
<Canvas x:Name="LayoutRoot2" Background="White">
<Rectangle x:Name="myRect" Fill="Red" Width="100" Height="500" CacheMode="BitmapCache" >
<Rectangle.RenderTransform>
<TranslateTransform x:Name="myRectTransform" X="0" />
</Rectangle.RenderTransform>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard x:Name="myStoryboard" >
<DoubleAnimation
From="0"
To="1000"
Storyboard.TargetName="myRectTransform"
Storyboard.TargetProperty="X"
Duration="0:0:1"
AutoReverse="True"
RepeatBehavior="Forever"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>

Can't notice any stuttering on my PC (Core 2 Quad 3 GHz, Win7 x64, IE9).
Especially for such a tile based game with low resolution sprites etc. I'd consider using a WriteableBitmap (a nice extended version can be found on Codeplex) and then update/display that one only. This also solves the missing backbuffer.
Also, to avoid noticeable stuttering, try to keep your game logic independent of framerate (using a timer or detecting the number of milliseconds passed since last frame). Skipped frames shouldn't be as noticeable then as well.
Also try to draw the visible part of the landscape only (not sure if clipping is enough; didn't try it).

Related

How to programically change ScaleY RenderTransform of a control in a XAML C# Windows App?

I have several instances of the following xaml code. Essentially it's a bunch of grids which show up in a stackpanel in a small (24 pixel tall) state, showing one line of the textblock. When you click on the arrow image (or rather the border around the image as there's transparency in the image) then the grid expands to show all the details within it. I have 15 of these in total:
<Grid x:Name="borLecSec1" Style="{StaticResource SearchedSectionGrid}">
<TextBlock x:Name="txtLecSec1" Style="{StaticResource SearchedSectionText}"
PointerEntered="SearchSectionEntered" PointerExited="SearchSectionExited"
Tapped="SearchSectionTapped"/>
<Border x:Name="backArrowSection_Lec_1" Style="{StaticResource ExpandSectionButton}"
PointerEntered="backArrowSectionEnter" PointerExited="backArrowSectionExit"
Tapped="backArrowSectionTapped">
<Image x:Name="arrowSection_Lec_1" Style="{StaticResource ExpandSectionImage}">
<Image.RenderTransform>
<CompositeTransform ScaleY="1"/>
</Image.RenderTransform>
</Image>
</Border>
</Grid>
I haven't figured out how to animate the grids themselves expanding yet as they would have to go from 24 pixels to Auto which I haven't gotten to work. What I have gotten to work is that when clicked, this arrow flips vertically so that it now signifies that another click will cause the grid to collapse down. The storyboard for this animation is this:
<Storyboard x:Name="SearchSectionArrowExpand">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
From="1" To="-1" Duration="00:00:0.15"/>
</Storyboard>
<Storyboard x:Name="SearchSectionArrowCollapse">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
From="-1" To="1" Duration="00:00:0.15"/>
</Storyboard>
As there are many instances of this arrow image control, the target property of the storyboard is changed before every Begin() statement in the C# code behind. The code I have follows. In it, the working_grid and working_image objects correspond to borLecSec1 and arrowSection_Lec_1 respectively in the xaml above.
if (working_grid.Height == 24)
{
System.Diagnostics.Debug.WriteLine("expand");
working_grid.Height = double.NaN;
SearchSectionArrowCollapse.Stop();
SearchSectionArrowExpand.Stop();
Storyboard.SetTargetName(SearchSectionArrowExpand, working_image_name);
SearchSectionArrowExpand.Begin();
}
The collapse part of the code is very similar in the subsequent else. The Stop() commands are necessary as I get an error if they're not there saying the root storyboard must be stopped before re-targeting. So everything I've said works fine. What doesn't work is that if I expand the first grid, so arrowSection_Lec_1 has a ScaleY of -1, if I then expand the second grid giving arrowSection_Lec_2 a ScaleY of -1 as well, the first image reverts back to having a ScaleY of 1 even though its corresponding grid is still expanded.
The solution I thought up is to have the storyboard Completed event set the ScaleY of the appropriate arrow explicitly so that it would maintain this position even if the storyboard is run again for a different arrow. I can't figure out how to reference this property in C#.
So for clarity, my question is how would I set the ScaleY transform of arrowSection_Lec_1 to -1 from the code behind?
Here is how to access the Scale from the codebehind:
var transform = (CompositeTransform)arrowSection_Lec_1.RenderTransform;
transform.ScaleY = -1;

Pixel Shader Effect in WPF

I was following along this tutorial about how to include Pixel Shader Effects in the form of an animation within a WPF application.
Background
I felt like things were going smoothly, however I wanted to make a change to the application. In the tutorial the author has a separate GrayscaleEffect project and in his XAML does the following:
xmlns:effect="clr-namespace:GrayscaleEffect;assembly=GrayscaleEffect"
Later he has this:
DataTemplate x:Key="itemTemplate">
<Grid Width="225" Margin="3">
<Border BorderBrush="White" BorderThickness="2">
<Image Source="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image.Effect>
<local:GrayscaleEffectTest x:Name="grayscaleEffect"/>
</Image.Effect>
<Image.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard>
<Storyboard>
<!--HERE--> <DoubleAnimation To="1.0" Duration="0:0:0.3" AccelerationRatio="0.10" DecelerationRatio="0.25" Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(local:GrayscaleEffectTest.DesaturationFactor)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation To="0.0" Duration="0:0:4" AccelerationRatio="0.10" DecelerationRatio="0.25" Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(local:GrayscaleEffectTest.DesaturationFactor)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
</Border>
</Grid>
</DataTemplate>
The key point here is the very long line <DoubleAnimation ....:
Storyboard.TargetProperty="(effect:GrayscaleEffect.DesaturationFactor)" />
My Approach
I wanted to make the same thing, except I wanted to keep all of my code in the same project, rather than having two projects.
So, I don't include the ;assembly=GrayscaleEffect. Furthermore my <DoubleAnimation ... line reads as:
<DoubleAnimation ... Storyboard.TargetName="grayscaleEffect" Storyboard.TargetProperty="(local:GrayscaleEffectTest.DesaturationFactor)" />
However the WPF designer throws an initializer exception. The program runs but no animation ever gets triggered when I mouse over...
Anyone have any ideas? I feel confident that WPF should be able to run a pixel shader from within the same project...My project has the pre-build event, and other than renaming as GrayscaleEffect Test and having both projects combined into one, my project should be identical to the tutorial. I've tried a number of other failure approaches, mainly setting Storyboard.TargetProeprty = every damn thing under the sun. Also tried to hack together something to set up the animation in code behind in hopes I could at least walk through with a debugger and try to see what is going on. Obviously, nothing worked :(.
Any help would be greatly appreciated. Cheers.
Slightly embarrassing but the error was between chair and keyboard. So, the code was working just fine. To maybe help others I'll explain my debugging process, then explain what the problem was.
First I changed the pixel shader to just go from Red to Red and Green. That is:
result.r = 255;
result.g = 0;
result.b = 255 * factor;
Factor is the data variable that gets sent to the graphics card. Once I did this, I noticed that things were kind of working. The transition is a step-function, not a linear interpolation, but colors were changing. Then, I figured pixel color range must be [0 to 1.0]. This gave me the effect I was supposed to be getting.
result.r = 1.0;
result.g = 0;
result.b = 1.0 * factor;
Lastly, I loaded a new penguin picture instead of the original test image I was using and I realized my major mistake. The penguin picture was working just fine and I thought, what the hell?! Then I looked at my test image, which was a black and white image!!! (Dumb, dumb dumb dumb). The shame of having to post this "solution" will hopefully prevent me from making such a mistake again. Thank you for the help #Nico Schertler.

How can I do multiple animations in a single storyboard in C#/XAML?

I'm trying to make a simple Windows Store game in C# and XAML, one that involves hexagonal tiles moving about. This is mostly to help me learn C# and XAML as I've never worked with graphics or even UI coding before.
I've got a method that can move a single hex to the target coordinates, but looking at it now I realize that it is impossible to do multiple moves at once, which is absolutely necessary.
I feel like there's got to be something fundamentally off in my approach, multiple objects moving about a single canvas cannot be an unusual thing, can it? I'm mostly asking this in the hope that someone will point out where I went wrong.
//moves the hex hexName to coordinates x, y, over a specified duration.
public void slideHex(int x, int y, string hexName, Duration duration)
{
GameStoryboard.Stop();
Polygon hex = GameCanvas.FindName(hexName) as Polygon;
TranslateTransform slideTransform = new TranslateTransform();
slideTransform.X = hex.RenderTransformOrigin.X;
slideTransform.Y = hex.RenderTransformOrigin.Y;
hex.RenderTransform = slideTransform;
DoubleAnimation animX = new DoubleAnimation();
DoubleAnimation animY = new DoubleAnimation();
animX.Duration = duration;
animY.Duration = duration;
GameStoryboard.Duration = duration;
GameStoryboard.Children.Add(animX);
GameStoryboard.Children.Add(animY);
Storyboard.SetTarget(animX, slideTransform);
Storyboard.SetTarget(animY, slideTransform);
Storyboard.SetTargetProperty(animX, "X");
Storyboard.SetTargetProperty(animY, "Y");
animX.To = x;
animY.To = y;
GameStoryboard.Begin();
}
A storyboard can contain multiple animations, and each animation can target a different UI element. Here's an example of a storyboard which "pulses" the border colours of three different controls:
<Storyboard x:Name="pulseAnimation" AutoReverse="True">
<ColorAnimation x:Name="animateLatitudeTextBoxBorderColour" Storyboard.TargetName="textBoxLatitude" From="{StaticResource PhoneTextBoxColor}" To="Green" Storyboard.TargetProperty="(TextBox.BorderBrush).(SolidColorBrush.Color)" Duration="0:0:0.4" />
<ColorAnimation x:Name="animateLongitudeTextBoxBorderColour" Storyboard.TargetName="textBoxLongitude" From="{StaticResource PhoneTextBoxColor}" To="Green" Storyboard.TargetProperty="(TextBox.BorderBrush).(SolidColorBrush.Color)" Duration="0:0:0.4" />
<ColorAnimation x:Name="animateHyperlinkTextColour" Storyboard.TargetName="hyperlinkButtonCurrentLocation" From="{StaticResource PhoneForegroundColor}" To="Green" Storyboard.TargetProperty="(HyperlinkButton.Foreground).(SolidColorBrush.Color)" Duration="0:0:0.4" />
</Storyboard>
Your code looks fine - you're already animating multiple properties of slideTransform, and since the target of an animation is a property of the animation rather than the storyboard, there's no reason why you couldn't retarget either animX or animY to a different object altogether.
You can create multiple storyboards and execute them concurrently in order to simultaneously animate multiple objects. See the example in the following article which animates multiple items within a to-do list:
http://www.codeproject.com/Articles/428088/A-Gesture-Driven-Windows-Phone-To-Do-List#swipe

Dynamically changing properties of a Storyboard (created in XAML) in the C# code-behind

I admit I'm new to Silverlight, but you have to start somewhere.
Here's my problem: I have XAML code which creates a Canvas to be used on my web page. I dynamiclly (in the code behind) create 24 smaller canvas objects (called tiles) and can correctly position and move these tiles inside the larger canvas. I want to now animate the movement of the tiles rather than have them just "jump" from one location to the next. Inside the XAML I created a Storyboard and a DoubleAnimation for one of the tiles. Clicking on the specific tile named in the DoubleAnimation/Storyboard produces the correct animation. Now I want to be able to change the properties of the animation in the XAML dynamically by the code in the code-behind. Specifically, I want to change the "From", "To", "Storyboard.TargetName", and "Storyboard.TargetProperty" values. This would allow me to use the single animation to control the movement of all 24 tiles (one at a time). Below is the XAML and below that is the code I've been attempting to get to work correctly.
XAML
<Canvas x:Name="LayoutRoot" Background="BlanchedAlmond" Height="700" Width="1405">
<Image Source="bkcolor.png" Canvas.Left="600" Height="500" Width="500" Stretch="UniformToFill" Canvas.Top="100"></Image>
<Canvas x:Name="myContainer" Canvas.Left = "50" Canvas.Top="100">
<!---->
<Canvas.Resources>
<Storyboard x:Name="MoveTileAnimation">
<DoubleAnimation x:Name="myDoubleAnimation"
From="400" To="300"
Duration="00:00:1"
Storyboard.TargetName="Tile23"
Storyboard.TargetProperty="(Canvas.Top)">
<DoubleAnimation.EasingFunction>
<PowerEase Power="3" EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</Canvas.Resources>
<!---->
</Canvas>
</Canvas>
C# Code in Code Behind
private void MainPage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Canvas c = sender as Canvas;
// New location is specified by nx, ny
int nx = 200;
int ny = 300;
// Old Location
int ox = 200;
int oy = 200;
// "Tile Moves Up" -- Other case removed for clarity
//----------------------------------------------
// Code below is known to work correctly
//----------------------------------------------
// Set the "To" and "From" properties
myDoubleAnimation.From = Convert.ToDouble(oy);
myDoubleAnimation.To = Convert.ToDouble(ny);
//----------------------------------------------
// Code below does not function correctly
//----------------------------------------------
MoveTileAnimation.SetValue(Storyboard.TargetNameProperty, c.Name); // c.Name is the
// name of the tile
// that was clicked
MoveTileAnimation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath(Canvas.TopProperty)); // Need to switch between Top and Left
//----------------------------------------------
MoveTileAnimation.Begin(); // This works if the TargetNameProperty in
// the XAML matches the Tile Name
//----------------------------------------------
// Code below is known to work correctly
//----------------------------------------------
// Move the Tile to its new position
tileCanvas[nCanvasID].SetValue(Canvas.TopProperty, Convert.ToDouble(ny));
tileCanvas[nCanvasID].SetValue(Canvas.LeftProperty, Convert.ToDouble(nx));
}
I don't want to have to create 96 storyboards to be able to move 24 tiles in each of 4 directions. If I can get changing the "TargetNameProperty" working, that reduces to 4 storyboards. If I can also get changing the "TargetPropertyProperty" working, I'm done to a single storyboard.
Thanks in advance,
John
This link points to an article with code for reusable storyboards. It's an old article, but still applies today - maybe these classes will help you. http://www.codeproject.com/Articles/24543/Creating-and-Reusing-Dynamic-Animations-in-Silverl

Programmatically create silverlight animation with easing in C#

I'm messing around with making a clock in silverlight. I am trying to set up the animation code programmatically because I want to reuse the same code for each of the 3 clock hands (and I don't think I can do this with a single storyboard in xaml).
public void Rotate(double toAngle, RotateTransform rotate)
{
Storyboard sb = new Storyboard();
DoubleAnimationUsingKeyFrames keyframes = new DoubleAnimationUsingKeyFrames();
EasingDoubleKeyFrame easingStart = new EasingDoubleKeyFrame();
easingStart.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0));
easingStart.Value = rotate.Angle;
EasingDoubleKeyFrame easingEnd = new EasingDoubleKeyFrame();
easingEnd.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.5));
easingEnd.Value = toAngle;
var ease = new ElasticEase();
ease.EasingMode = EasingMode.EaseIn;
easingEnd.EasingFunction = ease;
keyframes.KeyFrames.Add(easingStart);
keyframes.KeyFrames.Add(easingEnd);
Storyboard.SetTarget(keyframes, rotate);
Storyboard.SetTargetProperty(keyframes, new PropertyPath("(RotateTransform.Angle)"));
sb.Children.Add(keyframes);
sb.Begin();
}
I pass in both the angle I want the current hand to rotate to and the rotate transform for that hand. From this rotate transform I get the starting angle.
When I run the clock, the clock hands move (the second hand moves to the correct spot each second etc) but the animations don't appear to actually animate. They just go from start to end instantly.
Why does the animation not happen properly?
The skipping problem is likely the lag in starting a storyboard that wants to show changes in less than a second. By the time the storyboard gets itself ready to display it basically says "oh... it's after 1 second now, where should I be now". You are better off with longer storyboards.
I'm not sure how much control you are wanting over your clock, but regarding using a single storyboard, here goes...
I was not sure of the overall problem you are trying to solve, but the following example uses one simple storyboard to run the 3 hands of a clock. The only problem I had was that the maximum time interval I could specify for the hour hand (in XAML), without a parsing error, is 23:59:59.
<Storyboard x:Name="SecondHandStoryboard" RepeatBehavior="Forever">
<DoubleAnimation Duration="0:1:0" To="360" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)" Storyboard.TargetName="SecondHand" />
<DoubleAnimation Duration="1:0:0" To="360" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)" Storyboard.TargetName="MinuteHand"/>
<DoubleAnimation Duration="23:59:59" To="360" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.Rotation)" Storyboard.TargetName="HourHand"/>
</Storyboard>
I actually wanted to add a solid "tick" animation to the second hand, but have not figured out how to get rotation "by" storyboards to work. Basically use a repeated 6 degree per-second 1-second long storyboard with steep easing-in (will keep looking into that problem).
Anyway, hope this is of help to you.

Categories

Resources