Contents
This is my first Silverlight game. Some months ago, I had the idea to create something related to the ongoing 2010 FIFA World Cup, but for some time, I was asking myself what could be done? Fortunately, my childhood memories helped me to answer this question.
This game is a product of hours of hard work, mixing both study and trial-and-error approaches. Fortunately, when you really put focus on something, the learning curve starts getting easier. When the road is paved, you are ready to run.
In Brazil, there is a very well-known game named "Futebol de Botão" (Button Football), or "Futebol de Mesa" (Table Football), which deserves at least a brief note.
In the beginning of the 20th century, the boys in Brazil, just like in many other parts of the world, were big fans of football. There were football clubs everywhere, and the radio broadcasting helped spread this entertainment throughout Brazil. According to Wikipedia, someone had this idea to create a table football, where the field was any smooth flat surface, and the players were buttons positioned on a table. The buttons were the large ones, taken from clothes. Much for the despair of the poor mothers of that time, who had to sew new buttons, the table football became one of the most popular games among Brazilian children (specially when they were grounded and forbidden to play real football outdoors...)
Besides the use of clothing buttons, people tried other materials, such as pieces made of coconut shells and bones. The goalkeepers were just matchboxes filled in by sand or some heavy material. But finally, the industry started making buttons from plastic materials, which became the standard for commercial versions of the game.
Since most kids today prefer computer games, in this article, I tried to reproduce the feeling and atmosphere of those plastic buttons.
You can have a quick overview of the game by taking a look at the YouTube video I uploaded, in the link below:
In order to get the game to work, you can download the following, if you don't have VS 2008 and Silverlight 3:
Figure 1: Solution structure
The Visual Studio 2008 solution is made up mainly by the Soccerlight project, which contains the Silverlight logic itself, and other helper projects, as we can see in the following table.
Project | Description |
Silverlight | This is the Silverlight project itself. |
Silverlight.Controls | This Silverlight Class Library project that holds some of the custom controls I use in the project. |
Silverlight.Core | This project holds some of the game logic and the model classes. |
Silverlight.Web | This project is the start up project that contains the application's entry page. |
The Intro menu consists of a table containing all of the 8 groups which played in the 2010 FIFA World Cup.
The idea is that you pick up a national team and start playing with that team. Then you will have to follow exactly the same games as they were scheduled in the 2010 World Cup.
For the implementation of this teams table, I didn't use much of XAML. I did it mostly programmatically. What you see in the image below is made up of Grid
, TextBlock
, and Image
elements. I know I might have used a ListBox
element with a custom template, but... I wanted more freedom than that. By constructing the visual elements programmatically, I feel that I can have much more control over animations, transforms, etc.
Figure 2: Intro menu
The following function GenerateGroups
creates all the 32 teams divided into the 8 groups:
void GenerateGroups()
{
for(int i = 0; i < 8; i++)
{
Border brd = new Border();
brd.CornerRadius = new CornerRadius(5);
brd.Margin = new Thickness(2);
brd.SetValue(Grid.ColumnProperty, i % 4);
brd.SetValue(Grid.RowProperty, i / 4);
LinearGradientBrush lgb = new LinearGradientBrush();
lgb.StartPoint = new Point(0, 0);
lgb.EndPoint = new Point(1, 1);
lgb.GradientStops = new GradientStopCollection();
lgb.GradientStops.Add(new GradientStop()
{ Offset = 0.0, Color = Color.FromArgb(255, 0, 0, 0)});
lgb.GradientStops.Add(new GradientStop()
{ Offset = 0.5, Color = Color.FromArgb(255, 30, 30, 30)});
lgb.GradientStops.Add(new GradientStop()
{ Offset = 1.0, Color = Color.FromArgb(255, 40, 40, 40)});
brd.Background = lgb;
lgbEven.StartPoint = new Point(0, 0);
lgbEven.EndPoint = new Point(1, 1);
lgbEven.GradientStops = new GradientStopCollection();
lgbEven.GradientStops.Add(new GradientStop()
{ Offset = 0.0, Color = Color.FromArgb(255, 0, 0, 0) });
lgbEven.GradientStops.Add(new GradientStop()
{ Offset = 0.5, Color = Color.FromArgb(255, 30, 30, 30) });
lgbEven.GradientStops.Add(new GradientStop()
{ Offset = 1.0, Color = Color.FromArgb(255, 80, 80, 80) });
Grid grdGroup = new Grid();
grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(28) });
grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
grdGroup.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
grdGroup.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(18) });
TextBlock txtGroupID = new TextBlock()
{
Text = ((char)(i + 65)).ToString(),
Foreground = new SolidColorBrush(Colors.White),
FontSize = 18,
FontWeight = FontWeights.Bold
};
txtGroupID.SetValue(Grid.ColumnProperty, 0);
txtGroupID.SetValue(Grid.RowProperty, 0);
txtGroupID.SetValue(Grid.RowSpanProperty, 4);
txtGroupID.SetValue(VerticalAlignmentProperty, VerticalAlignment.Center);
txtGroupID.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Center);
grdGroup.Children.Add(txtGroupID);
for (int j = 0; j < 4; j++)
{
Grid grdTeam = new Grid();
grdTeam.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
grdTeam.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
grdTeam.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(25) });
grdTeam.SetValue(Grid.ColumnProperty, 1);
grdTeam.SetValue(Grid.ColumnSpanProperty, 2);
grdTeam.SetValue(Grid.RowProperty, j);
grdTeam.HorizontalAlignment = HorizontalAlignment.Stretch;
grdTeam.VerticalAlignment = VerticalAlignment.Center;
grdTeam.Width = 120;
Team team =
GameHelper.Instance.TeamsDictionary[GameHelper.Instance.TeamCodes[i * 4 + j]];
Image img = new Image()
{
Source = new BitmapImage(new Uri(string.Format(
@"http://www.fifa.com/imgml/flags/reflected/m/{0}.png",
team.TeamID.ToLower()), UriKind.Absolute)),
Width = 28.5,
Height = 25.0,
VerticalAlignment = VerticalAlignment.Center
};
img.SetValue(Grid.ColumnProperty, 0);
img.SetValue(Grid.RowProperty, j);
img.VerticalAlignment = VerticalAlignment.Top;
img.HorizontalAlignment = HorizontalAlignment.Stretch;
img.Clip = new RectangleGeometry() { Rect =
new Rect(new Point(0, 0), new Point(28.5, 14)) };
img.Tag = team.TeamID;
TranslateTransform tf = new TranslateTransform()
{
X = 0,
Y = 6
};
img.RenderTransform = tf;
TextBlock txt = new TextBlock()
{
Text = team.TeamName,
Foreground = new SolidColorBrush(Colors.White)
};
txt.SetValue(Grid.ColumnProperty, 1);
txt.SetValue(Grid.RowProperty, j);
txt.VerticalAlignment = VerticalAlignment.Center;
txt.HorizontalAlignment = HorizontalAlignment.Stretch;
txt.Tag = team.TeamID;
grdTeam.Tag = team.TeamID;
grdTeam.Children.Add(img);
grdTeam.Children.Add(txt);
grdTeam.MouseEnter += new MouseEventHandler(team_MouseEnter);
grdTeam.MouseLeave += new MouseEventHandler(team_MouseLeave);
grdTeam.MouseLeftButtonUp +=
new MouseButtonEventHandler(team_MouseLeftButtonUp);
grdGroup.Children.Add(grdTeam);
}
brd.Child = grdGroup;
grdGroupsContainer.Children.Add(brd);
}
}
Figure 3: The GenerateGroups() function in the IntroMenu.xaml.cs file
If you have chosen England as your team in the Intro menu, you would next be redirected to the following view, which is, of course, the first game of England in the World Cup (England vs. USA, 06/12, in Rustenburg).
Notice that both teams start with the traditional 3-5-2 formation, being 3 fullbacks, 5 midfielders, and 2 forwards. Although this is the default formation for all teams in the game, it wouldn't be so hard to select another formation for the selected teams, since the formation is simply an array of integers:
public Team()
{
Formation = new int[] {1, 3, 5, 2};
}
Figure 4: Team class constructor, showing how the 3-5-2 formation is set up for every team.
Notice that the array in the code snippet above starts with 1. This is so because we have to take the goalkeeper into consideration.
Figure 5: Soccerlight table, showing the 3-5-2 formations of England vs. USA
Notice in the listing below that the field lines and circles are not really images, but instead are made up of Border
and Ellipse
elements. Notice also that at the four corners, there are quarter-of-circles, which can easily be created by clipping the ordinary circles (see the Ellipse.Clip
tags below) using RectangleGeometry
to show only the quarter you want.
<Grid x:Name="LayoutRoot"
MouseLeftButtonUp="LayoutRoot_MouseLeftButtonUp"
VerticalAlignment="Center" ...
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="colLeftEscapeArea" Width="53"/>
<ColumnDefinition x:Name="colLeftPosts" Width="53"/>
<ColumnDefinition x:Name="colLeftGoalArea" Width="70"/>
<ColumnDefinition x:Name="colLeftPenaltyArea" Width="280"/>
<ColumnDefinition x:Name="colHalfWay" Width="280"/>
<ColumnDefinition x:Name="colRightPenaltyArea" Width="70"/>
<ColumnDefinition x:Name="colRightGoalArea" Width="53"/>
<ColumnDefinition x:Name="colRightPosts" Width="53"/>
<ColumnDefinition x:Name="colMenu" Width="32"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="rowTopEscapeArea" Height="50"/>
<RowDefinition x:Name="rowTopFieldLine" Height="108"/>
<RowDefinition x:Name="rowTopPenaltyArea" Height="67"/>
<RowDefinition x:Name="rowTopGoalArea" Height="153"/>
<RowDefinition x:Name="rowBottomGoalArea" Height="67"/>
<RowDefinition x:Name="rowBottomPenaltyArea" Height="108"/>
<RowDefinition x:Name="rowBottomFieldLine" Height="50"/>
</Grid.RowDefinitions>
<Border Grid.Column="0" Grid.Row="0"
Grid.ColumnSpan="8" Grid.RowSpan="1"
Background="DarkGreen" ...
<Border Grid.Column="0" Grid.Row="1"
Grid.ColumnSpan="1" Grid.RowSpan="5" Background="DarkGreen" ...
<Border Grid.Column="1" Grid.Row="1"
Grid.ColumnSpan="3" Grid.RowSpan="5" BorderBrush="White" ...
<Border Grid.Column="4" Grid.Row="1"
Grid.ColumnSpan="3" Grid.RowSpan="5" BorderBrush="White" ...
<Border Grid.Column="1" Grid.Row="3"
Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
<Border Grid.Column="6" Grid.Row="3"
Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
<Border Grid.Column="1" Grid.Row="2"
Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
<Border Grid.Column="1" Grid.Row="2"
Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
<Border Grid.Column="5" Grid.Row="2"
Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="White" ...
<Border Grid.Column="7" Grid.Row="1"
Grid.ColumnSpan="1" Grid.RowSpan="5" Background="DarkGreen" ...
<Border Grid.Column="0" Grid.Row="6"
Grid.ColumnSpan="8" Grid.RowSpan="1" Background="DarkGreen" ...
<StackPanel Grid.Column="1" Grid.Row="1"
Grid.ColumnSpan="6" Grid.RowSpan="5" Margin="8, 8, 0, 0" ...
<Image Source="../Images/Soccerlight.png" Stretch="UniformToFill">
<Image.RenderTransform>
<ScaleTransform ScaleX="0.25" ScaleY="0.25"/>
</Image.RenderTransform>
</Image>
</StackPanel>
<Grid Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="6" Grid.RowSpan="5" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="92"/>
<ColumnDefinition Width="115"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="115"/>
<ColumnDefinition Width="92"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
<RowDefinition Height="15"/>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" StrokeThickness="2" Stroke="White">
<Ellipse.Clip>
<RectangleGeometry Rect="15,15,30,30"/>
</Ellipse.Clip>
<Ellipse.RenderTransform>
<TranslateTransform X="-15" Y="-15"/>
</Ellipse.RenderTransform>
</Ellipse>
<Ellipse Grid.Column="10" Grid.Row="0" StrokeThickness="2" Stroke="White">
<Ellipse.Clip>
<RectangleGeometry Rect="0,15,15,15"/>
</Ellipse.Clip>
<Ellipse.RenderTransform>
<TranslateTransform X="15" Y="-15"/>
</Ellipse.RenderTransform>
</Ellipse>
<Ellipse Grid.Column="0" Grid.Row="6" StrokeThickness="2" Stroke="White">
<Ellipse.Clip>
<RectangleGeometry Rect="15,0,15,15"/>
</Ellipse.Clip>
<Ellipse.RenderTransform>
<TranslateTransform X="-15" Y="15"/>
</Ellipse.RenderTransform>
</Ellipse>
<Ellipse Grid.Column="10" Grid.Row="6" StrokeThickness="2" Stroke="White">
<Ellipse.Clip>
<RectangleGeometry Rect="0,0,15,15"/>
</Ellipse.Clip>
<Ellipse.RenderTransform>
<TranslateTransform X="15" Y="15"/>
</Ellipse.RenderTransform>
</Ellipse>
<Ellipse Grid.Column="4" Grid.Row="2" Grid.ColumnSpan="3" Grid.RowSpan="3"
StrokeThickness="2" Stroke="White"/>
<Ellipse Grid.Column="5" Grid.Row="3" Fill="White"/>
<Ellipse Grid.Column="2" Grid.Row="2" Grid.ColumnSpan="1" Grid.RowSpan="3"
StrokeThickness="2" Stroke="White">
<Ellipse.Clip>
<RectangleGeometry Rect="57.5,0,115,115"/>
</Ellipse.Clip>
<Ellipse.RenderTransform>
<TranslateTransform X="-57.5" Y="0"/>
</Ellipse.RenderTransform>
</Ellipse>
<Ellipse Grid.Column="8" Grid.Row="2" Grid.ColumnSpan="1"
Grid.RowSpan="3" StrokeThickness="2" Stroke="White">
<Ellipse.Clip>
<RectangleGeometry Rect="0,0,57.5,115"/>
</Ellipse.Clip>
<Ellipse.RenderTransform>
<TranslateTransform X="57.5" Y="0"/>
</Ellipse.RenderTransform>
</Ellipse>
</Grid>
<Canvas x:Name="rootCanvas"/>
<Border Grid.Column="0" Grid.Row="3"
Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
<Grid>
<Grid.Background>
<ImageBrush ImageSource="../Images/goalnet.png" Stretch="UniformToFill"/>
</Grid.Background>
</Grid>
</Border>
<Border Grid.Column="7" Grid.Row="3"
Grid.ColumnSpan="1" Grid.RowSpan="1" BorderBrush="White" ...
<Grid>
<Grid.Background>
<ImageBrush ImageSource="../Images/goalnet.png" Stretch="UniformToFill"/>
</Grid.Background>
</Grid>
</Border>
<Border x:Name="brdBallStrengthContainer"
Grid.Column="8" Grid.Row="4" Grid.ColumnSpan="1" ...
<Grid>
<Border x:Name="brdStrength" CornerRadius="4" Margin="8,56,8,8">
<Border.Background>
<LinearGradientBrush>
<GradientStop Color="Red" Offset="0.0"/>
<GradientStop Color="Orange" Offset="0.5"/>
<GradientStop Color="Yellow" Offset="1.0"/>
</LinearGradientBrush>
</Border.Background>
</Border>
<Image x:Name="imgBallStrength" Source="../Images/Jabulani.png"
VerticalAlignment="Top" Margin="0,32,0,0">
<Image.RenderTransform>
<RotateTransform x:Name="rtBallStrength"
CenterX="11.5" CenterY="11.5" Angle="30">
</RotateTransform>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
<Grid x:Name="grdStadiumScreen" MaxWidth="800"
MaxHeight="180" Grid.Column="1" Grid.Row="0"...
<Grid.Clip>
<RectangleGeometry Rect="0,0,800,180"/>
</Grid.Clip>
<TextBlock Foreground="White" FontSize="120"
Text="GOALLLLLL!!!" TextAlignment="Center" Margin="0">
<TextBlock.RenderTransform>
<TranslateTransform x:Name="lettersXTranslate" X="0"/>
</TextBlock.RenderTransform>
</TextBlock>
<Image Source="../Images/mask.png" Stretch="Fill" Opacity="0.75"/>
<Grid x:Name="grdBrightness" Background="Black" Opacity="1.00"/>
</Grid>
<Grid.RenderTransform>
<ScaleTransform ScaleX="0.868" ScaleY="0.8700"/>
</Grid.RenderTransform>
</Grid>
The score control shows the elapsed time, the team nicknames, a blinking ball indicating the playing team, and... the scores.
The elapsed time is controlled by a DispatcherTimer
object, which triggers a Tick
event every second. This is propagated to the score control to increase the total elapsed time.
In real soccer games, there are two halves of 45 minutes each, separated by a 15-minute break. In Soccerlight, however, the game is over when the timer reaches 30 minutes.
The blinking ball is a useful indicator to show which team is playing at a given moment.
Figure 6: The score control
Each goal is delimited by three borders: two at both sides, and one at the back of the goal. If the ball strikes these borders, it will bounce as if it was hitting a wall. Besides, at both sides in front of the goal, there are goalposts, which behave like real cylindrical goalposts, making the ball bounce as if it was hitting a circular object.
Besides, the goal has a transparent goal net, which gives the game a cool and realistic look and feel.
The rule is simple as that: whenever the ball enters the goal, the application updates the score control to increment the score of the attacking team.
Figure 7: The goal: notice the borders, the goal posts, and the detailed goal net
Figure 8: The Jabulani ball
As some of the readers may know, Jabulani is the innovative official ball in the FIFA World Cup, so I decided to use it too in Soccerlight World Cup.
Some players have complained about Jabulani in FIFA Cup, because they have reported "strange" behavior and unpredicted direction changing, while other players have declared their love for the ball. (On the other side, no Soccerlight player has complained about the ball yet...)
In Soccerlight, the physics calculations involving the ball are no different from those involving the players (except for the the fact that players have greater diameters and greater friction coefficients). In fact, both ball and players inherit from the same base class, Discoid
:
public class Ball : Discoid
{
public class Player : Discoid
{
Each team has a set of 11 players. In the real world, each player may have different roles, but in Soccerlight, they are treated in the same manner. The only difference between a striker and a goalkeeper, for example, is the fact that the goalkeeper is closer to the goal and the striker's initial position is closer to the opponent's goal. Maybe in future versions I will define different skills, such as, for example, a skilled striker might have more precision when shooting to goal than other average players.
Figure 9: Players caught in action: say "cheese"...
Whenever you select a player, the application will show a Player Info Bar. This bar contains basic info about the player: the number, the name, and the picture.
Each Soccerlight player has a number, a name, and a picture. Numbers and names are hard-coded in the application, but the pictures are taken from the FIFA website, as shown in the code snippet below:
imgPlayer.ImageSource = new BitmapImage(new Uri(string.Format(
"http://pt.fifa.com/imgml/tournament/worldcup2010/players/xl/{0}.png",
teamPlayer.ID), UriKind.Absolute));
The listing below is responsible for showing the Player Info Bar:
<Grid x:Name="grdPlayerInfo" HorizontalAlignment="Center"
VerticalAlignment="Bottom" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Border Margin="8,8,0,64" CornerRadius="8"
Width="75" Height="100"
Background="#C0000000">
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform X="4" Y="4"/>
</TransformGroup>
</Border.RenderTransform>
</Border>
<Border Margin="8,8,0,64" CornerRadius="8"
Width="75" Height="100">
<Border.Background>
<RadialGradientBrush>
<GradientStop Offset="0.0" Color="#00000000"/>
<GradientStop Offset="9.0" Color="#20000000"/>
<GradientStop Offset="1.0" Color="#40000000"/>
</RadialGradientBrush>
</Border.Background>
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform X="4" Y="4"/>
</TransformGroup>
</Border.RenderTransform>
</Border>
<Border Margin="8,8,0,64" CornerRadius="8"
Width="75" Height="100">
<Border.Background>
<ImageBrush x:Name="imgPlayer"/>
</Border.Background>
</Border>
<StackPanel Grid.Column="2" VerticalAlignment="Center" Margin="0,0,0,64">
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="4">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0.0" Color="Yellow"/>
<GradientStop Offset="0.5" Color="Yellow"/>
<GradientStop Offset="0.5" Color="Gold"/>
<GradientStop Offset="1.0" Color="Goldenrod"/>
</LinearGradientBrush>
</Border.Background>
<Border.RenderTransform>
<SkewTransform AngleX="-10"/>
</Border.RenderTransform>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="numPlayer" Text="" Foreground="Black"
FontSize="22" FontWeight="Bold" Margin="32,0,0,0"/>
<TextBlock Text=" - " Foreground="Black" FontSize="22"
FontWeight="Bold"/>
<TextBlock x:Name="txtPlayerName" Text="" Foreground="Black"
FontSize="22" FontWeight="Bold" Margin="0,0,0,0"/>
</StackPanel>
</Border>
</StackPanel>
</Grid>
Figure 10: Player Info Bar
The Strength Control Bar is the means that you have to calibrate your shot strength. The higher you put the Jabulani ball in the bar, the stronger you shoot the ball.
Figure 11: Strength Control Bar
The strength is redefined whenever the user clicks in the Strength Control Bar. This is done by the following code:
private void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Point point = e.GetPosition(rootCanvas);
if ((point.X > strengthPointNW.X) &&
(point.X < strengthPointSE.X) &&
(point.Y > strengthPointNW.Y) &&
(point.Y < strengthPointSE.Y))
{
e.Handled = true;
double relativeY = strengthPointSE.Y - point.Y;
currentGame.Team1BallStrength = ((strengthPointSE.Y - point.Y) /
(strengthPointSE.Y - strengthPointNW.Y)) * 100.0;
imgBallStrength.Margin = new Thickness(0, point.Y -
strengthPointNW.Y - imgBallStrength.ActualHeight / 2.0, 0, 0);
brdStrength.Margin = new Thickness(8, point.Y -
strengthPointNW.Y + imgBallStrength.ActualHeight / 2.0, 8, 8);
}
The role of the Stadium Screen is just to show that a team has scored the goal. Simple as that.
Figure 12: Stadium Screen
The Stadium Screen has three different animations. First, there is the translateAnimation
, which translates the "GOOAL" string from right to left. Then there is the lettersOpacityAnimation
that fades in and fades out the letters of the screen. And finally, there is the screenOpacityAnimation
, responsible for fading out the whole Stadium Screen element:
sbStadiumScreen = new Storyboard()
{
Duration = new Duration(new TimeSpan(0, 0, 0, 10))
};
DoubleAnimation translateAnimation = new DoubleAnimation()
{
From = 800,
To = 0,
Duration = new Duration(new TimeSpan(0, 0, 0, 3))
};
Storyboard.SetTarget(translateAnimation, lettersXTranslate);
Storyboard.SetTargetProperty(translateAnimation, new PropertyPath("X"));
DoubleAnimation lettersOpacityAnimation = new DoubleAnimation()
{
From = 0.8,
To = 1.0,
Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500)),
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTarget(lettersOpacityAnimation, grdBrightness);
Storyboard.SetTargetProperty(lettersOpacityAnimation, new PropertyPath("Opacity"));
DoubleAnimation screenOpacityAnimation = new DoubleAnimation()
{
From = 1.0,
To = 0.0,
BeginTime = new TimeSpan(0, 0, 0, 0),
Duration = new Duration(new TimeSpan(0, 0, 0, 4))
};
sbStadiumScreen.Children.Add(translateAnimation);
sbStadiumScreen.Children.Add(lettersOpacityAnimation);
sbStadiumScreen.Children.Add(screenOpacityAnimation);
Storyboard.SetTarget(screenOpacityAnimation, grdStadiumScreen);
Storyboard.SetTargetProperty(screenOpacityAnimation, new PropertyPath("Opacity"));
Some issues with the game, and some desirable improvements which I'm willing to be correcting/implementing in future releases:
- The game still doesn't handle fouls. At first, this seems easy to implement, but depending on where the foul occurred, after the foul, you might have to rearrange the players so that they don't collide with each other, etc.
- There are still no games for the knockout stage. Maybe I'll generate random results for the other matches, and if your team keeps winning, you'll advance from the round of 16 to the quarter-of-finals, semi-finals, and finally to the final match.
- It would be very cool to implement an online, service-based, human-to-human version of the game. A WCF Service will do, I think.
If you reached this line, I'd like to thank you for your patience. Please tell me what you liked or disliked in the application. Criticism and suggestions are welcome, and I'm willing to improve this article and application based on your feedback.
History
- 2010-06-29: First version.