What with all the high-tech announcements around “Project Natal” I thought I might head the other direction and play with something a little more on the low-tech end of the spectrum;
a Silverlight 3 version of Pong 🙂
I tried to be reasonably retro here and reproduce the original look and feel somewhat ( didn’t take me long to draw those 2 lines in Expression Blend ).
You can run the application from here – not sure it really makes for a two-player game these days but I spent a few minutes playing against myself.
(keys involved are left, right, up, down, space to start, N for new game, L/S for load/save UI )
A couple of interesting things drop out from this.
One is that I ended up using the new Viewbox control because it is easier for me to program against a game like this using the absolute layout capabilities of a Canvas but I still want a resizable UI. So…I program against a fixed size Canvas and then just drop it into a Viewbox to get a cheap-and-cheerful form of resizable UI.
The other is that I added in a few secret keys. One reveals a different UI;
and that UI then allows me to Save out the default UI that the game is using which looks like this;
<controls:Viewbox
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Stretch="Fill">
<Grid>
<Canvas
Width="640"
Height="480"
Background="#FF242424">
<Path
Fill="#FFFFFFFF"
Stretch="Fill"
Stroke="#FF98FF94"
StrokeThickness="10"
Height="10"
Width="620"
UseLayoutRounding="False"
Canvas.Left="10"
Canvas.Top="10"
Data="M35.930759,28.61153 L609.49213,28.61153"
StrokeDashArray="3"
StrokeDashOffset="0" />
<Canvas
x:Name="CanvasUI"
Height="460"
Width="640"
Canvas.Left="0"
Canvas.Top="20">
<Rectangle
x:Name="Player1UI"
Fill="#FF98FF94"
Stroke="{x:Null}"
StrokeThickness="10"
Height="100"
Width="15"
Canvas.Top="{Binding PlayerPositions.Player1Position}" />
<Rectangle
x:Name="Player2UI"
Fill="#FF98FF94"
Stroke="{x:Null}"
StrokeThickness="10"
Height="100"
Width="15"
Canvas.Left="625"
Canvas.Top="{Binding PlayerPositions.Player2Position}" />
<Rectangle
x:Name="BallUI"
Fill="#FF98FF94"
Stroke="{x:Null}"
StrokeThickness="10"
Height="20"
Width="20"
Canvas.Left="{Binding BallPosition.X}"
Canvas.Top="{Binding BallPosition.Y}" />
</Canvas>
<Path
Fill="#FFFFFFFF"
Stretch="Fill"
Stroke="#FF98FF94"
StrokeDashArray="1"
StrokeDashOffset="3"
StrokeThickness="10"
Height="460"
Width="10"
UseLayoutRounding="False"
Canvas.Left="320"
Canvas.Top="20"
Data="M303.91434,48.573063 L303.91434,443.24084" />
<TextBlock
Height="120"
Width="84"
Canvas.Left="349"
Canvas.Top="39"
FontFamily="./Fonts/Fonts.zip#OCR A Std"
FontSize="72"
TextAlignment="Left"
TextWrapping="Wrap"
Foreground="#FF98FF94"
Text="{Binding PlayerScores.Player1Score}" />
<TextBlock
Height="120"
Width="84"
Canvas.Left="220"
Canvas.Top="39"
FontFamily="./Fonts/Fonts.zip#OCR A Std"
FontSize="72"
TextAlignment="Right"
TextWrapping="Wrap"
Foreground="#FF98FF94"
Text="{Binding PlayerScores.Player2Score}">
</TextBlock>
</Canvas>
<TextBlock
Foreground="#FF98FF94"
HorizontalAlignment="Center"
FontSize="72"
FontFamily="./Fonts/Fonts.zip#OCR A Std"
VerticalAlignment="Center"
Text="Game Over"
Visibility="{Binding IsGameOverVisibility}"/>
</Grid>
</controls:Viewbox>
and then I could go ahead and tweak that because it’s essentially a templated control ( I should perhaps go the whole-hog here and introduce the Visual State Manager to actually build a control ) but I rely on the UI containing a few pieces;
- Player1UI
- Player2UI
- BallUI
- CanvasUI
and I make a few properties available to binding;
- PlayerScores.Player1Score, Player2Score
- BallPosition.X, BallPosition.Y
- PlayerPositions.Player1Position, Player2Position
but, with a few caveats, the UI can be ripped and replaced so if I change that UI to look something like;
<controls:Viewbox
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Stretch="Fill">
<Grid>
<Canvas
x:Name="CanvasUI"
Width="640"
Height="480">
<Canvas.Background>
<ImageBrush
ImageSource="http://www.windowsvienna.com/Pictures/Windows%207/Wallpaper/Windows%207%20Aurora%20Green%20Wallpaper.jpg"
Stretch="Fill" />
</Canvas.Background>
<Rectangle
x:Name="Player1UI"
Stroke="#FFFFFFFF"
StrokeThickness="3"
Height="100"
Width="15"
Canvas.Left="25"
Canvas.Top="{Binding PlayerPositions.Player1Position}"
RadiusX="5"
RadiusY="5">
<Rectangle.Fill>
<LinearGradientBrush
EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop
Color="#FF52BE00" />
<GradientStop
Color="#FF006909"
Offset="1" />
<GradientStop
Color="#FF90FE6B"
Offset="0.51" />
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.Effect>
<DropShadowEffect
Color="#FF128400" />
</Rectangle.Effect>
</Rectangle>
<Rectangle
x:Name="Player2UI"
Stroke="#FFFFFFFF"
StrokeThickness="3"
Height="100"
Width="15"
Canvas.Left="600"
Canvas.Top="{Binding PlayerPositions.Player2Position}"
RadiusX="5"
RadiusY="5">
<Rectangle.Fill>
<LinearGradientBrush
EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop
Color="#FF52BE00" />
<GradientStop
Color="#FF006909"
Offset="1" />
<GradientStop
Color="#FF90FE6B"
Offset="0.51" />
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.Effect>
<DropShadowEffect
Color="#FF128400" />
</Rectangle.Effect>
</Rectangle>
<Image
x:Name="BallUI"
Height="48"
Width="48"
Source="http://www.pclaunches.com/entry_images/0907/22/microsoft_vista-logo.png"
Canvas.Left="{Binding BallPosition.X}"
Canvas.Top="{Binding BallPosition.Y}" />
</Canvas>
<TextBlock
FontFamily="Trebuchet MS"
Foreground="White"
HorizontalAlignment="Center"
FontSize="72"
VerticalAlignment="Center"
Text="Game Over"
Visibility="{Binding IsGameOverVisibility}" />
<StackPanel
HorizontalAlignment="Left"
VerticalAlignment="Top">
<StackPanel
Orientation="Horizontal">
<TextBlock
FontFamily="Trebuchet MS"
FontSize="20"
Foreground="White"
Text="Player 1" />
<TextBlock
Margin="10,0,0,0"
FontFamily="Trebuchet MS"
FontSize="20"
Foreground="White"
Text="{Binding PlayerScores.Player1Score}" />
</StackPanel>
<StackPanel
Orientation="Horizontal">
<TextBlock
FontFamily="Trebuchet MS"
FontSize="20"
Foreground="White"
Text="Player 2" />
<TextBlock
Margin="10,0,0,0"
FontFamily="Trebuchet MS"
FontSize="20"
Foreground="White"
Text="{Binding PlayerScores.Player2Score}" />
</StackPanel>
</StackPanel>
</Grid>
</controls:Viewbox>
then I end up with a new “playing experience” something like;
I wonder how far I could take that kind of idea – this was just for fun but I might revisit this whole idea and perhaps;
- Port the code to WPF and see what that looks like based on my previous post.
- Introduce the Visual State Manager and turn this into more of a templated control.
Here’s the code to download – not necessarily the most beautiful thing I’ve ever done so don’t read too much into it.