Despite the fact that I have woefully neglected my incipient series on developing a WPF application with TDD (aka ChumChase), like many others this week I've been taken by the Silverlight Lust.
I decided to make a simple game, and settled on an Asteroids clone. I drew some inspiration and insight from the blogs of Bill Reiss, Andy Beaulieu, and Robby Ingebretsen.
I started off with a basic triangular ship, drawn using this:
<UserControl x:Class="BlueSpire.Roids.Symbols.StarShip"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Canvas x:Name="LayoutRoot"
Width="50" Height="105">
<Path x:Name="Ship"
Fill="#FF1C8249"
Stretch="Fill"
Stroke="#FFFFFFFF"
Data="M50,0 L25,105 L50,80 L75,105 z"/>
</Canvas>
</UserControl>
I was more interested in gameplay, than graphics. :-)
However, as my imagination began to flame, I had a idea that required animated bitmap graphics.
Animations that use bitmaps are essentially little flip-books, where you display a sequence of bitmaps in rapid succession providing the illusion of animation. There are lots of ways to achieve this technically. I found a StackOverflow question asking about the best approach. The aforementioned Bill Reiss provided an answer and I decided to implement it.
Sprite Sheet
First you create a single image that contains all of frames. For my proof of concept, I decided to make a little engine glow over the course of 8 frames. The results was the 512x64 image.
The idea is that we display just a portion of the image, in this case 64x64 square portion. The actual UIElement that will represent the ship on screen is a Rectangle. We'll make it 64x64 in size, and set it's Fill property with an ImageBrush pointing to my sprite sheet. Then I animated a TranslateTransform on the ImageBrush to display the each frame in sequence. Well, code speaks louder than words, so:
private void SpriteSheetTest()
{
const int sprite_size = 64; //the frame is 64x64
const int num_of_frames = 8;
var ship_element = new Rectangle();
ship_element.Width = ship_element.Height = sprite_size;
var ship_sprite_sheet = new ImageBrush
{
Stretch = Stretch.None,
AlignmentX = AlignmentX.Left,
AlignmentY = AlignmentY.Top
};
// If the brush alignment is not set, the image will be
// centered and translation transform won't be right.
var sprite_sheet_position = new TranslateTransform();
ship_sprite_sheet.Transform = sprite_sheet_position;
ship_sprite_sheet.ImageSource = ResourceHelper.GetBitmap("Resources/Roid.png");
ship_element.Fill = ship_sprite_sheet;
// I'm dynamically creating the frames, displaying a
// new frame every 80 ms.
var sprite_anim = new DoubleAnimationUsingKeyFrames();
for (int i = 0; i < num_of_frames; i++)
{
var frame_span = new TimeSpan(0, 0, 0, 0, i * 80);
sprite_anim.KeyFrames.Add(new DiscreteDoubleKeyFrame
{
Value = (-sprite_size*i),
KeyTime = KeyTime.FromTimeSpan(frame_span)
});
}
// finally, we need a storyboard to tie the animation
// to the transform
var sb = new Storyboard {RepeatBehavior = RepeatBehavior.Forever};
sb.Children.Add(sprite_anim);
Storyboard.SetTarget(sprite_anim, sprite_sheet_position);
Storyboard.SetTargetProperty(sprite_anim,
new PropertyPath(TranslateTransform.XProperty));
sb.Begin();
//Stage is a Canvas that represent the main area of the game
Stage.Children.Add(ship_element);
}
The ResourceHelper class is a handy bit for working with embedded resources, that I picked up here. Hint, embedded resources have a Build Action of Resource and not Embedded Resource. I wasted 15 or 20 minutes on that one...
Posted
10-17-2008 11:39 AM
by
Christopher Bennage