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:
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
    {
        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="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*"/>
            <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="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    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"/>
            </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.