Diese Woche wird ein weiterer Webcast zum Thema Spieleentwicklung mit Silverlight bei www.my-msdn.com/webcasts veröffentlicht.
Dazu habe ich mein Lieblingsspiel im Windows Mobile Spiele-Sortiment als Vorlage verwendet und es auf Silverlight nach implementiert. Dauer zwei Stunden, meine Freundin ist leidtragende Zeugin :-)
Die Live Version für Silverlight 2 kann man hier finden: http://www.the-oliver.com/demos/sl2/bubblesgame/
Den gesamten Code kann hier herunterladen.
Das ganze ist recht simple gehalten, darf aber sehr gerne hergenommen werden und um neue Features erweitert werden … Einzige Bedingung … ich will es auch mal spielen dürfen :-)
Für die Konfiguration des Spiels habe ich die Klasse Settings.cs erstellt
public class Settings
{
private static int _dimensionX = 12;
public static int DimensionX
get
return Settings._dimensionX;
}
set { Settings._dimensionX = value; }
private static int _dimensionY = 12;
public static int DimensionY
get { return Settings._dimensionY; }
set { Settings._dimensionY = value; }
private static int _margin = 1;
public static int Margin
get { return Settings._margin; }
set { Settings._margin = value; }
private static GameLevel _level = GameLevel.Normal;
public static GameLevel Level
if (HtmlPage.IsEnabled == false)
return _level;
if (IsolatedStorageSettings.ApplicationSettings.Contains("Level"))
try
GameLevel level = (GameLevel)IsolatedStorageSettings.ApplicationSettings["Level"];
return level;
catch
set
switch (value)
case GameLevel.Kindergarten:
DimensionX = 6;
DimensionY = 6;
break;
case GameLevel.Hammer:
DimensionX = 20;
DimensionY = 20;
case GameLevel.Normal:
default:
DimensionX = 12;
DimensionY = 12;
IsolatedStorageSettings.ApplicationSettings["Level"] = value;
public enum GameLevel
Kindergarten,
Normal,
Hammer
Zweiter Schritt: Das Basis-Element der “Bubble” in der Klasse Bubbles.xaml und Bubbles.xaml.cs
<UserControl x:Class="Bubbles.Controls.Bubble"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="Auto" Height="Auto" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="18" d:DesignHeight="18">
<Grid x:Name="LayoutRoot" Background="{x:Null}">
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<Ellipse Stroke="{x:Null}" Grid.RowSpan="2">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,0.987">
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="0.782" ScaleY="0.782"/>
<SkewTransform CenterX="0.5" CenterY="0.5"/>
<RotateTransform CenterX="0.5" CenterY="0.5"/>
<TranslateTransform Y="0.114" X="-0.006"/>
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop x:Name="_g0" Color="#FF000000" Offset="1"/>
<GradientStop x:Name="_g1" Color="#FFFFFFFF" Offset="0"/>
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Path Stretch="Fill" Stroke="{x:Null}" Data="M154,78 C155.90759,106.84779 118.52592,63.935123 76,63.935123 C33.474075,63.935123 -0.95520449,109.44034 0,78 C0.78876519,52.037975 34.474075,0 77,0 C119.52592,0 152.05786,48.629925 154,78 z" Margin="0.032,0.085,-0.042,0.389">
<Path.Fill>
<LinearGradientBrush EndPoint="0.506,0.915" StartPoint="0.5,0">
<GradientStop Color="#19FFFFFF" Offset="1"/>
<GradientStop Color="#BFFFFFFF"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
</Grid>
</UserControl>
Die entsprechende Klasse dazu:
using System.Windows.Controls;
using System.Windows.Media;
using System;
namespace Bubbles.Controls
public partial class Bubble : UserControl
public Bubble()
InitializeComponent();
private Color _bubbleColor;
public Color BubbleColor
_bubbleColor = _g0.Color;
return _bubbleColor;
_bubbleColor = value;
_g0.Color = _bubbleColor;
internal void Remove()
//throw new NotImplementedException();
Die eigentliche Spielfläche (Playground.cs)
using System.Windows;
using System.Collections.Generic;
using System.ComponentModel;
public partial class PlayGround : UserControl
Bubble[,] _bubbleArea;
Grid _grid;
MediaElement _sound;
public PlayGround()
// Only for Design Time
if (DesignerProperties.GetIsInDesignMode(this))
CreatePlayground();
return;
// Trick to initialize ;-)
Settings.Level = Settings.Level;
#region Events
public event EventHandler GameFinished;
public event BubblesRemovedEventHandler BubblesRemoved;
#endregion
#region Eventhandler
void bubble_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
Bubble bubble = sender as Bubble;
DoBubbleClick(bubble);
public void Start()
private void CreatePlayground()
#region Create Bubble Area
// Create Grid
_grid = new Grid();
this.Content = _grid;
// Create Columns
double width = 1 / Convert.ToDouble(Settings.DimensionX);
for (int x = 0; x < Settings.DimensionX; x++)
ColumnDefinition cd = new ColumnDefinition();
cd.Width = new GridLength(width, GridUnitType.Star);
_grid.ColumnDefinitions.Add(cd);
// Create Rows
double height = 1 / Convert.ToDouble(Settings.DimensionY);
for (int y = 0; y < Settings.DimensionY; y++)
RowDefinition cd = new RowDefinition();
cd.Height = new GridLength(height, GridUnitType.Star);
_grid.RowDefinitions.Add(cd);
#region Create Bubbles
// Create Bubble
_bubbleArea = new Bubble[Settings.DimensionX, Settings.DimensionY];
Random rnd = new Random();
// Create Random Bubble
Bubble bubble = new Bubble();
bubble.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(bubble_MouseLeftButtonDown);
Color color;
switch (rnd.Next(5))
case 0:
color = Colors.Blue;
case 1:
color = Colors.Red;
case 2:
color = Colors.Yellow;
case 3:
color = Colors.Green;
case 4:
color = Colors.Orange;
color = Colors.Black;
bubble.BubbleColor = color;
_bubbleArea[x, y] = bubble;
#region Display Bubbles
// Display Bubbles
Bubble bubble = _bubbleArea[x, y];
bubble.Margin = new Thickness(Settings.Margin);
bubble.SetValue(Grid.ColumnProperty, x);
bubble.SetValue(Grid.RowProperty, y);
_grid.Children.Add(bubble);
#region Add Sound
_sound = new MediaElement();
_sound.Source = new Uri("bubblepop.wma", UriKind.Relative);
_sound.AutoPlay = false;
_sound.Volume = 1;
_grid.Children.Add(_sound);
private void DoBubbleClick(Bubble bubble)
// Find Surrounding Bubbles
List<Bubble> surroundingBubbles = new List<Bubble>();
GetSurroundingBubbles(bubble, surroundingBubbles);
surroundingBubbles.Add(bubble);
if (surroundingBubbles.Count > 1)
RemoveBubbles(surroundingBubbles);
private void RemoveBubbles(List<Bubble> surroundingBubbles)
if (surroundingBubbles.Count == 0)
_sound.Stop();
if (BubblesRemoved != null)
BubblesEventArgs args = new BubblesEventArgs();
args.BubbleCount = surroundingBubbles.Count;
BubblesRemoved(this, args);
_sound.Play();
foreach (Bubble b in surroundingBubbles)
if (_bubbleArea[x, y] == b)
_bubbleArea[x, y] = null;
b.Remove();
b.MouseLeftButtonDown -= bubble_MouseLeftButtonDown;
_grid.Children.Remove(b);
LetBubblesFall();
private void LetBubblesFall()
for (int i = 0; i < Settings.DimensionY - 1; i++)
for (int y = Settings.DimensionY - 2; y >= 0; y--)
if (_bubbleArea[x, y + 1] == null)
// Move Bubble down
_bubbleArea[x, y + 1] = _bubbleArea[x, y];
BubblesToTheRight();
private void BubblesToTheRight()
for (int x = 0; x < Settings.DimensionY - 1; x++)
if (_bubbleArea[x + 1, y] == null)
if (_bubbleArea[x, y] != null)
_bubbleArea[x + 1, y] = _bubbleArea[x, y];
UpdateBubbles();
private void UpdateBubbles()
if (bubble != null)
CheckForEnd();
private void CheckForEnd()
bool finished = true;
if (surroundingBubbles.Count > 0)
finished = false;
if (finished)
if (GameFinished != null)
GameFinished(this, EventArgs.Empty);
private void GetSurroundingBubbles(Bubble bubble, List<Bubble> surroundingBubbles)
if (bubble == null)
int bubbleX = -1;
int bubbleY = -1;
if (_bubbleArea[x, y] == bubble)
bubbleX = x;
bubbleY = y;
Bubble bubbleToCheck;
// Left Bubble
if (bubbleX > 0 && bubbleY != -1)
bubbleToCheck = _bubbleArea[bubbleX - 1, bubbleY];
CheckBubble(bubbleToCheck, bubble, surroundingBubbles);
// Right Bubble
if (bubbleX < Settings.DimensionX - 1 && bubbleY != -1)
bubbleToCheck = _bubbleArea[bubbleX + 1, bubbleY];
// Top Bubble
if (bubbleX != -1 && bubbleY > 0)
bubbleToCheck = _bubbleArea[bubbleX, bubbleY - 1];
// Bottom Bubble
if (bubbleX != -1 && bubbleY < Settings.DimensionY - 1)
bubbleToCheck = _bubbleArea[bubbleX, bubbleY + 1];
private void CheckBubble(Bubble bubbleToCheck, Bubble bubble, List<Bubble> surroundingBubbles)
if (bubbleToCheck == null)
if (bubbleToCheck.BubbleColor == bubble.BubbleColor)
if (!surroundingBubbles.Contains(bubbleToCheck))
surroundingBubbles.Add(bubbleToCheck);
GetSurroundingBubbles(bubbleToCheck, surroundingBubbles);
Die Hauptseite (Page.xaml)
<UserControl x:Class="Bubbles.Page"
xmlns:Bubbles_Controls="clr-namespace:Bubbles.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="300" d:DesignHeight="300" MinHeight="300" MinWidth="300" >
<Grid x:Name="LayoutRoot">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF000000"/>
<GradientStop Color="#FFB9B9B9" Offset="0.906"/>
<GradientStop Color="#FF000000" Offset="1"/>
</Grid.Background>
<RowDefinition Height="36"/>
<RowDefinition Height="*"/>
<Bubbles_Controls:PlayGround x:Name="_playground" Grid.Row="1"/>
<TextBlock HorizontalAlignment="Left" x:Name="_header" VerticalAlignment="Top" FontSize="20" Text="Bubbles" TextWrapping="Wrap" Foreground="#FFFFFFFF" FontFamily="Comic Sans MS"/>
<TextBlock HorizontalAlignment="Right" x:Name="_points" VerticalAlignment="Center" Text="0" TextWrapping="Wrap" Foreground="#FFFFFFFF" Margin="0,0,0,0" FontFamily="Comic Sans MS" FontSize="20"/>
<StackPanel HorizontalAlignment="Left" Margin="82,0,0,0" Width="Auto" Orientation="Horizontal" VerticalAlignment="Center">
<Button Content="Start" d:LayoutOverrides="Width" x:Name="_startButton" Foreground="#FFFFFFFF" Template="{StaticResource ButtonControlTemplate1}" FontSize="8"/>
<Button Margin="4,0,0,0" x:Name="_optionsButton" Foreground="#FFFFFFFF" Template="{StaticResource ButtonControlTemplate1}" Content="Optionen" FontSize="8"/>
<Button x:Name="_highScoreButton" Foreground="#FFFFFFFF" Template="{StaticResource ButtonControlTemplate1}" Content="Highscore" Height="24" Width="66" FontSize="8"/>
</StackPanel>
<Grid x:Name="_optionsGrid" Grid.RowSpan="2" Background="#7F000000">
<Bubbles_Controls:Options Height="Auto" Margin="0,0,0,0" x:Name="_options" VerticalAlignment="Stretch"/>
<Grid x:Name="_highScoreGrid" Background="#7F000000" Grid.RowSpan="2">
<Bubbles_Controls:HighScore HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="_highScore" Margin="20,20,20,20"/>
<Grid Height="Auto" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch" Width="Auto" Grid.Row="0" x:Name="_nameGrid" Grid.RowSpan="2" Background="#7F000000">
<Bubbles_Controls:EnterNameControl BorderThickness="0,0,0,0" Margin="0,0,0,0" d:LayoutOverrides="Height" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="_nameControl"/>
Plus die Codebehindklasse (Page.xaml.cs)
namespace Bubbles
public partial class Page : UserControl
public Page()
_playground.GameFinished += new EventHandler(_playground_GameFinished);
_playground.BubblesRemoved += new BubblesRemovedEventHandler(_playground_BubblesRemoved);
_startButton.Click += new RoutedEventHandler(_startButton_Click);
_optionsButton.Click += new RoutedEventHandler(_optionsButton_Click);
_options.Closed += new EventHandler(_options_Closed);
_highScore.Closed += new EventHandler(_highScore_Closed);
_highScoreButton.Click += new RoutedEventHandler(_highScoreButton_Click);
_nameControl.NameSaved += new Bubbles.Controls.SaveNameEventHandler(_nameControl_NameSaved);
if (!DesignerProperties.GetIsInDesignMode(this))
_optionsGrid.Visibility = Visibility.Collapsed;
_highScoreGrid.Visibility = Visibility.Collapsed;
_nameGrid.Visibility = Visibility.Collapsed;
// Init Trick
void _nameControl_NameSaved(object sender, Bubbles.Controls.SaveNameEventArgs args)
HighScores.AddHighscore(args.Name, _points.Text, Settings.Level);
_highScoreGrid.Visibility = Visibility.Visible;
_highScore.ShowHighscore();
void _highScore_Closed(object sender, EventArgs e)
void _highScoreButton_Click(object sender, RoutedEventArgs e)
void _options_Closed(object sender, EventArgs e)
void _optionsButton_Click(object sender, RoutedEventArgs e)
_optionsGrid.Visibility = Visibility.Visible;
void _playground_BubblesRemoved(object sender, BubblesEventArgs args)
UpdatePoints(args.BubbleCount);
private void UpdatePoints(int bubbles)
int points = int.Parse(_points.Text);
points += bubbles * bubbles;
_points.Text = points.ToString();
void _startButton_Click(object sender, RoutedEventArgs e)
StartGame();
private void StartGame()
_playground.Opacity = 1;
_points.Text = "0";
_playground.Start();
void _playground_GameFinished(object sender, EventArgs e)
_playground.Opacity = .5;
_nameGrid.Visibility = Visibility.Visible;
Es gehören noch einige Elemente mehr hinzu, wie z.B. Highscores, Namenseingabe und das Setzen des Spiellevels über die Optionen.