Silverlight 2 – Minigame

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 :-)

image

Die Live Version für Silverlight 2 kann man hier finden:
https://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
     {
         get 
         {
             if (HtmlPage.IsEnabled == false)
             {
                 return _level;
             }
  
             if (IsolatedStorageSettings.ApplicationSettings.Contains("Level"))
             {
                 try
                 {
                     GameLevel level = (GameLevel)IsolatedStorageSettings.ApplicationSettings["Level"];
                     return level;
                 }
                 catch
                 {
                     return _level;
                 }
             }
             return _level; 
         }
         set
         {
             switch (value)
             {
                 case GameLevel.Kindergarten:
                     DimensionX = 6;
                     DimensionY = 6;
                     break;
  
                 case GameLevel.Hammer:
                     DimensionX = 20;
                     DimensionY = 20;
                     break;
  
                 case GameLevel.Normal:
                 default:
                     DimensionX = 12;
                     DimensionY = 12;
                     break;
                     
             }
             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="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
     Width="Auto" Height="Auto" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://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*"/>
             <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
         {
             get
             {
                 _bubbleColor = _g0.Color;
                 return _bubbleColor;
             }
             set
             {
                 _bubbleColor = value;
                 _g0.Color = _bubbleColor;
             }
         }
  
  
         internal void Remove()
         {
             //throw new NotImplementedException();
         }
     }
 }

