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