How to develop a casual game with Silverlight 2
Game programming was my first love in computer science, and a couple of weeks ago I decided to give it a try with the new Silverlight 2 version available. To my surprise I could accomplish something fun with little effort, and maybe there's someone out there trying to do stuff like this, so let's go through the basics of building a casual game with silverlight 2. Our target will be to develop a mutant version of the old breakout classic. We are going to animate a paddle to prevent a ball from touching the ground while breaking bricks on top of each screen.
Assuming you have a Visual Studio 2008 installation, with the Silverlight Tools for VS2008 loaded, let's start by creating a Silverlight Application Project.
|
First we are going to modify the Page.xaml created page, to include all the elements our simple game will have, being those:
- a paddle,
- a ball,
- some bricks to destroy
Let's start by changing the default Grid layout, to use instead a Canvas layout, with some rectangles in it. Use the following xaml code:
<Canvas x:Name="GameCanvas" Background="LightGray" Width="400" Height="300">
<Rectangle x:Name="paddle" Fill="Black" Width="60" Height="10" Canvas.Top="280" Canvas.Left="180"/>
<Rectangle x:Name="ball" Fill="Black" Width="10" Height="10" RadiusX="5" RadiusY="5" Canvas.Left="200" Canvas.Top="250"/>
<Rectangle x:Name="Brick1" Fill="Red" Width="40" Height="15" Canvas.Left="120" Canvas.Top="50"/>
<Rectangle x:Name="Brick2" Fill="Blue" Width="40" Height="15" Canvas.Left="180" Canvas.Top="50"/>
<Rectangle x:Name="Brick3" Fill="Green" Width="40" Height="15" Canvas.Left="240" Canvas.Top="50"/>
</Canvas>
You should be able to see something like this in the updated design preview,
Animating the paddle
To animate the paddle we are going to add an event handler for the KeyDown event, in the Canvas element of the Page user control. When doing this we'll come across a beta bug, adding the Canvas KeyDown event handler in the xaml doesn't work (at least, not for me), but if I register the event handler in the code behind it works.
So far, my code looks something like the following code block, and after build you should be able to happily move your paddle left and right ways.
public Page()
{
InitializeComponent();
this.KeyDown += new KeyEventHandler(GameCanvas_KeyDown);
}
private void GameCanvas_KeyDown(object sender, KeyEventArgs e)
{
double currentX = (double)paddle.GetValue(Canvas.LeftProperty);
double newX = 0;
if (e.Key.Equals(Key.Left))
{
newX = currentX - 5;
if (newX < 0)
{
newX = 0;
}
}
else if (e.Key.Equals(Key.Right))
{
newX = currentX + 5;
if ((newX + paddle.Width) > GameCanvas.Width)
{
newX = GameCanvas.Width - paddle.Width;
}
}
paddle.SetValue(Canvas.LeftProperty, newX);
}
But there's still not much fun involved, everything still is pretty static, so let's animate the ball now.
Animating the ball
In Silverlight there might be a better way to do this with storyboards, but I'm going to go old school on this one and use a timer, and increment the ball coordinates with a vector.
System.Threading.Timer tick;
double vectorY = 10;
double vectorX = 10;
In the code of a new GotFocus handler for the Canvas element, we are going to set the timer. The timer invokes the OnTick handler on wake up, which in turn uses the beautiful Dispatcher.BeginInvoke(()=>UpdateBallPosition()) lambda expression to execute code in the UI thread, and be able to access safely all the UI elements in the canvas, and calculate the new coordinates of the ball and update them.
private void OnGotFocus(object source, EventArgs args)
{
tick = new System.Threading.Timer(OnTick, null, 1000, 100);
}
void OnTick(object sender)
{
this.Dispatcher.BeginInvoke(() => UpdateBallPosition());
}
void UpdateBallPosition()
{
double x = (double)ball.GetValue(Canvas.LeftProperty);
double y = (double)ball.GetValue(Canvas.TopProperty);
double paddleX = (double)paddle.GetValue(Canvas.LeftProperty);
double paddleY = (double)paddle.GetValue(Canvas.TopProperty);
double maxX = GameCanvas.Width;
double maxY = GameCanvas.Height;
x += vectorX;
y += vectorY;
#region collision detection
#region outer boundaries
if (x <= 0 || x >= (maxX - ball.Width))
{
vectorX = -(vectorX);
x = (x <= 0) ? 0 : x;
x = x >= (maxX - ball.Width) ? (maxX - ball.Width) : x;
}
bool paddleHit = ((y + ball.Height) >= paddleY && x >= paddleX && x <= (paddleX + paddle.Width));
if (y <= 0 || y >= (maxY - ball.Height) || paddleHit)
{
vectorY = -(vectorY);
y = (y <= 0) ? 0 : y;
y = y >= (maxY - ball.Height) ? (maxY - ball.Height) : y;
}
#endregion
#endregion
ball.SetValue(Canvas.TopProperty, y);
ball.SetValue(Canvas.LeftProperty, x);
}
Then we add code like the following to enable collision detection with the bricks,
#region collision detection blocks
double blockY = (double)Brick1.GetValue(Canvas.TopProperty);
double blockX = (double)Brick1.GetValue(Canvas.LeftProperty);
if (y >= blockY && y <= (blockY + Brick1.Height) && x >= blockX && x <= (blockX + Brick1.Width))
{
//block collision
Brick1.Fill = new SolidColorBrush(Colors.Yellow);
vectorY = -(vectorY);
}
#endregion
This is just for one brick, you do your thing to generalize this code for all three bricks.
And of course, it wouldn't be a game if there's no way to loose or win, so we add logic to control win or lose conditions.
#region gameover and win conditions
//GAME OVER?
if (y >= (maxY - ball.Height))
{
//game over
tick.Dispose();
return;
}
#endregion
This is just the lose condition, but I hope you get the idea. Next week we'll pimp up this solution to add score handling and sounds to this breakout mutant. The source code from the developed casual game sample can be downloaded from here.