Die eigentliche Spielfläche (Playground.cs)

 using System;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
 using System.Collections.Generic;
 using System.ComponentModel;
  
 namespace Bubbles.Controls
 {
     public partial class PlayGround : UserControl
     {
         Bubble[,] _bubbleArea;
         Grid _grid;
         MediaElement _sound;
  
         public PlayGround()
         {
             InitializeComponent();
  
             // 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);
         }
  
  
         #endregion
  
         public void Start()
         {
             CreatePlayground();
         }
  
         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);
             }
  
             #endregion
  
             #region Create Bubbles
  
             // Create Bubble
             _bubbleArea = new Bubble[Settings.DimensionX, Settings.DimensionY];
             Random rnd = new Random();
             for (int x = 0; x < Settings.DimensionX; x++)
             {
                 for (int y = 0; y < Settings.DimensionY; y++)
                 {
                     // 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;
                             break;
  
                         case 1:
                             color = Colors.Red;
                             break;
  
                         case 2:
                             color = Colors.Yellow;
                             break;
  
                         case 3:
                             color = Colors.Green;
                             break;
  
                         case 4:
                             color = Colors.Orange;
                             break;
  
                         default:
                             color = Colors.Black;
                             break;
                     }
                     bubble.BubbleColor = color;
                     _bubbleArea[x, y] = bubble;
                 }
             }
  
             #endregion
  
             #region Display Bubbles
  
             // Display Bubbles
             for (int x = 0; x < Settings.DimensionX; x++)
             {
                 for (int y = 0; y < Settings.DimensionY; y++)
                 {
                     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);
                 }
             }
  
             #endregion
  
             #region Add Sound
  
             _sound = new MediaElement();
             _sound.Source = new Uri("bubblepop.wma", UriKind.Relative);
             _sound.AutoPlay = false;
             _sound.Volume = 1;
             _grid.Children.Add(_sound);
  
             #endregion 
         }
  
         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)
             {
                 return;
             }
  
             _sound.Stop();
  
             if (BubblesRemoved != null)
             {
                 BubblesEventArgs args = new BubblesEventArgs();
                 args.BubbleCount = surroundingBubbles.Count;
                 BubblesRemoved(this, args);
             }
  
             _sound.Play();
             
             foreach (Bubble b in surroundingBubbles)
             {
                 for (int x = 0; x < Settings.DimensionX; x++)
                 {
                     for (int y = 0; y < Settings.DimensionY; y++)
                     {
                         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 x = 0; x < Settings.DimensionX; x++)
             {
                 for (int i = 0; i < Settings.DimensionY - 1; i++)
                 {
                     for (int y = Settings.DimensionY - 2; y >= 0; y--)
                     {
                         Bubble bubble = _bubbleArea[x, y];
  
                         if (_bubbleArea[x, y + 1] == null)
                         {
                             // Move Bubble down
                             _bubbleArea[x, y + 1] = _bubbleArea[x, y];
                             _bubbleArea[x, y] = null;
                         }
                     }
                 }
             }
             BubblesToTheRight();
         }
  
         private void BubblesToTheRight()
         {
             for (int y = 0; y < Settings.DimensionY; y++)
             {
                 for (int i = 0; i < Settings.DimensionY - 1; i++)
                     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];
                                 _bubbleArea[x, y] = null;
                             }
                         }
                     }
             }
  
             UpdateBubbles();
         }
  
         private void UpdateBubbles()
         {
             // Display Bubbles
             for (int x = 0; x < Settings.DimensionX; x++)
             {
                 for (int y = 0; y < Settings.DimensionY; y++)
                 {
                     Bubble bubble = _bubbleArea[x, y];
                     if (bubble != null)
                     {
                         bubble.SetValue(Grid.ColumnProperty, x);
                         bubble.SetValue(Grid.RowProperty, y);
                     }
                 }
             }
             CheckForEnd();
         }
  
         private void CheckForEnd()
         {
             bool finished = true;
             for (int x = 0; x < Settings.DimensionX; x++)
             {
                 for (int y = 0; y < Settings.DimensionY; y++)
                 {
                     Bubble bubble = _bubbleArea[x, y];
  
                     List<Bubble> surroundingBubbles = new List<Bubble>();
                     GetSurroundingBubbles(bubble, surroundingBubbles);
  
                     if (surroundingBubbles.Count > 0)
                     {
                         finished = false;
                         break;
                     }
                 }
             }
             if (finished)
             {
                 if (GameFinished != null)
                 {
                     GameFinished(this, EventArgs.Empty);
                 }
             }
         }
  
         private void GetSurroundingBubbles(Bubble bubble, List<Bubble> surroundingBubbles)
         {
             if (bubble == null)
             {
                 return;
             }
  
             int bubbleX = -1;
             int bubbleY = -1;
  
             // Display Bubbles
             for (int x = 0; x < Settings.DimensionX; x++)
             {
                 for (int y = 0; y < Settings.DimensionY; y++)
                 {
                     if (_bubbleArea[x, y] == bubble)
                     {
                         bubbleX = x;
                         bubbleY = y;
                         break;
                     }
                 }
             }
  
             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];
                 CheckBubble(bubbleToCheck, bubble, surroundingBubbles);
             }
  
             // Top Bubble
             if (bubbleX != -1 && bubbleY > 0)
             {
                 bubbleToCheck = _bubbleArea[bubbleX, bubbleY - 1];
                 CheckBubble(bubbleToCheck, bubble, surroundingBubbles);
             }
  
             // Bottom Bubble
             if (bubbleX != -1 && bubbleY < Settings.DimensionY - 1)
             {
                 bubbleToCheck = _bubbleArea[bubbleX, bubbleY + 1];
                 CheckBubble(bubbleToCheck, bubble, surroundingBubbles);
             }
         }
  
         private void CheckBubble(Bubble bubbleToCheck, Bubble bubble, List<Bubble> surroundingBubbles)
         {
             if (bubbleToCheck == null)
             {
                 return;
             }
  
             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="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:Bubbles_Controls="clr-namespace:Bubbles.Controls" 
     xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="https://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"/>
             </LinearGradientBrush>
         </Grid.Background>
  
         <Grid.RowDefinitions>
             <RowDefinition Height="36"/>
             <RowDefinition Height="*"/>
         </Grid.RowDefinitions>
  
         <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>
         <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>
         <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"/>
         </Grid>
  
     </Grid>
 </UserControl>

Plus die Codebehindklasse (Page.xaml.cs)

 using System;
 using System.Windows;
 using System.Windows.Controls;
 using System.ComponentModel;
  
 namespace Bubbles
 {
     public partial class Page : UserControl
     {
         public Page()
         {
             InitializeComponent();
  
             _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
                 Settings.Level = Settings.Level;
             }
         }
  
         void _nameControl_NameSaved(object sender, Bubbles.Controls.SaveNameEventArgs args)
         {
             HighScores.AddHighscore(args.Name, _points.Text, Settings.Level);
             _nameGrid.Visibility = Visibility.Collapsed;
             _highScoreGrid.Visibility = Visibility.Visible;
             _highScore.ShowHighscore();
         }
  
         void _highScore_Closed(object sender, EventArgs e)
         {
             _highScoreGrid.Visibility = Visibility.Collapsed;
         }
  
         void _highScoreButton_Click(object sender, RoutedEventArgs e)
         {
             _highScoreGrid.Visibility = Visibility.Visible;
         }
  
         void _options_Closed(object sender, EventArgs e)
         {
             _optionsGrid.Visibility = Visibility.Collapsed;
         }
  
         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.