Soccerlight 世界杯 2010






4.96/5 (78投票s)
一款使用 Silverlight 和 VS 2008 开发的足球游戏。
目录
- 引言
- 背景
- 系统要求
- Soccerlight 解决方案
- 主菜单
- Soccerlight 比赛场地
- 得分控制
- 球门
- Jabulani
- 参与者
- 球员信息栏
- 力量控制条
- 体育场屏幕
- 愿望清单 / 已知问题
- 最终思考
引言
这是我的第一个 Silverlight 游戏。几个月前,我萌生了制作与正在进行的 2010 年 FIFA 世界杯相关的东西的想法,但有一段时间,我一直在问自己能做什么?幸运的是,我童年的回忆帮助我回答了这个问题。
这款游戏是无数辛勤工作的结晶,融合了学习和试错的方法。幸运的是,当你真正专注于某件事时,学习曲线就开始变得容易。当道路铺平了,你就可以奔跑了。
背景
在巴西,有一个非常著名的游戏叫做“Futebol de Botão”(按钮足球),或者“Futebol de Mesa”(桌面足球),值得一提。
20 世纪初,巴西的男孩们,就像世界上许多其他地方的孩子一样,都是足球的狂热爱好者。到处都是足球俱乐部,广播播放着足球比赛,将这项娱乐传播到整个巴西。根据维基百科的说法,有人想出了这个主意,制作一个桌面足球游戏,场地可以是任何光滑的平面,球员就是放在桌子上的纽扣。这些纽扣是衣服上用的那种大纽扣。让当时的穷妈妈们头疼不已,不得不缝上新的纽扣,桌面足球成为了巴西儿童中最流行的游戏之一(尤其是当他们被禁止在户外踢真足球的时候……)
除了使用衣物纽扣,人们还尝试过其他材料,例如用椰子壳和骨头制成的部件。守门员只是用沙子或重物填充的火柴盒。但最终,工业界开始用塑料材料制作纽扣,这成为了该游戏的商业版本的标准。
鉴于如今大多数孩子更喜欢电脑游戏,在本文中,我试图重现那些塑料纽扣的感觉和氛围。
您可以通过查看我上传到下面链接的 YouTube 视频来快速了解游戏。
系统要求
要使游戏正常工作,如果您还没有 VS 2008 和 Silverlight 3,可以下载以下内容。
Soccerlight 解决方案
Visual Studio 2008 解决方案主要由 Soccerlight 项目组成,其中包含 Silverlight 本身以及其他辅助项目,如下表所示。
项目 | 描述 |
Silverlight | 这是 Silverlight 项目本身。 |
Silverlight.Controls | 这个 Silverlight 类库项目包含我在项目中使用的部分自定义控件。 |
Silverlight.Core | 这个项目包含部分游戏逻辑和模型类。 |
Silverlight.Web | 这个项目是启动项目,包含应用程序的入口页面。 |
主菜单
主菜单包含一个表格,其中列出了 2010 年 FIFA 世界杯的所有 8 个小组。
想法是选择一支国家队并开始比赛。然后您将必须严格按照 2010 年世界杯的赛程进行比赛。
为了实现这个队伍表格,我并没有过多使用 XAML。我主要通过编程来实现。您在下图看到的由 Grid
、TextBlock
和 Image
元素组成。我知道我本可以使用带有自定义模板的 ListBox
元素,但是……我想要更多的自由。通过以编程方式构建视觉元素,我认为我可以更好地控制动画、变换等。
下面的函数 GenerateGroups
创建了所有 32 支队伍,并将它们分成 8 个小组。
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);
}
}
Soccerlight 比赛场地
如果您在主菜单中选择了英格兰队,接下来您将被重定向到以下视图,这当然是英格兰在世界杯上的第一场比赛(英格兰对阵美国,6 月 12 日,在鲁斯坦堡)。
请注意,两支球队都以传统的 3-5-2 阵型开始比赛,其中 3 名后卫,5 名中场,2 名前锋。虽然这是所有球队在游戏中的默认阵型,但为选定的球队选择其他阵型并不难,因为阵型只是一个整数数组。
public Team()
{
Formation = new int[] {1, 3, 5, 2};
}
请注意,上面的代码片段中的数组以1开头。这是因为我们必须考虑到守门员。
请注意,下面的列表中的场地线条和圆圈实际上不是图像,而是由 Border
和 Ellipse
元素构成的。另请注意,四个角上有四分之一圆,可以通过使用 RectangleGeometry
来裁剪普通圆(参见下面的 Ellipse.Clip
标签)来轻松创建,从而只显示您想要的四分之一部分。
<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>
得分控制
得分控制显示了比赛剩余时间、球队昵称、一个闪烁的球指示当前比赛球队,以及……比分。
剩余时间由 DispatcherTimer
对象控制,该对象每秒触发一次 Tick
事件。这会传播到得分控制,以增加总剩余时间。
在真实的足球比赛中,有两场 45 分钟的半场,中间休息 15 分钟。然而,在 Soccerlight 中,当计时器达到 30 分钟时,比赛就结束了。
闪烁的球是一个有用的指示器,可以显示当前比赛的球队。
球门
每个球门由三个边框界定:两边各一个,后方一个。如果球撞击这些边框,它会像撞到墙壁一样反弹。此外,在球门的正面两侧,还有门柱,它们就像真实的圆柱形门柱一样,使球反弹,就像撞击圆形物体一样。
此外,球门还有一个透明的球网,为游戏增添了酷炫逼真的外观和感觉。
规则很简单:每当球进入球门时,应用程序都会更新得分控制,以增加进攻球队的比分。
Jabulani
正如一些读者可能知道的,Jabulani 是 FIFA 世界杯的创新官方用球,所以我决定在 Soccerlight World Cup 中也使用它。
一些球员对 FIFA 杯的 Jabulani 提出了抱怨,因为他们报告了“奇怪”的运动轨迹和不可预测的方向变化,而另一些球员则声明了对该球的喜爱。(另一方面,目前还没有 Soccerlight 球员对这个球提出抱怨……)
在 Soccerlight 中,涉及球的物理计算与涉及球员的物理计算没有区别(除了球员具有更大的直径和更大的摩擦系数)。事实上,球和球员都继承自同一个基类 Discoid
。
public class Ball : Discoid
{
public class Player : Discoid
{
参与者
每支球队有 11 名球员。在现实世界中,每名球员可能有不同的角色,但在 Soccerlight 中,它们被以相同的方式对待。例如,前锋和守门员之间的唯一区别是守门员离球门更近,而前锋的初始位置更靠近对方球门。也许在未来的版本中,我会定义不同的技能,例如,一名技术娴熟的前锋在射门时可能比其他普通球员更精准。
球员信息栏
每当您选择一名球员时,应用程序都会显示一个球员信息栏。该栏包含球员的基本信息:号码、姓名和照片。
每名 Soccerlight 球员都有号码、姓名和照片。号码和姓名在应用程序中硬编码,但照片是从 FIFA 网站获取的,如下面的代码片段所示。
imgPlayer.ImageSource = new BitmapImage(new Uri(string.Format(
"http://pt.fifa.com/imgml/tournament/worldcup2010/players/xl/{0}.png",
teamPlayer.ID), UriKind.Absolute));
下面的列表负责显示球员信息栏。
<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>
力量控制条
力量控制条是您用来校准射门力量的工具。您将 Jabulani 球在条上推得越高,射门的力量就越大。
每次用户点击力量控制条时,力量都会被重新定义。这是通过以下代码实现的。
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);
}
体育场屏幕
体育场屏幕的作用只是显示一支出线球队。就是这么简单。
体育场屏幕有三种不同的动画。首先是 translateAnimation
,它将“GOOAL”字符串从右向左平移。然后是 lettersOpacityAnimation
,它使屏幕上的字母淡入淡出。最后是 screenOpacityAnimation
,负责淡出整个体育场屏幕元素。
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"));
愿望清单和已知问题
游戏中的一些问题以及一些期望的改进,我将在未来的版本中进行更正/实现。
- 游戏仍然不处理犯规。起初,这似乎很容易实现,但根据犯规发生的位置,犯规后您可能需要重新安排球员,以免他们互相碰撞等。
- 仍然没有淘汰赛阶段的比赛。也许我会为其他比赛生成随机结果,如果您的球队一路获胜,您将从 16 强晋级到四分之一决赛、半决赛,并最终进入决赛。
- 实现一个在线的、基于服务的、人对人的版本游戏将会很酷。我认为 WCF 服务可以做到。
最终思考
如果您读到这里,我想感谢您的耐心。请告诉我您对这款应用的喜好。欢迎提出批评和建议,我愿意根据您的反馈改进这篇文章和应用程序。
历史
- 2010-06-29:第一个版本。